/tartjs

Tiny Actor Run-Time in JavaScript

Primary LanguageJavaScriptMIT LicenseMIT

tart

Stability: 1 - Experimental

NPM version

JavaScript implementation of Tiny Actor Run-Time.

Contributors

@dalnefre, @tristanls

Overview

The goal of tart is to provide the smallest possible actor library in JavaScript that has the full power of a "pure" actor model of computation.

tart happens to fit into a tweet :D

function(){var c=function(b){var a=function(m){setImmediate(function(){x.behavior(m)})},x={self:a,behavior:b,sponsor:c};return a};return c}

Modules

Here is a selection of modules from the authors of Tart:

Usage

To run the below example run:

npm run readme
"use strict";

var tart = require('../index.js');

var sponsor = tart.minimal();

// create an actor that has no state
var statelessActor = sponsor(function (message) {
    console.log('got message', message); 
});

// create an actor with state
var statefulActorBeh = function (state) {
    return function (message) {
        console.log('got message', message);
        console.log('actor state', state);
    };
};

var statefulActor = sponsor(statefulActorBeh({some: 'state'}));

// create an actor with state that changes behavior
var flipFlop = function (state) {
    var firstBeh = function (message) {
        console.log('firstBeh got message', message);
        console.log('actor state', state);
        this.behavior = secondBeh;
    };
    var secondBeh = function (message) {
        console.log('secondBeh got message', message);
        console.log('actor state', state);
        this.behavior = firstBeh;
    };
    return firstBeh;
};

var serialActor = sponsor(flipFlop({some: 'state'}));

// create an actor that creates a chain of actors
var chainActorBeh = function (count) {
    return function (message) {
        console.log('chain actor', count);
        if (--count >= 0) {
            var next = this.sponsor(chainActorBeh(count));
            next(message);
        }
    }; 
};

var chainActor = sponsor(chainActorBeh(10));

// send messages to the actors
statelessActor('some message');
statefulActor({some: 'other message'});
serialActor('first message');
serialActor('second message');
serialActor('third message');
serialActor('fourth message');
chainActor('go');

Tests

npm test

Benchmarks

Benchmarks were run on the Minimal implementation.

Erlang Challenge

Erlang Challenge consists of creating a ring of M actors, sending N simple messages around the ring and increasing M until running out of resources.

The benchmark implements a modified version of the challenge by creating 100,000 actors and running 10 simple messages around the ring.

npm run erlangChallenge

100,000 actor ring

starting 100000 actor ring
sending 10 messages
..........
done
all times in NANOSECONDS
construction time:
336492833
loop times:
221762650
212925428
213672756
215595649
215465809
216958143
217696839
221343351
222385758
226308376
loop average:
218411475.9

For rings of sizes larger than 4 Million you may need to expand memory available to V8. To do that, the following command will start the Erlang challenge with ~10GB of memory available:

node --max_old_space_size=10000 scripts/erlangChallenge.js

30,000,000 actor ring

30 Million actor ring benchmark took up about ~8.5 GB of memory.

starting 30000000 actor ring
sending 1 messages
.
done
all times in NANOSECONDS
construction time:
233098891645
loop times:
244265390009
loop average:
244265390009

Documentation

The Minimal implementation is the implementation optimized for fastest execution time. In contrast, Pluggable implementation allows for total control of the runtime and execution semantics. Although the default behavior of Pluggable is the same as Minimal, it is somewhat slower due to extra overhead incurred by pluggability of control and observability mechanisms.

Tweet

Public API

tart.tweet()

Creates a sponsor capability to create new actors with using the Tweetable implementation :D.

WARNING: If an exception is thrown during message processing the Tweetable run-time will crash. For fastest stable implementation use Minimal.

sponsor(behavior)

Same as the core Minimal implementation. See: sponsor(behavior)

actor(message)

Same as the core Minimal implementation. See: actor(message)

Minimal

Public API

tart.minimal([options])

  • options: Object (Default: undefined)
    • fail: Function (Default: function (exception) {}) function (exception) {} An optional handler to call if a sponsored actor behavior throws an exception.
  • Return: Function function (behavior) {} A capability to create new actors.

Creates a sponsor capability to create new actors with.

var tart = require('tart');
var sponsor = tart.minimal();

var reportingSponsor = tart.minimal({
    fail: function (exception) {
        console.dir(exception);
    }
});

sponsor(behavior)

  • behavior: Function function (message) {} Actor behavior to invoke every time an actor receives a message.
  • Return: Function function (message) {} Actor reference in form of a capability that can be invoked to send the actor a message.

Creates a new actor and returns the actor reference in form of a capability to send that actor a message.

var tart = require('tart');
var sponsor = tart.minimal();
var actor = sponsor(function (message) {
    console.log('got message', message); 
    console.log(this.self);
    console.log(this.behavior);
    console.log(this.sponsor);
});

When the behavior is invoked upon the receipt of a message, it's this will be bound with the following:

  • this.self: Function function (message) {} Reference to the actor that is executing the behavior (in form of a capability that can be invoked to send the actor a message).
  • this.behavior: Function function (message) {} The behavior of the actor. To change actor behavior (a "become" operation) assign a new function to this parameter.
  • this.sponsor: Function function (behavior) {} A capability to create new actors. To create a new actor call this.sponsor(behavior).

actor(message)

  • message: Any Any message.

Asynchronously sends the message to the actor.

var tart = require('tart');
var sponsor = tart.minimal();
var actor = sponsor(function behavior(message) {
    console.log('got message', message);
});
actor('hello actor world');

Pluggable

Public API

tart.pluggable([options])

  • options: Object (Default: undefined) Optional overrides.
    • constructConfig: Function (Default: function (options) {}) function (options) {} Configuration creation function that is given options. It should return a capability function (behavior) {} to create new actors.
    • deliver: Function (Default: function (context, message, options) {}) function (context, message, options) {} Deliver function that returns a function for dispatch to dispatch.
    • dispatch: Function (Default: setImmediate) function (deliver) {} Dispatch function for dispatching deliver closures.
    • fail: Function (Default: function (exception) {}) function (exception) {} An optional handler to call if a sponsored actor behavior throws an exception.
    • annotate: Function (Default: function (actor) { return actor; }) function (actor) {} An optional method to wrap/modify newly-created actors.
  • Return: Function function (behavior) {} A capability to create new actors.

Creates a sponsor capability to create new actors with and allows replacing parts of the implementation.

To run the below example run:

npm run pluggable
var tart = require('tart');

var dispatch = function (deliver) {
    console.log('delivering a message'); 
    deliver(); 
};

var deliver = function deliver(context, message, options) {
    console.log('delivering message', message, 'to context', context);
    return function deliver() {
        try {
            context.behavior(message);
        } catch (exception) {
            console.log('got exception', exception);
        }
    };
};

var constructConfig = function constructConfig(options) {
    var config = function create(behavior) {
        var actor = function send(message) {
            options.dispatch(options.deliver(context, message, options));
        };
        actor = options.annotate(actor);
        var context = {
            self: actor,
            behavior: behavior,
            sponsor: config
        };
        console.log('created actor in context', context);
        return actor;
    };
    return config;
};

var annotate = (function (n) {
    return function annotate(actor) {
        var id = '@' + n++;
        actor.toString = actor.inspect = function () {
            return id;
        };
        return actor;
    };
})(0);

var sponsor = tart.pluggable({
    constructConfig: constructConfig,
    deliver: deliver,
    dispatch: dispatch,
    annotate: annotate
});

var actor = sponsor(function (message) {
    console.log('got message', message);
});

actor('foo');

sponsor(behavior)

Same as the core Minimal implementation. See: sponsor(behavior)

actor(message)

Same as the core Minimal implementation. See: actor(message)

Releases

We follow semantic versioning policy (semver.org):

Given a version number MAJOR.MINOR.PATCH, increment the:

MAJOR version when you make incompatible API changes,
MINOR version when you add functionality in a backwards-compatible manner, and
PATCH version when you make backwards-compatible bug fixes.

Sources