croczilla.com 
 home   stratified   bits&pieces   blog   personal   
  home > blog > Introducing Stratified JavaScript
27 August
2009

Introducing Stratified JavaScript

[oni] 

This blog post is quite old now. Please see stratifiedjs.org for more up-to-date information about SJS

Here's a sneak preview of a project I've been working on in my spare time for a while, and which is nearing its first release:

'Stratified JavaScript' ('SJS') is a cross-browser extension to JavaScript which adds some concurrency features to the language. Without going into too much detail, the main features include:

(1) The ability to pause execution

The function

  hold(time_ms);

pauses execution of a piece of code for time_ms milliseconds. E.g.:

  var elem = document.getElementById("animated_element");
  var x = 0;
  while (true) {
    elem.style.left = x; 
    x = (x + 10) % 200;
    hold(100);
  }
(full sample: animate.html)

Note that 'hold' doesn't busy wait. The browser's UI stays fully functionally while this code executes.

(2) Fork-Join Parallelism

In conventional JavaScript, arguments to function calls are evaluated sequentially. In Stratified JavaScript, they are evaluated concurrently:

  function animate(elemname, duration_ms, step_ms) {
    var elem = document.getElementById(elemname);
    var start_time = new Date();
    var x = 0;
    do {
      elem.style.left = x;
      x = (x + 10) % 200;
      hold(step_ms);
    } while (duration_ms > new Date() - start_time);
  }

  function par(a, b) {
    alert("all done");
  }

  par(animate("animated_element_1", 10000, 100),
      animate("animated_element_2", 7000, 80));
(full sample: forkjoin.html)

Here, the two 'animate' calls are being executed concurrently. Once they both return, the body of 'par' gets called.

(3) Exploratory parallelism

Many concurrency problems fit into the pattern

"Explore options a,b,c,... concurrently. Once one of them yields a satisfactory result, return it and abort exploration of the remaining options."

Exploratory parallelism in Stratified JavaScript is embodied by the '@' ('alt', 'alternatives') operator:

  animate("animated_element_1", 10000, 100) @ 
  animate("animated_element_2", 7000, 80);

  alert("all done");
(full sample: alt.html)

This code pops up 'all done' after 7s (after the animation of animated_element_2 has finished). At the same time, the animation of animated_element_1 will be aborted.

In many ways, exploratory parallelism forms the heart of Stratified JavaScript. It is applicable to many situations which are very cumbersome to express in conventional thread-based parallelism.

(4) Suspend/Resume

Most JavaScript programs live by asynchronous events, e.g. events generated by button clicks, or asynchronous XMLHttpRequests. Stratified JavaScript contains a generic mechanism for converting these asynchronous constructs into synchronous ones:

   suspend {
     ... resume() ...
   }
   retract {
     ...
   }
   finally {
     ...
   }

This will be executed by first evaluating the code in the 'suspend' block and then suspending execution. Execution will resume when the 'resume' function defined in the suspend block is called. The optional 'retract' block will be executed if the current code is aborted before 'resume' was called. The optional 'finally' block will be executed in either case; if execution was resumed, or if execution was aborted.

E.g., this is how an event listener could be 'synchronized':

  function waitForEvent(event, elem) {
    suspend {
      var rv;
      var listener_func = function(e) {
        rv = e;
        resume();
      };
      elem.addEventListener(event, listener_func, false);
    }
    finally {
      elem.removeEventListener(event, listener_func, false);
    }
    return rv;
  }

With this function we can now e.g. wait for a button click:

  while (true) {
    waitForEvent("click", document.getElementById('button1'));
    alert("You clicked the button");
  }
(full sample: suspend.html)

Or something more complicated:

  function dump(message) {
    document.getElementById("output").innerHTML = message;
  }

  while (true) {
    var e = waitForEvent("click", document.getElementById('button1')) @
            waitForEvent("click", document.getElementById('button2')) @
            hold(5000);
    if (e)
      dump("You clicked button '"+e.target.id+"'");
    else
      dump("Click a button already!");
  }
(full sample: suspend2.html)

If all of this sounds similar to Oni, that's because it is. Under the hood, Stratified JavaScript is executed by a runtime based on Oni. Everything that can be expressed in Stratified JavaScript can be written directly in Oni without going through any compilation stage. The advantage of Stratified JavaScript is that it doesn't have the awkward functional syntax of Oni.


Posted by alex at 18:25 | Comments (7)
<< Ubiquity, Oni, and Composability | Main | Stratified JavaScript and Multitasking >>
Comments
Re: Introducing Stratified JavaScript

Are there any advantages to SJS over Narrative JavaScript or JavaScript strands?

Posted by: Kris Zyp at August 27,2009 19:48
Re: Introducing Stratified JavaScript

@Kris:
The most important enhancement of SJS versus Narrative
JavaScript or Strands is the support for 'exploratory parallelism' or
'parallel selection', as embodied by SJS's Alt operator ('@'). There
are many problems that are tricky to express with just threads, but
which can be expressed elegantly in a
structured, compositional way with the use of Alt.

As an example, consider searching through a bunch of blogs for a
particular phrase. We want the code to print the first blog post that
contains the phrase. In SJS this would look something like this:


function searchBlogs(phrase) {
return search(phrase, blogA) @
search(phrase, blogB) @
search(phrase, blogC);
}

print(searchBlogs('the quick brown fox'));


Note that as soon as one of the Alt branches returns a result, all
other pending branches are automatically aborted. All allocated
resources are freed, any pending XMLHttpRequests closed, etc. In a
conventional threaded approach, you would need to set up some form of
communication between the different search threads and take care of
resource management manually.

To further illustrate the compositional nature of Stratified
JavaScript, consider adding a global timeout: If the blog searching
hasn't yielded a result in 100s, we want to give up. Easy to express
in Stratified JavaScript:


print(searchBlogs('the quick brown fox')) @
( hold(100000) , print('timeout :-(') );


Again, the timeout will take care of cleaning up any pending
XMLHttpRequests, etc, automatically.

As a final enhancement, let's add a manual timeout:


print(searchBlogs('the quick brown fox')) @
( hold(100000), print('timeout :-(') ) @
( waitForEvent('click', 'cancel_button'), print('You cancelled!') );


As before, a click will take care of cleaning up, and conversely, a
timeout, or a successful search result will automatically cancel the
event listener.

Posted by: alex at August 27,2009 21:22
Re: Introducing Stratified JavaScript

Taking a glance at the code, it seems it doesn't take the advantage of generators/iterators and worker thread.
Could you kindly post more about how you get it work? Mostly interested in hold, parallelism .etc
:)

Posted by: Question at August 28,2009 02:26
Re: Introducing Stratified JavaScript

Ah, so you've actually done the next logical step :)
Yes, the syntax of Oni is its biggest problem. I've seen the same problem when looking into futures and other possible way to express concurrency - as long as you stay within the JavaScript language your code gets pretty ugly, the additional complexity makes the benefit questionable. And creating a JavaScript preprocessor just wasn't worth it for me.

Posted by: Wladimir Palant at August 28,2009 09:25
Re: Introducing Stratified JavaScript

@Question:

You're correct, the implementation doesn't use generators/iterators or
worker threads. The basic mechanism of SJS is to compile code into a
big Oni expression. E.g.


foo(); hold(100); bar();

becomes

Oni.Seq(Oni.Apply(Oni.Get('foo')),
Oni.Sleep(100),
Oni.Apply(Oni.Get('bar')))

This Oni expression is executed by a scheduler which maintains a tree
of continuations behind the scenes. At any point execution can be
suspended until some external asynchronous signal is received (e.g. in
Oni.Sleep() we suspend until we get a setTimeout callback). Oni knows
where to pick up execution again by looking at the continuation tree.

Oni can also pass signals down the continuation tree. This is e.g. how
an 'Alt' operator aborts some of its branches as soon as one of them
returns.

Note that while in theory the Oni scheduler could execute branches of
an SJS program on multiple threads (if the underlying JS engine had a
threading facility), currently SJS programs execute in a single
thread. SJS is not so much about exploiting parallelism for
performance reasons, but rather about making existing concurrency (in
the form of asychronous events, pending requests, etc) manageable.

@Wladimir:

You've hit the nail on the head :-)

Being syntax-less and expression-based, Oni is very easy to theorize
about, but it is a pain to write any sizeable program in it. For a
long time I shied away from creating a proper syntactic JavaScript
extension, but having Oni as a target language, coupled with Brendan's
excellent Narcissus parser made it fairly easy.

Posted by: alex at August 28,2009 11:24
Re: Introducing Stratified JavaScript

First I'd like to say thanks for doing this work and sharing it with the community.

You demonstrate using hold(100) inside of a while loop... what will hold(0) do there?

Although I understand that it is the tiniest use of this masterpiece, I am very interested in using SJS to eliminate the blocking nature of heavy computation in nested loops.

A hold(0) would seem to do this, or perhaps a different method for this purpose would be appropriate?

Posted by: Jonathan Cook at September 08,2009 21:26
Re: Introducing Stratified JavaScript

@Jonathan:
Good question - see my next blogpost!

Posted by: alex at September 10,2009 15:55