Skip to content

Commit

Permalink
Merge branch 'release/4.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
appurva21 committed Sep 25, 2024
2 parents 1ff5bdd + aaab63a commit 1390715
Show file tree
Hide file tree
Showing 10 changed files with 324 additions and 413 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [16, 18]
node-version: [18, 20]
os: [ubuntu-latest, windows-latest]
include:
- coverage: true
node-version: 20
node-version: latest
os: ubuntu-latest

steps:
Expand All @@ -88,4 +88,4 @@ jobs:

- if: ${{ matrix.coverage }}
name: Upload coverage
run: npm run codecov -- -c -Z -f .coverage/coverage-final.json -F unit
run: npm run codecov -- -c -Z -f .coverage/coverage-final.json -F unit -t ${{ secrets.CODECOV_TOKEN }}
9 changes: 9 additions & 0 deletions CHANGELOG.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
4.0.0:
date: 2024-09-25
breaking changes:
- GH-715 Dropped support for Node < v18
fixed bugs:
- GH-711 Fixed an issue where boot code was not being garbage collected
chores:
- GH-710 Refactor worker interface to be node alike

3.0.0:
date: 2024-06-11
new feature:
Expand Down
3 changes: 2 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ coverage:

# coverage status for unit tests
unit:
flags: unit
target: 100
flags:
- unit

parsers:
javascript:
Expand Down
3 changes: 1 addition & 2 deletions firmware/sandbox-base.browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ module.exports = `
(function (self) {
var init = function (e) {
self.removeEventListener('message', init);
const __init_uvm = e && (e.__init_uvm || (e.data && e.data.__init_uvm));
// eslint-disable-next-line no-eval
const __init_uvm = e?.data?.__init_uvm;
(typeof __init_uvm === 'string') && eval(__init_uvm);
};
self.addEventListener('message', init);
Expand Down
55 changes: 26 additions & 29 deletions lib/bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@ const Flatted = require('flatted'),
__uvm_emit = function (postMessage, args) {
postMessage({__id_uvm: "${id}", __emit_uvm: args});
}.bind(null, __self.postMessage.bind(__self));
__uvm_addEventListener = __self.addEventListener.bind(__self);
__uvm_on = __self.on.bind(__self);
${bridgeClientCode()}
(function (emit, id) {
__uvm_addEventListener("message", function (e) {
const { __emit_uvm, __id_uvm } = e?.data || e || {};
__uvm_on("message", function (e) {
const { __emit_uvm, __id_uvm } = e || {};
if (typeof __emit_uvm === 'string' && __id_uvm === id) {
emit(__emit_uvm);
}
});
}(__uvm_dispatch, "${id}"));
__uvm_dispatch = null; delete __uvm_dispatch;
__uvm_addEventListener = null; delete __uvm_addEventListener;
__uvm_on = null; delete __uvm_on;
(function (self, bridge, setTimeout) {
${Worker.__exceptionHandler}
Expand All @@ -67,11 +67,11 @@ module.exports = function (bridge, options, callback) {
firmwareCode;

const id = UVM_ID_ + randomNumber(),
{ bootCode, debug, bootTimeout, _sandbox } = options,

// function to forward messages emitted
forwardEmits = (e) => {
// e.data for Web Worker (MessageEvent.data)
const { __emit_uvm, __id_uvm } = e?.data || e || /* istanbul ignore next */ {};
const { __emit_uvm, __id_uvm } = e;

/* istanbul ignore next-line */
if (!(typeof __emit_uvm === 'string' && __id_uvm === id)) { return; }
Expand Down Expand Up @@ -100,17 +100,15 @@ module.exports = function (bridge, options, callback) {
if (!worker) { return; }

// remove event listeners for this sandbox
worker.removeEventListener(MESSAGE, forwardEmits);
worker.removeEventListener(ERROR, forwardErrors);
worker.removeEventListener(MESSAGE_ERROR, forwardErrors);
worker.removeEventListener(EXIT, forwardExit);
worker.off(MESSAGE, forwardEmits);
worker.off(ERROR, forwardErrors);
worker.off(MESSAGE_ERROR, forwardErrors);
worker.off(EXIT, forwardExit);

const terminate = !options._sandbox ?
worker.terminate.bind(worker, callback) :
callback;

(_sandbox ? Promise.resolve() : worker.terminate())
.then(() => { callback(); });
worker = null;
terminate && terminate();
};

// on load attach the dispatcher
Expand All @@ -134,24 +132,23 @@ module.exports = function (bridge, options, callback) {
});

// get firmware code string with boot code
firmwareCode = sandboxFirmware(options.bootCode, id, options.debug);
firmwareCode = sandboxFirmware(bootCode, id, debug);

// start boot timer, stops once we get the load signal, terminate otherwise
bootTimer = setTimeout(() => {
terminateWorker();
callback(new Error(`uvm: boot timed out after ${options.bootTimeout}ms.`));
}, options.bootTimeout);
callback(new Error(`uvm: boot timed out after ${bootTimeout}ms.`));
}, bootTimeout);

// if sandbox worker is provided, we simply need to init with firmware code
// @todo validate sandbox type or APIs
if (options._sandbox) {
worker = options._sandbox;

/* istanbul ignore else */
if (typeof worker.addEventListener !== 'function') {
// add event listener methods for Node.js worker
worker.addEventListener = worker.on.bind(worker);
worker.removeEventListener = worker.off.bind(worker);
if (_sandbox) {
worker = _sandbox;

// add event listener methods for Web worker
/* istanbul ignore next-line */
if (typeof worker.on !== 'function') {
Worker.attachNodeStyleListener(worker);
}

worker.postMessage({ __init_uvm: firmwareCode });
Expand All @@ -167,10 +164,10 @@ module.exports = function (bridge, options, callback) {

// add event listener for receiving events
// from worker (is removed on disconnect)
worker.addEventListener(MESSAGE, forwardEmits);
worker.addEventListener(ERROR, forwardErrors);
worker.addEventListener(MESSAGE_ERROR, forwardErrors);
worker.addEventListener(EXIT, forwardExit);
worker.on(MESSAGE, forwardEmits);
worker.on(ERROR, forwardErrors);
worker.on(MESSAGE_ERROR, forwardErrors);
worker.on(EXIT, forwardExit);

// equip bridge to disconnect (i.e. terminate the worker)
bridge._disconnect = terminateWorker;
Expand Down
53 changes: 40 additions & 13 deletions lib/worker.browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,54 @@ class WebWorker {
this.worker = new Worker(firmwareObjectURL);

window.URL.revokeObjectURL(firmwareObjectURL);
WebWorker.attachNodeStyleListener(this, this.worker);
}

addEventListener (name, listener) {
this.worker.addEventListener(name, listener);
postMessage (data) {
this.worker.postMessage(data);
}

removeEventListener (name, listener) {
this.worker.removeEventListener(name, listener);
}
terminate () {
if (this.worker) {
this.worker.terminate();
this.worker = null;
}

postMessage (data) {
this.worker.postMessage(data);
return Promise.resolve();
}

terminate (callback) {
if (!this.worker) { return; }
static attachNodeStyleListener (self, worker) {
function extract (name, event) {
switch (name) {
case 'message':
return event.data;
case 'error':
return event.error || new Error(event.message);
case 'unhandledrejection':
return event.reason;
default:
throw new Error('uvm: Unknown event type');
}
}

if (!worker) {
worker = self;
}

self.on = function (name, listener) {
worker.addEventListener(name, function (event) {
listener(extract(name, event));
});
};

this.worker.terminate();
this.worker = null;
callback && callback();
self.off = worker.removeEventListener.bind(worker);
}

static __self = 'self';
static __self = `(function () {
function ${WebWorker.attachNodeStyleListener.toString()}
attachNodeStyleListener(self);
return self;
}())`;

static __exceptionHandler = `
((close) => {
Expand All @@ -49,6 +74,7 @@ class WebWorker {
return bridge.emit('uncaughtException', event.error || event.reason);
}
// Instance of PromiseRejectionEvent
if (event.reason) {
event.preventDefault();
throw event.reason;
Expand All @@ -57,6 +83,7 @@ class WebWorker {
setTimeout(self.close, 0);
};
// Not using 'self.on' since we need the original event object in 'onError'.
self.addEventListener('error', onError);
self.addEventListener('unhandledrejection', onError);
`;
Expand Down
32 changes: 7 additions & 25 deletions lib/worker.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,18 @@
const { Worker } = require('worker_threads');

class NodeWorker {
class NodeWorker extends Worker {
constructor (bootCode, options) {
this.worker = new Worker(bootCode, {
super(bootCode, {
eval: true,
stdout: !options?.debug,
stderr: !options?.debug
stderr: !options?.debug,
execArgv: [
// TODO: To be removed when support for Node.js v18 is dropped
'--experimental-global-webcrypto'
]
});
}

addEventListener (name, listener) {
this.worker.on(name, listener);
}

removeEventListener (name, listener) {
this.worker.off(name, listener);
}

postMessage (data) {
this.worker.postMessage(data);
}

terminate (callback) {
/* istanbul ignore next-line */
if (!this.worker) { return; }

this.worker.terminate().then(() => {
callback && callback();
});
this.worker = null;
}

static __self = 'require("worker_threads").parentPort';

static __exceptionHandler = `
Expand Down
Loading

0 comments on commit 1390715

Please sign in to comment.