Table of Contents

Server-side Workers

Hop server-side workers are processes that run concurrently. Each worker possesses its own isolated memory heap. Workers cannot share JavaScript objects. Each worker possesses its own private JavaScript global objects. Worker communicate using message passing. Workers are the only mean for running concurrent JavaScript execution on the server.

Note: This page only documents server-side workers. The documentation of client-side workers can be found here.

Terminology

We define the parent thread as the thread which executes the new Worker constructor, and the worker thread as the thread which is created by the system in response to the new Worker instruction.

A worker thread is attached to one and only one parent thread.

Any thread may be the parent thread of multiple worker threads.

The parent to worker threads connection graph is a tree, rooted by the main thread of process.

Constructor

new Worker( path )

Workers are created using the Worker function which takes as parameter the source file of the worker. The path is resolved using the same rules as the require module importation function (see module).

Example:

var w = new Worker( "./slave.js" );

New Worker( path ) returns immediately, before the worker module is fully initialized. Messages sent to the worker are buffered until the worker is able to process them, so the parent thread may send messages as soon as the Workerobject is created.

However, since workers may also define services, the developer should ensure that services defined within the worker will not be invoked until the worker is fully initialized. In general, this is achieved by letting the worker pass the service handle to the parent thread. Note that services that are invoked as a result of a user action on a web client are very likely to be up and running long before the user clicks on the button triggering the service invocation.

Properties

The following properties define EventListeners for the parent thread, attached to Worker.

Worker.onerror

An EventListener called whenever an error occurs on Worker.

Worker.onexit

An EventListener called when Worker terminates.

Worker.onmessage

An EventListener called whenever the parent thread receives a message from the Worker thread. The message is stored in the dataproperty of the event.

Variables

The following variable can be set in a worker thread, it is implicitely attached to the parent thread.

onmessage

An EventListener called whenever the worker thread receives a message from its parent thread. The message is stored in the dataproperty of the event.

Methods

Worker.postMessage( msg )

Use this Method in the parent thread to send a message to a worker. The argument msg is a JavaScript object which is donated to the target. Donation depends on the type of the objects. The following rules apply:

  1. If msg is an immediate value, the value is transmitted as is.
  2. If msg is an Array, an ArrayBuffer, an ArrayBufferView, or a TypedBuffer, the array is donated to the target worker. All the values are donated to the target worker. The source worker is left with an array of length 0.
  3. In all other cases, msg is deep copied and each field is donated to the target.
  4. Service handles can be passed within a message.
  5. Objects made from the above data types are passed with the same donation rules.
  6. Objects that cannot be donated (Worker, Arguments, Function, Math, Json, Error, or Promise) are silently replaced with the undefined value.

postMessage( msg )

Use this function in the Worker thread to send a message to the implicit parent.

Basic communications

This example shows how to spawn server-side worker and how the master and slave worker communicate.

worker/worker.js

console.log( "starting worker-master..." );
var bar = require( "./bar.js" );

var w = new Worker( "./slave.js" );

w.onmessage = function( e ) {
   console.log( "received from the slave: ", e.data );
   console.log( "onmsg master bar=", bar.count( 1 ), " (expect 16)" );
}

w.postMessage( "hello slave" );

console.log( "master, bar=", bar.count( 10 ), " (expect 10)" );
console.log( "master, bar=", bar.count( 5 ), " (expect 15)" );

console.log( "master done...");

worker/slave.js

console.log( "starting worker-slave..." );

var bar = require( "./bar.js" );

onmessage = function( e ) {
   console.log( "received from master: ", e.data );
   postMessage( "what master?" );
}

postMessage( "hi master" );

console.log( "slave bar=", bar.count( 100 ), " (expect 100)" );
console.log( "slave bar=", bar.count( 3 ), " (expect 103)" );

worker/bar.js Note that the parent thread and the Worker thread each get their instance of bar.js.

console.log( "bar.js" );

var count = 0;

function count_inc( v ) {
   count += v;
   return count;
}

exports.count = count_inc;

Worker.terminate()

Terminates Worker as soon as possible. Sub Workers are recursively terminated.

close()

Use the function close to self terminate a Worker. Sub Workers are recursively terminated.

This example shows how to terminate workers.

worker2/worker2.js

console.log( "Master creating a Worker" );
var w = new Worker( "./slave.js" );

// can post as soon as the Worker object is created.
console.log( "Master sending a Work Order" );
w.postMessage( "Work Order" );


w.onmessage = function ( e ) {
   console.log( "Master received '%s'", e.data );
   console.log( "Master terminating worker" );
   w.terminate();
};

//w.onexit is useful to track worker self termination (not the case here).
w.onexit = function() {
   console.log( "Worker is terminated, I knew that already" );
}

worker2/slave.js

console.log( "Worker: starting" );

onmessage = function( e ) {
   console.log( "Worker received '%s'", e.data );
   console.log( "Worker processing ..." );
   setTimeout( function() {
      console.log( "Worker sending Report" );
      postMessage( "Report" );
   }, 1000 );
};

Workers and Services

This example demonstrates advanced use of JavaScript workers. The main task is responsible to initiate two slave twin workers running the same code, and create a browser service launch both workers in iframes. When launched, each slave worker defines a dynamic service and sends a service handle to the main thread to let the thread build the main html page.

Service handles, just like other hop.js objects, can be sent as worker messages.

Since there is a unique name space for services, all services created in slave.js are anonymous. Hop ensures that unique names are generated for each service.

Note that each slave.js worker operates its private counter. The same isolation property would apply to submodules required by slave.js.

Note: the worker5 service cannot be invoked until both slave workers are fully initialized, which is not an issue since the service is invoked from the client browser only.

Putting the createWorker calls within the main service would require an asynchronous coding style where the service implementation creates slave workers and returns only after the worker services are created.

worker5/worker5.js

function createWorker( title ) {
   var w = new Worker( "./slave.js" );
   w.onmessage = function( message ) {
      w.svc = message.data;
      console.log( 'main:  service handle received from %s', title );
   }
   w.title = title;
   console.log( 'main: %s created', title );
   return w;
}

var w1 = createWorker( 'left worker' );
var w2 = createWorker( 'right worker' );

service worker5() {
   return new Promise( function( resolve, reject ) {
      setTimeout( function() {
         resolve( <html>
           <div>
             <iframe style="border: 1px solid black" src=${w1.svc( w1.title )}/>
             <iframe style="border: 1px solid black" src=${w2.svc( w2.title )}/>
           </div>
         </html> ) }, 1000 );
   } );
}

console.log( "Go to \"http://%s:%d/hop/worker5\"", hop.hostname, hop.port );

worker5/slave.js

console.log( "starting worker-slave..." );

var counter = 0;

var svc = service( title ) {
   console.log( '%s service invoked: building html frame', title );
   var count = <span id="counter">${counter}</span>;
   return <html>
     <h1>${title}</h1>
     <div>counter: ${count}</div>
     <button onclick=~{
        ${service () {
           console.log( '%s counter: set value to %d', title, counter + 1 );
           return ++counter;
        }}().post( function( v ) { ${count}.innerHTML = v } )
     }>
     inc me
     </button>
   </html>
}

onmessage = function( message ) {
   console.log( "slave worker: message received from main thread: ignoring ",
                message.data );
}

postMessage( svc );

console.log( 'slave worker: service handle sent to main thread' );