Async JS

The agent source code is complex because JavaScript is single-threaded, so we must use asynchronous APIs.

WebDriver scripts suffer the same problem because nearly all operations require browser I/O.

Here are some notes.  Also see the WebDriverJs API documentation.

Understanding Async JS

In JavaScript you shouldn't do:
var data = fs.readfileSync("foo");
because it'd block the main (and *only*) thread.  This is especially significant for long-lived I/O calls or exec's.

Instead, JS developers are strongly encouraged to use an async function:
    fs.readfile("foo", function(e, data) { ... });
and process the data in the callback.  The above call returns immediately.

KEY POINT:  NEVER do work after calling an async function, because the async hasn't finished yet!  Instead, resume work in the callback!

WebDriver provides a "schedule" library that makes it easier to chain these callbacks together:
    // see our helper process_utils.scheduleFunction
    app_.schedule('my comment', function() {
      var done = new webdriver.promise.Deferred();
      fs.readfile("foo", function(e, data) {
        if (e) {
          done.reject(e);
        } else {
          done.fulfill(data);
        }
      });
      return d.promise;
    }).then(function(data) {...}, function(e) {...});
"then" is a "done.promise" function to attach a callback -- if these callbacks do blocking I/O, they must be scheduled too!
The only reason to use "then" is if the "fulfill" passes a value back.  E.g.:
    app_schedule("A").then(function() {
      app_.schedule("B");
    });
is  equivalent to:
    app_schedule("A");
    app_.schedule("B");

KEY POINT: The promise library keeps track of pending & nested schedules, e.g. if you do:
    app_.schedule("A", function() {
      app.schedule("B", ...);
      app.schedule("C", ...);
    }).then(... schedule("D", ...));
    app._schedule("E", ...);
it'll run in the expected ABCDE order, not ADBCE or other races (e.g. we don't want ACBDE if C finishes before B).

KEY POINT:  NEVER do non-scheduled work after a schedule!  This includes anything after calling a function that schedules!


RFE: create a linter that asserts the above property:
1) Define a "@schedule" function annotation  (or some JS equivalent)
2) Notionally mark "app_.schedule" and all async functions (e.g. fs.read) as "@schedule"
3) Assert that any function that calls a "@schedule"-marked function must itself be marked "@schedule"
4) Assert that, once a function calls a "@schedule" function, all subsequent calls are to "@schedule" functions.
5) Optional: after a "@schedule" call, also allow read/write of immutable vars.  This is okay but tricky to get right.
Probably still a good idea as an OSS contrib, but low priority...
Comments