DAVE'S LIFE ON HOLD

Concurrent Javascript & Erlang style message passing

Today I added a few nice additions to the latest version of Phos. Since I've been writing a lot of Erlang the past few weeks on another related project, I decided that I should add the equivalent of Erlang processes to the Phos object set. The result of about an hour of hacking is a fully functional process system for pseudo-concurrent processing in Javascript. The first function that I implemented was spawn which creates a new process to which we can attach behavior:


Function.prototype.spawn = function() return fun[op].apply(fun,message);
else fun["_"].apply(fun,[ op, message]);
" alt="
var fun = function() {
if (fun.caller != null) return fun.send.apply(fun,arguments);
if (fun.Mailbox.length) {
var message = fun.Mailbox.shift();
var op = message.shift();
if (typeof(fun[op]) == "function")
return fun[op].apply(fun,message);
else fun["_"].apply(fun,[ op, message]);
" />
};
fun.Mailbox = ;
return fun.start();
};

In this case, spawn creates a new function which has a Mailbox, a dispatch lookup, and a default message handler _. The default message handler is inspired by Smaltalk's doesNotUnderstand method, and by default simply logs a message to the console saying that the function doesn't understand the message:

Function.prototype._ = function(op) " alt="
console.log(this.name + " does not understand " + op);
" />

The functions generated by spawn have 2 different modes of operation. The primary mode is as the API to the process, which sends a message to the mail box. For example, if we spawn a function called foo, we can send it a message by invoking it as follows:

var foo = Function.spawn();
foo("alert","this is a test");

Since we have not attached a behavior to foo to process alerts this will result in the following output in the console:

foo('alert','this is a test')
function () return fun[op].apply(fun,message);
else fun["_"].apply(fun,[ op, message]);
" alt="
if (fun.caller != null) return fun.send.apply(fun,arguments);
if (fun.Mailbox.length) {
var message = fun.Mailbox.shift();
var op = message.shift();
if (typeof(fun[op]) == "function")
return fun[op].apply(fun,message);
else fun["_"].apply(fun,[ op, message]);
" />
}
Function:57 does not understand alert

But if we then attach some behavior to the foo.alert method of the function we can fire off an alert:

foo.does('alert', function(x)  alert(x) );
foo('alert','this is a test');

Which will then pop open a new alert with the message "this is a test".
catch(e) " href="http://2.bp.blogspot.com/_XCDTVvEbBMU/TU9eFrriiLI/AAAAAAAAAGE/TKfhxwKkshY/s1600/Screen%2Bshot%2B2011-02-06%2Bat%2B9.50.18%2BPM.png">
By default, spawn fires off an interval timer for each function that is spawned.

Function.prototype.start = function() <br />        var fun = this;<br />        if (fun.Pid) return;<br />        this.Pid = setInterval( fun, fun.Delay );<br />        return this;<br />

Function.prototype.stop = function() <br />        clearInterval(this.Pid);<br />        this.Pid = false;<br />        return this;<br />
This interval timer has a delay that can be set as a value in milliseconds. Passing in a value of 0, effectively makes the function burn CPU, and is generally not recommended. The delay works as a sort of priority rating, allowing you to create different tiers of responsiveness. To change a function's delay property, you must first stop the process which will cancel its interval timer, and then start it again with the new delay.

Function.prototype.delay = function() <br />        if (arguments.length) {<br />                this.Delay = arguments[0];<br />                return this;<br />
return this.delay;
}

Because each function processes its messages in the order that they were received, each function defines its own job queue. Additionally, since each function can contain its own state properties, it is possible to maintain a finite state machine within the function itself. Also, since multiple function properties can be associated with each function, on process can respond to many different messages. The messaging protocol is rather simple:

myFunction( 'message', arguments ... )

Since the first parameter is the name of the method you wish to invoke, you can name each transition in your state machine and invoke them via message send. The function itself is always passed along as the object on which the method is applied, so its full state is also available via this in the method's context. It is trivial to pass along complex representation of state in additional properties of the function. Currently only Mailbox,Pid,Delay, and Pending are used to store state information in the mechanisms used by this module. Pending is used to create a chain of continuations using the then() wrapper, which isn't necessary if you use full function processes and message passing.

Sample Application



chatLog = A.function().
does('draw', function(x)  The.Screen.print(x).print().
does('clear', function()  The.Screen.clear() ).
does('sync', function() then(function(messages) { chatLog('clear'); messages.each(function (x) { chatLog('draw',x) " alt=" "http://chat.dloh.org".get().
then(function(messages) { chatLog('clear'); messages.each(function (x) { chatLog('draw',x) " />)
})

So why would you want such a thing? Well look at the sample chat log widget in the listing above. It is effectively a message server that has 3 base operations: draw,clear, and sync. Each operation can be invoked by any 3rd party process, but each is performed primarily as a result of an asynchronous HTTP fetch via XHR. We could have also written it as follows:

chatLog = A.function().
does('draw', function(x)  The.Screen.print(x).print().
does('clear', function()  The.Screen.clear() ).
does('sync', function() then(function(messages) { chatLog.clear(); messages.each(function (x) { chatLog.draw(x) " alt=" "http://chat.dloh.org".get().
then(function(messages) { chatLog.clear(); messages.each(function (x) { chatLog.draw(x) " />)
})

And it will have the same observable effect, but in a fully synchronous fashion but using only the XHR thread! This might not seem important, until you consider what happens when there are more than one object writing to that chatLog.

chatEditor = A.function().
does('post', function(x)  chatLog('draw',x); chatServer('send',x).then('sync') ).

In this case, we want to show the user that we've posted our message, by appending it directly to the chat log, and then we post it to the server. Only after the chat server has acknowledged our post, do we then request the chatServer apply its sync method, which will then redisplay the chat with our message and the other new messages in the correct order. This allows us to be more responsive to the user's actions, and have multiple concurrent processes communicate between each other better encapsulating the behaviors. It also allows us to defer action until a time slice becomes available for a given subsystem, and not block all processing in particularly heavy activities.

This all become much more useful as well when you attach multiple nested contexts into a page, and use things like <iframe> and <object> tags to embed content. Using HTML5 cross document messaging with a Publisher/Subscriber model messaging hub in conjunction with message passing process functions dramatically simplifies the programming model as both local and remote messages are all processed asynchronously. When user driven events, timers, network events, and cross document messages are all in play, having a unified model become essential.

Looking at the direction of Phos in the near future, I am almost certain that all of the core subsystems will be re-implemented using this model. It will make the rendering pipeline simpler. It will make event dispatching cleaner as well. Finally, it will in conjunction with the PubSubHub module, make it possible to build the Web Widgets Platform in a way that will be reusable across nearly all existing web sites. Finally, it dovetails nicely with the backend work I've been doing in Erlang, and will provide the sort of fault tolerance currently missing from the single-threaded Phos.