Search code examples
javascriptmultithreadinggoogle-chromeweb-worker

Nested Web Worker in Chrome


I am trying to spin up a Web Worker from inside a Web Worker using Chrome. Historically, this has been a problem space with the Chrome Host API, but:

According to ChromeStatus,as of 2018-10-22

Dedicated workers can create nested workers, but shared workers and service workers cannot.

Creating a nested dedicated worker from a shared worker is not yet supported.

Nested shared workers are also in the spec, but there is no plan to support them at this time.

Lucky me; I'm using a dedicated worker.

I can find no information available that formally contradicts this declaration of support in Chrome for desktop release 69 (I am using Chrome 72) and my nested Worker just sits on his duff and refuses to get Janice her Coffee.

I have three moving parts at play: a SlaveDriver, a Minion, and a Peon. The SlaveDriver delegates to the Minion. The Minion delegates to the Peon, the Peon does the work, and each receiver takes the credit.

SlaveDriver

(
function(){
     var minion = new Worker('./Features/Work/Work.Minion.js');
     let crackTheWhip= ()=>
     {
        let message = {ID: 1};
        minion.postMessage(
           JSON.stringify(message)
        );
        console.log(message.ID);
     };
     let take_the_credit = (message)=>
     {
        let work = JSON.parse(message.data);
        console.log("Hot Coffee!");
        console.log(JSON.stringify(work));
     };
     minion.onmessage = take_the_credit;

     return {
        GetJaniceHerCoffee: crackTheWhip
     };
})();

Minion ./Features/Work/Work.Minion.js

(
   function(){
      var self = this;
      var peon = new Worker('./Features/Work/Work.Peon.js');
      let receiveWorkOrder = (message)=>
      {
          console.log('Delegating to a Peon');
          peon.postMessage(
              message.data
          );
      };
      let take_the_credit = (message)=>
      {
          console.log('Taking the credit');
          let work = JSON.parse(message.data);
          self.postMessage(JSON.stringify(
             work
          ));

      };
      peon.onmessage = take_the_credit;
      self.onmessage = receiveWorkOrder;
   }
)();

Peon ./Features/Work/Work.Peon.js

(
   function(){
       var self = this;
       let receiveWorkOrder = (message)=>
       {
           console.log("surfing LinkedIn.");
           console.log("surfing stackoverflow");
           console.log("...wth is this? *sigh*");

           let work_result = {Value: "Coffee"};
           self.postMessage(JSON.stringify(
               work_result
           ));
       };
       self.onmessage = receiveWorkOrder;
   }
)();

If I stop delegating at the minion and have him do all of the work of the peon, everything processes fine. But, as soon as I try to have the minion delegate to the peon, it gets squirrely on me. In DevTools, I can see the minion activate in my Threads list when I postMessage to it. My debugging context jumps to the worker thread and when I postMessage to the peon, the Peon thread gets added to the Threads list but all propagation seems to stop. The logs to the console do not happen. The debugging context does not jump to the Peon thread. My minion's take_the_credit callback does not receive a response.

The Peon just sits there and does not do his work.

Why is my peon refusing to get Janice her coffee?


Solution

  • The answer to my question is currently found in the Spawning Subworkers subsection of the MDN documentation on Using Web Workers

    Workers may spawn more workers if they wish. So-called sub-workers must be hosted within the same origin as the parent page. Also, the URIs for subworkers are resolved relative to the parent worker's location rather than that of the owning page. This makes it easier for workers to keep track of where their dependencies are.

    Ultimately, the problem that I experienced was rooted in the fact that when my minion worker attempted to spawn its peon, it attempted to make reference to a file that was relative to document root rather than from the context of the worker but nothing was output to the console indicating that the file did not exist.

    To tie this simple example out:

    Slavedriver

    <script type="application/javascript">
            var slave_driver = (
                function(){
                    let minion = new Worker('/Features/Work/Work.Minion.js');
                    let crackTheWhip = ()=>
                    {
                        minion.postMessage(
                            JSON.stringify({SERVICE: "get"})
                        );
                    };
                    let take_the_credit = (message)=>
                    {
                        console.log("It's about time");
                        alert(message.data);
                    };
                    minion.onmessage = take_the_credit;
                    return {
                        GetJaniceHerCoffee: crackTheWhip
                    }
                }
            )();
    
            slave_driver.GetJaniceHerCoffee();
        </script>
    

    Minion ./Features/Work/Work.Minion.js

    (
       function(){
          var self = this;
          let home = location;
          let peon = new Worker('/Work.Peon.js');
          let receiveWorkOrder = (message)=>
          {
             console.log("Delegating to a Peon");
             peon.postMessage(
                message.data
             );
          }
          let take_the_credit = (message)=>
          {
             console.log("It's about time!");
             console.log("Minion Taking the Credit");
             let work = JSON.parse(message.data);
             self.postMessage(JSON.stringify(work));
          };
        
          peon.onmessage = take_the_credit;
          self.onmessage = receiveWorkOrder;
       }
    )();
    

    Peon ./Features/Work/Work.Peon.js

    (
     function(){
         var self = this;
         let receiveWorkOrder = (message)=>
         {
           console.log("Peon surfing stackoverflow");
           console.log("Peon surfing ebay for beanie babies, cuz they're coming back, yo");
           console.log("...wth is this? *sigh*");
    
           let work_result = {Value: "Coffee"};
           self.postMessage(JSON.stringify(
               work_result
           ));
         };
         self.onmessage = receiveWorkOrder;
      })();
    

    Notice the small change to the file reference made when the minion spawns the peon. If you drop the "slavedriver" script into the body of an index.html file and the relevant modules into their identified locations, you should get an alert that displays {"Value":"Coffee"} when you open index.