From 9f69cffbe7b52aaa2b1709712207378d9b8e15ef Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 2 Feb 2016 21:02:55 -0500 Subject: [PATCH] Ground network; minor refactorings and bugfixes; smoketest example --- js/examples/smoketest/index.html | 13 ++++++ js/examples/smoketest/index.js | 31 +++++++++++++ js/src/ground.js | 77 ++++++++++++++++++++++++++++++++ js/src/main.js | 5 +-- js/src/mux.js | 2 +- js/src/patch.js | 7 ++- js/src/syndicate.js | 49 ++++++++++++-------- js/test/test-mux.js | 2 +- js/test/test-patch.js | 2 +- 9 files changed, 160 insertions(+), 28 deletions(-) create mode 100644 js/examples/smoketest/index.html create mode 100644 js/examples/smoketest/index.js create mode 100644 js/src/ground.js diff --git a/js/examples/smoketest/index.html b/js/examples/smoketest/index.html new file mode 100644 index 0000000..f5a020e --- /dev/null +++ b/js/examples/smoketest/index.html @@ -0,0 +1,13 @@ + + + + Syndicate: Smoketest + + + + + + +

Smoketest

+ + diff --git a/js/examples/smoketest/index.js b/js/examples/smoketest/index.js new file mode 100644 index 0000000..8c9dcc5 --- /dev/null +++ b/js/examples/smoketest/index.js @@ -0,0 +1,31 @@ +var G; +$(document).ready(function () { + var Network = Syndicate.Network; + var sub = Syndicate.sub; + var __ = Syndicate.__; + var _$ = Syndicate._$; + + G = new Syndicate.Ground(function () { + console.log('starting ground boot'); + + Network.spawn({ + counter: 0, + boot: function () {}, + handleEvent: function (e) {}, + step: function () { + Network.send(["beep", this.counter++]); + return this.counter <= 10; + } + }); + + Network.spawn({ + boot: function () { return sub(["beep", __]); }, + handleEvent: function (e) { + if (e.type === 'message') { + console.log("beep!", e.message[1]); + } + } + }); + }); + G.startStepping(); +}); diff --git a/js/src/ground.js b/js/src/ground.js new file mode 100644 index 0000000..c5e5d41 --- /dev/null +++ b/js/src/ground.js @@ -0,0 +1,77 @@ +var Immutable = require('immutable'); +var Syndicate = require('./syndicate.js'); +var Network = Syndicate.Network; + +function Ground(bootFn) { + var self = this; + this.stepperId = null; + this.baseStack = Immutable.List.of({ network: this, activePid: -1 }); + Network.withNetworkStack(this.baseStack, function () { + self.network = new Network(bootFn); + }); +} + +Ground.prototype.step = function () { + var self = this; + return Network.withNetworkStack(this.baseStack, function () { + return self.network.step(); + }); +}; + +Ground.prototype.checkPid = function (pid) { + if (pid !== -1) console.error('Weird pid in Ground markPidRunnable', pid); +}; + +Ground.prototype.markPidRunnable = function (pid) { + this.checkPid(pid); + this.startStepping(); +}; + +Ground.prototype.startStepping = function () { + var self = this; + if (this.stepperId) return; + if (this.step()) { + this.stepperId = setTimeout(function () { + self.stepperId = null; + self.startStepping(); + }, 0); + } +}; + +Ground.prototype.stopStepping = function () { + if (this.stepperId) { + clearTimeout(this.stepperId); + this.stepperId = null; + } +}; + +Ground.prototype.kill = function (pid, exn) { + this.checkPid(pid); + console.log("Ground network terminated"); + this.stopStepping(); +}; + +Ground.prototype.enqueueAction = function (pid, action) { + this.checkPid(pid); + + switch (action.type) { + case 'stateChange': + if (action.patch.isNonEmpty()) { + console.error('You have subscribed to a nonexistent event source.', + action.patch.pretty()); + } + break; + + case 'message': + console.error('You have sent a message into the outer void.', action); + break; + + default: + console.error('Internal error: unexpected action at ground level', action); + break; + } +}; + +/////////////////////////////////////////////////////////////////////////// + +module.exports.Ground = Ground; diff --git a/js/src/main.js b/js/src/main.js index b7a05c2..fd5bd3d 100644 --- a/js/src/main.js +++ b/js/src/main.js @@ -26,12 +26,11 @@ copyKeys(['emptyPatch', 'observe', 'atMeta', 'advertise', 'isObserve', 'isAtMeta', 'isAdvertise', 'assert', 'retract', 'sub', 'unsub', 'pub', 'unpub', - 'patchSeq', - 'prettyPatch'], + 'patchSeq'], module.exports, module.exports.Patch); -// module.exports.Ground = require("./ground.js").Ground; +module.exports.Ground = require("./ground.js").Ground; // module.exports.Actor = require("./actor.js").Actor; // module.exports.Spy = require("./spy.js").Spy; // module.exports.WakeDetector = require("./wake-detector.js").WakeDetector; diff --git a/js/src/mux.js b/js/src/mux.js index f244845..6247235 100644 --- a/js/src/mux.js +++ b/js/src/mux.js @@ -87,7 +87,7 @@ function computeAffectedPids(routingTable, delta) { Mux.prototype.routeMessage = function (body) { if (Route.matchValue(this.routingTable, body) === null) { - return Route.matchValue(m.routingTable, Patch.observe(body)) || Immutable.Set(); + return Route.matchValue(this.routingTable, Patch.observe(body)) || Immutable.Set(); } else { // Some other stream has declared body return Immutable.Set(); diff --git a/js/src/patch.js b/js/src/patch.js index 7077d3f..1a3b838 100644 --- a/js/src/patch.js +++ b/js/src/patch.js @@ -206,9 +206,9 @@ Patch.prototype.projectObjects = function (compiledProjection) { Route.projectObjects(this.removed, compiledProjection)]; }; -function prettyPatch(p) { - return ("<<<<<<<< Removed:\n" + Route.prettyTrie(p.removed) + - "======== Added:\n" + Route.prettyTrie(p.added) + +Patch.prototype.pretty = function () { + return ("<<<<<<<< Removed:\n" + Route.prettyTrie(this.removed) + + "======== Added:\n" + Route.prettyTrie(this.added) + ">>>>>>>>\n"); } @@ -240,4 +240,3 @@ module.exports.unpub = unpub; module.exports.patchSeq = patchSeq; module.exports.computePatch = computePatch; module.exports.biasedIntersection = biasedIntersection; -module.exports.prettyPatch = prettyPatch; diff --git a/js/src/syndicate.js b/js/src/syndicate.js index a64c392..9939bc0 100644 --- a/js/src/syndicate.js +++ b/js/src/syndicate.js @@ -101,37 +101,43 @@ Network.exit = function (exn) { Network.current().kill(Network.activePid(), exn); }; -Network.terminateNetwork = function () { +Network.exitNetwork = function () { Network.enqueueAction(terminateNetwork()); }; +Network.inertBehavior = { + handleEvent: function (e) {} +}; + // Instance methods Network.prototype.asChild = function (pid, f, omitLivenessCheck) { + var self = this; var p = this.processTable.get(pid, null); if (!omitLivenessCheck && (p === null)) { console.warn("Network.asChild eliding invocation of dead process", pid); return; } - return Network.withWorldStack(Network.stack.push({ network: this, activePid: pid }), - function () { - try { - return f(p); - } catch (e) { - this.kill(pid, e); - } - }); + return Network.withNetworkStack( + Network.stack.push({ network: this, activePid: pid }), + function () { + try { + return f(p); + } catch (e) { + self.kill(pid, e); + } + }); }; Network.prototype.kill = function (pid, exn) { if (exn && exn.stack) { - console.log("Process exited", pid, exn, exn.stack); + console.log("Process exiting", pid, exn, exn.stack); } else { - console.log("Process exited", pid, exn); + console.log("Process exiting", pid, exn); } var p = this.processTable.get(pid); - this.processTable = this.processTable.remove(pid); + this.processTable = this.processTable.set(pid, { behavior: Network.inertBehavior }); if (p) { if (p.behavior.trapexit) { this.asChild(pid, function () { return p.behavior.trapexit(exn); }, true); @@ -172,13 +178,14 @@ Network.prototype.enqueueAction = function (pid, action) { }; Network.prototype.dispatchActions = function () { + var self = this; var actionQueue = this.pendingActions; this.pendingActions = Immutable.List(); var alive = true; actionQueue.forEach(function (entry) { var pid = entry[0]; var action = entry[1]; - if (!this.interpretAction(pid, action)) { + if (!self.interpretAction(pid, action)) { alive = false; return false; } @@ -191,19 +198,22 @@ Network.prototype.markRunnable = function (pid) { }; Network.prototype.runRunnablePids = function () { + var self = this; var pidSet = this.runnablePids; this.runnablePids = Immutable.Set(); pidSet.forEach(function (pid) { - var childBusy = this.asChild(pid, function (p) { + var childBusy = self.asChild(pid, function (p) { return p.behavior.step // exists, haven't called it yet && p.behavior.step(); }); - if (childBusy) this.markRunnable(pid); + if (childBusy) self.markRunnable(pid); }); return true; }; Network.prototype.interpretAction = function (pid, action) { + var self = this; + switch (action.type) { case 'stateChange': var oldMux = this.mux.shallowCopy(); @@ -218,7 +228,7 @@ Network.prototype.interpretAction = function (pid, action) { Network.send(action.message[1]); } else { this.mux.routeMessage(action.message).forEach(function (pid) { - this.deliverEvent(pid, action); + self.deliverEvent(pid, action); }); } return true; @@ -240,6 +250,8 @@ Network.prototype.interpretAction = function (pid, action) { case 'terminate': var oldMux = this.mux.shallowCopy(); this.deliverPatches(oldMux, this.mux.removeStream(pid)); + console.log("Process exit complete", pid); + this.processTable = this.processTable.remove(pid); return true; case 'terminateNetwork': @@ -254,15 +266,16 @@ Network.prototype.interpretAction = function (pid, action) { }; Network.prototype.deliverPatches = function (oldMux, updateStreamResult) { + var self = this; var events = Mux.computeEvents(oldMux, this.mux, updateStreamResult); events.eventMap.forEach(function (patch, pid) { - this.deliverEvent(pid, stateChange(patch)); + self.deliverEvent(pid, stateChange(patch)); }); events.metaEvents.forEach(Network.stateChange); }; Network.prototype.deliverEvent = function (pid, event) { - var childBusy = this.asChild(pid, function (p) { return p.handleEvent(event); }); + var childBusy = this.asChild(pid, function (p) { return p.behavior.handleEvent(event); }); if (childBusy) this.markRunnable(pid); }; diff --git a/js/test/test-mux.js b/js/test/test-mux.js index f269a38..661f0b5 100644 --- a/js/test/test-mux.js +++ b/js/test/test-mux.js @@ -13,7 +13,7 @@ function checkPrettyTrie(m, expected) { } function checkPrettyPatch(p, expectedAdded, expectedRemoved) { - expect(Patch.prettyPatch(p)).to.equal( + expect(p.pretty()).to.equal( ('<<<<<<<< Removed:\n' + expectedRemoved.join('\n') + '======== Added:\n' + expectedAdded.join('\n') + '>>>>>>>>\n')); diff --git a/js/test/test-patch.js b/js/test/test-patch.js index bf0f2c2..ec083a1 100644 --- a/js/test/test-patch.js +++ b/js/test/test-patch.js @@ -8,7 +8,7 @@ var __ = Route.__; var _$ = Route._$; function checkPrettyPatch(p, expectedAdded, expectedRemoved) { - expect(Patch.prettyPatch(p)).to.equal( + expect(p.pretty()).to.equal( ('<<<<<<<< Removed:\n' + expectedRemoved.join('\n') + '======== Added:\n' + expectedAdded.join('\n') + '>>>>>>>>\n'));