Permalink
291 lines (236 sloc) 13.7 KB

Namespace zone

Table of Contents

Introduction

Zone is a key concept of napajs that exposes multi-thread capabilities in JavaScript world, which is a logical group of symmetric workers for specific tasks.

Please note that it's not the same zone concept of a context object for async calls in Dart, or Angular, or a proposal in TC39.

Multiple workers vs. Multiple zones

Zone consists of one or multiple JavaScript threads, we name each thread worker. Workers within a zone are symmetric, which means code executed on any worker from the zone should return the same result, and the internal state of every worker should be the same from a long-running point of view.

Multiple zones can co-exist in the same process, with each loading different code, bearing different states or applying different policies, like heap size, etc. The purpose of having multiple zone is to allow multiple roles for complex work, each role loads the minimum resources for its own usage.

Zone types

There are two types of zone:

  • Napa zone - zone consists of Napa.js managed JavaScript workers (V8 isolates). Can be multiple, each may contain multiple workers. Workers in Napa zone support partial Node.JS APIs.
  • Node zone - a 'virtual' zone which exposes Node.js eventloop, has access to full Node.js capabilities.

Zone operations

There are two operations, designed to reinforce the symmetry of workers within a zone:

  1. Broadcast - run code that changes worker state on all workers, returning a promise for the pending operation. Through the promise, we can only know if the operation succeed or failed. Usually we use broadcast to bootstrap the application, pre-cache objects, or change application settings. Function broadcastSync is also offered as a synchronized version of broadcast operations.
  2. Execute - run code that doesn't change worker state on an arbitrary worker, returning a promise of getting the result. Execute is designed for doing the real work.

Zone operations are on a basis of first-come-first-serve, while broadcast takes higher priority over execute.

API

create(id: string, settings: ZoneSettings): Zone

It creates a Napa zone with a string id. If zone with the id is already created, error will be thrown. ZoneSettings can be specified for creating zones.

Example 1: Create a zone with id 'zone1', using default ZoneSettings.

var napa = require('napajs');
var zone1 = napa.zone.create('zone1');

Example 2: Create a zone with id 'zone2', with 1 worker.

var zone2 = napa.zone.create('zone2', {
    workers: 1
});

get(id: string): Zone

It gets a reference of zone by an id. Error will be thrown if the zone doesn't exist.

Example:

var zone = napa.zone.get('zone1');

current: Zone

It returns a reference of the zone of the currently running isolate. If it's under node, it returns the node zone.

Example: Get current zone.

var zone = napa.zone.current;

node: Zone

It returns a reference to the node zone. It is equivalent to napa.zone.get('node');

Example:

var zone = napa.zone.node;

Interface ZoneSettings

Settings for zones, which will be specified during the creation of zones. If not specified, DEFAULT_SETTINGS will be used.

settings.workers: number

Number of workers in the zone.

Object DEFAULT_SETTINGS

Default settings for creating zones.

{
    workers: 2
}

Interface Zone

Zone is the basic concept to execute JavaScript and apply policies in Napa. You can find its definition in Introduction. Through the Zone API, developers can broadcast JavaScript code on all workers, or execute a function on one of them. When you program against a zone, it is the best practice to ensure all workers within a zone are symmetrical to each other, that is, you should not assume a worker may maintain its own states.

The two major sets of APIs are broadcast and execute, which are asynchronous operations with a few variations on their inputs.

zone.id: string

It gets the id of the zone.

zone.broadcast(code: string): Promise<void>

It asynchronously broadcasts a snippet of JavaScript code in a string to all workers, which returns a Promise of void. If any of the workers failed to execute the code, the promise will be rejected with an error message.

Example:

var napa = require('napajs');
var zone = napa.zone.get('zone1');
zone.broadcast('var state = 0;')
    .then(() => {
        console.log('broadcast succeeded.');
    })
    .catch((error) => {
        console.log('broadcast failed.')
    });

zone.broadcastSync(code: string): void

It synchronously broadcasts a snippet of JavaScript code in a string to all workers. If any of the workers failed to execute the code, an exception will be thrown with an error message.

Remarks:

  • It's not allowed to call broadcastSync on current zone. It will cause a deadlock

Example:

var napa = require('napajs');
var zone = napa.zone.get('zone1');
try {
    zone.broadcastSync('var state = 0;');
    console.log('broadcast succeeded.');
} catch (error) {
    console.log('broadcast failed.')
}

zone.broadcast(function: (...args: any[]) => void | Promise<void>, args?: any[]): Promise<void>

It asynchronously broadcasts an anonymous function with its arguments to all workers, which returns a Promise of void. If any of the workers failed to execute the code, the promise will be rejected with an error message.

Remarks:

  • If the function returns a Promise object, its state will be adopted to broadcast's return value
  • The function object cannot access variables from closure
  • Unless the function object has an origin property, it will use the current file as origin, which will be used to set __filename and __dirname. (See transporting functions)
  • Transport context is not available in broadcast. All types that depend on TransportContext (eg. ShareableWrap, Transportable) cannot be passed in arguments list.

Example:

zone.broadcast((state) => {
        require('some-module').setModuleState(state)
    }, [{field1: 1}])
    .then(() => {
        console.log('broadcast succeeded.');
    })
    .catch((error) => {
        console.log('broadcast failed:', error)
    });

zone.broadcastSync(function: (...args: any[]) => void | Promise<void>, args?: any[]): void

It synchronously broadcasts an anonymous function with its arguments to all workers. If any of the workers failed to execute the code, an exception will be thrown with an error message.

Remarks:

  • It's not allowed to call broadcastSync on current zone. It will cause a deadlock
  • If the function returns a Promise object, its state will be adopted. Function broadcastSync will not return until that Promise resolved or rejected.
  • The function object cannot access variables from closure
  • Unless the function object has an origin property, it will use the current file as origin, which will be used to set __filename and __dirname. (See transporting functions)
  • Transport context is not available in broadcast. All types that depend on TransportContext (eg. ShareableWrap, Transportable) cannot be passed in arguments list.

Example:

try {
    zone.broadcastSync((state) => {
            require('some-module').setModuleState(state)
        }, [{field1: 1}]);
    console.log('broadcast succeeded.');
} catch (error) {
    console.log('broadcast failed:', error)
}

zone.execute(moduleName: string, functionName: string, args?: any[], options?: CallOptions): Promise<any>

Execute a function asynchronously on an arbitrary worker via module name and function name. Arguments can be of any JavaScript type that is transportable. It returns a Promise of Result. If an error happens, either bad code, user exception, or timeout is reached, the promise will be rejected.

Example: Execute function bar in module foo, with arguments [1, 'hello', { field1: 1 }]. 300ms timeout is applied.

zone.execute(
    'foo', 
    'bar', 
    [1, "hello", {field1: 1}], 
    { timeout: 300 })
    .then((result) => {
        console.log('execute succeeded:', result.value);
    })
    .catch((error) => {
        console.log('execute failed:', error);
    });

zone.execute(function: (...args: any[]) => any, args?: any[], options?: CallOptions): Promise<any>

Execute a function object asynchronously on an arbitrary worker. Arguments can be of any JavaScript type that is transportable. It returns a Promise of Result. If an error happens, either bad code, user exception, or timeout is reached, promise will be rejected.

Remarks:

  • If the function returns a Promise object, it will be adopted
  • The function object cannot access variables from closure
  • Unless the function object has an origin property, it will use the current file as origin, which will be used to set __filename and __dirname. (See transporting functions)

Example:

zone.execute((a: number, b: string, c: object) => {
        return a + b + JSON.stringify(c);
    }, [1, "hello", {field1: 1}])
    .then((result) => {
        console.log('execute succeeded:', result.value);
    })
    .catch((error) => {
        console.log('execute failed:', error);
    });

Output:

execute succeeded: 1hello{"field1":1}

Another example demonstrates accessing __filename when executing an anonymous function:

// File: /usr/file1.js
zone.execute(() => { console.log(__filename);});

Output:

/usr/file1.js

Interface CallOptions

Interface for options to call functions in zone.execute.

options.timeout: number

Timeout in milliseconds. Default value 0 indicates no timeout.

Interface Result

Interface to access the return value of execute.

result.value: any

JavaScript value returned from the function which is invoked from zone.execute/executeSync. Napa marshalls/unmarshalls transportable values between different workers (V8 isolates). Unmarshalling will happen when the first result.value is queried.

Example:

var value = result.value;

result.payload: string

Marshalled payload (in JSON) from the returned value. This field is for users that want to pass results through to its caller, where the unmarshalled value is not required.

Example:

var payload = result.payload;

result.transportContext: transport.TransportContext

TransportContext that is required to unmarshall result.payload into result.value.

Example:

var napa = require('napajs');
var zone = napa.zone.create('zone1');
zone.execute(() => { return 0; }, [])
    .then((result) => {
        // Manually marshall.
        var transportContext = result.transportContext;
        var value = napa.transport.unmarshall(result.payload, result.transportContext);

        // result.value and manual unmarshall from payload are the same.
        assert.equal(value, result.value);
    });