The U8 workers

Purpose

Allow starting parallel processes in independed, isolated VM, that can fully use multicode/multi CPU hardware. Exchange information between such processes.

Requested feautres

  • the interface should be simple and intuitive
  • workers should be either all shared or easily allow sharing.
  • it should allow exchange informarion

Draft?

Let's start discussion with:

// create a worker and call its methods:

const workers = require('workers');

async function sample() {

    // start worker or connect to existing one
    let worker = workers.start("worker.js");

    // We call remote function and wait for the async the answer:
    let result = await worker.foo("bar", "baz");


    // if we want to get some calls from it, we just export a function:
    worker.export.reportFoo = (payload) => {
        console.log("I got a Fpp report: "+payload);
    };

    // and now we can it without waiting the result (no-result):
    worker.fooBar("buz");

    //
    worker.events.close = (woker) => {
        // event handler:
        // remote part has closed us
    }

    // if the worker is no longer needed, it will be GC'd and will 
    // therefore close connection
}

// ------------------------------------------------------------ worker.js

// Woker start in main() function - well, this _is a tradition:
async function main(gate) {
    // unlike normal main, we have an only argument: gate
    // the purpose of main function is to prepare the gate for future use, 
    // exporting some of our methods:

    gate.export.foo = async (bar, baz) => {
        sleep(100);
        return `foo_${bar}_${baz}`;
    }

    gate.export.fooBar = (buz) => {
        console.log("we got a buz: "+buz);
        // and we call remote the same way it asks us:
        gate.fooBar();
    };

    gate.export.panic = () => {
        // this effectively closes gate connection
        gate.close();
    };
}

Symmetric calls between threads

// sample 2: calling remote ---------------- 

let worker = new AdvancedWorker("worker.js", {exclusive: true});

worker.export.myMethod = (foo) => { return ${foo}bar; }
worker.options.someSetting = "bazz";

var remoteInterface = await worker.connect();

let remotebar = await remoteInterface.bar("bar");

// sample 2: worker ------------------------

function main(gate) {
    gate.export.bar = async (bar) => {
        return local: foo${bar} remote: ${await gate.remoteInterface.foo("foo")};
    };
    if( gate.options.someSetting == "bazz" ) {
        //...
    }
}

Features

// create a worker and call its methods:

const workers = require('workers');

async function sample() {

    // register class instance serializers if not provided by class
    workers.register("Car", carSerializer, carDeserializer);

    // start worker or connect to existing one
    // options.numCPUs (int) number of CPUs for worker methods that can be 
    //   started in parallel
    // options.services (object) map of connection strings of third-party 
    //   services, to prevent creating connections every time worker method started
    // options.ttl (int) millis to wait for graceful shutdown in case of freeze/fatal error
    // options will be available in worker context
    let worker = workers.start("worker.js", options);

    // Add worker method on the fly with eval. Possible, but dangerous.
    worker.defineMethod("fooBarBaz", function(msg) { console.log("Hello " + msg); });

    // and call of it
    worker.fooBarBaz("world");

    worker.beep(new Car("bmw")); // with registered serializer we can pass class instances to workers

    // alternative way to run methods to protect reserved names ("export", "events")
    worker.run("foo", params); // expands to worker.methods.foo(params)
    worker.run("export", data);


}

// ------------------------------------------------------------ worker.js

async function main(gate) {
    console.log(this.options); // access to worker options

    gate.export.beep = (car) => {
        car.beep(); // car is instance of Car
    }

    function sayHello() {
        sleep(5000);
        console.log("Hello!");
    }

    // flag to make sure that there is 
    // only one call in process at the same time. Needs semaphores based on 
    // third-party service such as Redis
    sayHello.unique = true;

    gate.export.sayHello = sayHello;
}