зеркало из https://github.com/mozilla/gecko-dev.git
Bug 801598 - Exposing OS.File communication mechanism as an independent API. r=froydnj
--HG-- rename : toolkit/components/osfile/modules/_PromiseWorker.jsm => toolkit/components/promiseworker/PromiseWorker.jsm
This commit is contained in:
Родитель
711ff71243
Коммит
8a06cee147
|
@ -33,6 +33,7 @@ PARALLEL_DIRS += [
|
|||
'passwordmgr',
|
||||
'perf',
|
||||
'places',
|
||||
'promiseworker',
|
||||
'prompts',
|
||||
'protobuf',
|
||||
'reflect',
|
||||
|
|
|
@ -1,205 +0,0 @@
|
|||
/**
|
||||
* Thin wrapper around a ChromeWorker that wraps postMessage/onmessage/onerror
|
||||
* as promises.
|
||||
*
|
||||
* Not for public use yet.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["PromiseWorker"];
|
||||
|
||||
// The library of promises.
|
||||
Components.utils.import("resource://gre/modules/Promise.jsm", this);
|
||||
|
||||
/**
|
||||
* An implementation of queues (FIFO).
|
||||
*
|
||||
* The current implementation uses one array, runs in O(n ^ 2), and is optimized
|
||||
* for the case in which queues are generally short.
|
||||
*/
|
||||
let Queue = function Queue() {
|
||||
this._array = [];
|
||||
};
|
||||
Queue.prototype = {
|
||||
pop: function pop() {
|
||||
return this._array.shift();
|
||||
},
|
||||
push: function push(x) {
|
||||
return this._array.push(x);
|
||||
},
|
||||
isEmpty: function isEmpty() {
|
||||
return this._array.length == 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An object responsible for dispatching messages to
|
||||
* a chrome worker and routing the responses.
|
||||
*
|
||||
* @param {string} url The url containing the source code for this worker,
|
||||
* as in constructor ChromeWorker.
|
||||
* @param {Function} log A logging function.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function PromiseWorker(url, log) {
|
||||
if (typeof url != "string") {
|
||||
throw new TypeError("Expecting a string");
|
||||
}
|
||||
if (typeof log !== "function") {
|
||||
throw new TypeError("log is expected to be a function");
|
||||
}
|
||||
this._log = log;
|
||||
this._url = url;
|
||||
|
||||
/**
|
||||
* The queue of deferred, waiting for the completion of their
|
||||
* respective job by the worker.
|
||||
*
|
||||
* Each item in the list may contain an additional field |closure|,
|
||||
* used to store strong references to value that must not be
|
||||
* garbage-collected before the reply has been received (e.g.
|
||||
* arrays).
|
||||
*
|
||||
* @type {Queue<{deferred:deferred, closure:*=}>}
|
||||
*/
|
||||
this._queue = new Queue();
|
||||
|
||||
/**
|
||||
* The number of the current message.
|
||||
*
|
||||
* Used for debugging purposes.
|
||||
*/
|
||||
this._id = 0;
|
||||
|
||||
/**
|
||||
* The instant at which the worker was launched.
|
||||
*/
|
||||
this.launchTimeStamp = null;
|
||||
|
||||
/**
|
||||
* Timestamps provided by the worker for statistics purposes.
|
||||
*/
|
||||
this.workerTimeStamps = null;
|
||||
}
|
||||
PromiseWorker.prototype = {
|
||||
/**
|
||||
* Instantiate the worker lazily.
|
||||
*/
|
||||
get _worker() {
|
||||
delete this._worker;
|
||||
let worker = new ChromeWorker(this._url);
|
||||
let self = this;
|
||||
Object.defineProperty(this, "_worker", {value:
|
||||
worker
|
||||
});
|
||||
|
||||
// We assume that we call to _worker for the purpose of calling
|
||||
// postMessage().
|
||||
this.launchTimeStamp = Date.now();
|
||||
|
||||
/**
|
||||
* Receive errors that are not instances of OS.File.Error, propagate
|
||||
* them to the listeners.
|
||||
*
|
||||
* The worker knows how to serialize errors that are instances
|
||||
* of |OS.File.Error|. These are treated by |worker.onmessage|.
|
||||
* However, for other errors, we rely on DOM's mechanism for
|
||||
* serializing errors, which transmits these errors through
|
||||
* |worker.onerror|.
|
||||
*
|
||||
* @param {Error} error Some JS error.
|
||||
*/
|
||||
worker.onerror = function onerror(error) {
|
||||
self._log("Received uncaught error from worker", error.message, error.filename, error.lineno);
|
||||
error.preventDefault();
|
||||
let {deferred} = self._queue.pop();
|
||||
deferred.reject(error);
|
||||
};
|
||||
|
||||
/**
|
||||
* Receive messages from the worker, propagate them to the listeners.
|
||||
*
|
||||
* Messages must have one of the following shapes:
|
||||
* - {ok: some_value} in case of success
|
||||
* - {fail: some_error} in case of error, where
|
||||
* some_error is an instance of |PromiseWorker.WorkerError|
|
||||
*
|
||||
* Messages may also contain a field |id| to help
|
||||
* with debugging.
|
||||
*
|
||||
* Messages may also optionally contain a field |durationMs|, holding
|
||||
* the duration of the function call in milliseconds.
|
||||
*
|
||||
* @param {*} msg The message received from the worker.
|
||||
*/
|
||||
worker.onmessage = function onmessage(msg) {
|
||||
self._log("Received message from worker", msg.data);
|
||||
let handler = self._queue.pop();
|
||||
let deferred = handler.deferred;
|
||||
let data = msg.data;
|
||||
if (data.id != handler.id) {
|
||||
throw new Error("Internal error: expecting msg " + handler.id + ", " +
|
||||
" got " + data.id + ": " + JSON.stringify(msg.data));
|
||||
}
|
||||
if ("timeStamps" in data) {
|
||||
self.workerTimeStamps = data.timeStamps;
|
||||
}
|
||||
if ("ok" in data) {
|
||||
// Pass the data to the listeners.
|
||||
deferred.resolve(data);
|
||||
} else if ("StopIteration" in data) {
|
||||
// We have received a StopIteration error
|
||||
deferred.reject(StopIteration);
|
||||
} if ("fail" in data) {
|
||||
// We have received an error that was serialized by the
|
||||
// worker.
|
||||
deferred.reject(new PromiseWorker.WorkerError(data.fail));
|
||||
}
|
||||
};
|
||||
return worker;
|
||||
},
|
||||
|
||||
/**
|
||||
* Post a message to a worker.
|
||||
*
|
||||
* @param {string} fun The name of the function to call.
|
||||
* @param {Array} array The contents of the message.
|
||||
* @param {*=} closure An object holding references that should not be
|
||||
* garbage-collected before the message treatment is complete.
|
||||
*
|
||||
* @return {promise}
|
||||
*/
|
||||
post: function post(fun, array, closure) {
|
||||
let deferred = Promise.defer();
|
||||
let id = ++this._id;
|
||||
let message = {fun: fun, args: array, id: id};
|
||||
this._log("Posting message", message);
|
||||
try {
|
||||
this._worker.postMessage(message);
|
||||
} catch (ex if typeof ex == "number") {
|
||||
this._log("Could not post message", message, "due to xpcom error", ex);
|
||||
// handle raw xpcom errors (see eg bug 961317)
|
||||
return Promise.reject(new Components.Exception("Error in postMessage", ex));
|
||||
} catch (ex) {
|
||||
this._log("Could not post message", message, "due to error", ex);
|
||||
return Promise.reject(ex);
|
||||
}
|
||||
|
||||
this._queue.push({deferred:deferred, closure: closure, id: id});
|
||||
this._log("Message posted");
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An error that has been serialized by the worker.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
PromiseWorker.WorkerError = function WorkerError(data) {
|
||||
this.data = data;
|
||||
};
|
||||
|
||||
this.PromiseWorker = PromiseWorker;
|
|
@ -7,7 +7,6 @@
|
|||
JS_MODULES_PATH = 'modules/osfile'
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'_PromiseWorker.jsm',
|
||||
'osfile_async_front.jsm',
|
||||
'osfile_async_worker.js',
|
||||
'osfile_native.jsm',
|
||||
|
|
|
@ -0,0 +1,342 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* A wrapper around ChromeWorker with extended capabilities designed
|
||||
* to simplify main thread-to-worker thread asynchronous function calls.
|
||||
*
|
||||
* This wrapper:
|
||||
* - groups requests and responses as a method `post` that returns a `Promise`;
|
||||
* - ensures that exceptions thrown on the worker thread are correctly deserialized;
|
||||
* - provides some utilities for benchmarking various operations.
|
||||
*
|
||||
* Generally, you should use PromiseWorker.jsm along with its worker-side
|
||||
* counterpart PromiseWorker.js.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["BasePromiseWorker"];
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
/**
|
||||
* An implementation of queues (FIFO).
|
||||
*
|
||||
* The current implementation uses one array, runs in O(n ^ 2), and is optimized
|
||||
* for the case in which queues are generally short.
|
||||
*/
|
||||
function Queue() {
|
||||
this._array = [];
|
||||
};
|
||||
Queue.prototype = {
|
||||
pop: function pop() {
|
||||
return this._array.shift();
|
||||
},
|
||||
push: function push(x) {
|
||||
return this._array.push(x);
|
||||
},
|
||||
isEmpty: function isEmpty() {
|
||||
return this._array.length == 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructors for decoding standard exceptions received from the
|
||||
* worker.
|
||||
*/
|
||||
const EXCEPTION_CONSTRUCTORS = {
|
||||
EvalError: function(error) {
|
||||
let result = new EvalError(error.message, error.fileName, error.lineNumber);
|
||||
result.stack = error.stack;
|
||||
return result;
|
||||
},
|
||||
InternalError: function(error) {
|
||||
let result = new InternalError(error.message, error.fileName, error.lineNumber);
|
||||
result.stack = error.stack;
|
||||
return result;
|
||||
},
|
||||
RangeError: function(error) {
|
||||
let result = new RangeError(error.message, error.fileName, error.lineNumber);
|
||||
result.stack = error.stack;
|
||||
return result;
|
||||
},
|
||||
ReferenceError: function(error) {
|
||||
let result = new ReferenceError(error.message, error.fileName, error.lineNumber);
|
||||
result.stack = error.stack;
|
||||
return result;
|
||||
},
|
||||
SyntaxError: function(error) {
|
||||
let result = new SyntaxError(error.message, error.fileName, error.lineNumber);
|
||||
result.stack = error.stack;
|
||||
return result;
|
||||
},
|
||||
TypeError: function(error) {
|
||||
let result = new TypeError(error.message, error.fileName, error.lineNumber);
|
||||
result.stack = error.stack;
|
||||
return result;
|
||||
},
|
||||
URIError: function(error) {
|
||||
let result = new URIError(error.message, error.fileName, error.lineNumber);
|
||||
result.stack = error.stack;
|
||||
return result;
|
||||
},
|
||||
StopIteration: function() {
|
||||
return StopIteration;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An object responsible for dispatching messages to a chrome worker
|
||||
* and routing the responses.
|
||||
*
|
||||
* Instances of this constructor who need logging may provide a method
|
||||
* `log: function(...args) { ... }` in charge of printing out (or
|
||||
* discarding) logs.
|
||||
*
|
||||
* Instances of this constructor may add exception handlers to
|
||||
* `this.ExceptionHandlers`, if they need to handle custom exceptions.
|
||||
*
|
||||
* @param {string} url The url containing the source code for this worker,
|
||||
* as in constructor ChromeWorker.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
this.BasePromiseWorker = function(url) {
|
||||
if (typeof url != "string") {
|
||||
throw new TypeError("Expecting a string");
|
||||
}
|
||||
this._url = url;
|
||||
|
||||
/**
|
||||
* A set of methods, with the following
|
||||
*
|
||||
* ConstructorName: function({message, fileName, lineNumber}) {
|
||||
* // Construct a new instance of ConstructorName based on
|
||||
* // `message`, `fileName`, `lineNumber`
|
||||
* }
|
||||
*
|
||||
* By default, this covers EvalError, InternalError, RangeError,
|
||||
* ReferenceError, SyntaxError, TypeError, URIError, StopIteration.
|
||||
*/
|
||||
this.ExceptionHandlers = Object.create(EXCEPTION_CONSTRUCTORS);
|
||||
|
||||
/**
|
||||
* The queue of deferred, waiting for the completion of their
|
||||
* respective job by the worker.
|
||||
*
|
||||
* Each item in the list may contain an additional field |closure|,
|
||||
* used to store strong references to value that must not be
|
||||
* garbage-collected before the reply has been received (e.g.
|
||||
* arrays).
|
||||
*
|
||||
* @type {Queue<{deferred:deferred, closure:*=}>}
|
||||
*/
|
||||
this._queue = new Queue();
|
||||
|
||||
/**
|
||||
* The number of the current message.
|
||||
*
|
||||
* Used for debugging purposes.
|
||||
*/
|
||||
this._id = 0;
|
||||
|
||||
/**
|
||||
* The instant at which the worker was launched.
|
||||
*/
|
||||
this.launchTimeStamp = null;
|
||||
|
||||
/**
|
||||
* Timestamps provided by the worker for statistics purposes.
|
||||
*/
|
||||
this.workerTimeStamps = null;
|
||||
};
|
||||
this.BasePromiseWorker.prototype = {
|
||||
log: function() {
|
||||
// By Default, ignore all logs.
|
||||
},
|
||||
|
||||
/**
|
||||
* Instantiate the worker lazily.
|
||||
*/
|
||||
get _worker() {
|
||||
delete this._worker;
|
||||
let worker = new ChromeWorker(this._url);
|
||||
Object.defineProperty(this, "_worker", {value:
|
||||
worker
|
||||
});
|
||||
|
||||
// We assume that we call to _worker for the purpose of calling
|
||||
// postMessage().
|
||||
this.launchTimeStamp = Date.now();
|
||||
|
||||
/**
|
||||
* Receive errors that have been serialized by the built-in mechanism
|
||||
* of DOM/Chrome Workers.
|
||||
*
|
||||
* PromiseWorker.js knows how to serialize a number of errors
|
||||
* without losing information. These are treated by
|
||||
* |worker.onmessage|. However, for other errors, we rely on
|
||||
* DOM's mechanism for serializing errors, which transmits these
|
||||
* errors through |worker.onerror|.
|
||||
*
|
||||
* @param {Error} error Some JS error.
|
||||
*/
|
||||
worker.onerror = error => {
|
||||
this.log("Received uncaught error from worker", error.message, error.filename, error.lineno);
|
||||
error.preventDefault();
|
||||
let {deferred} = this._queue.pop();
|
||||
deferred.reject(error);
|
||||
};
|
||||
|
||||
/**
|
||||
* Receive messages from the worker, propagate them to the listeners.
|
||||
*
|
||||
* Messages must have one of the following shapes:
|
||||
* - {ok: some_value} in case of success
|
||||
* - {fail: some_error} in case of error, where
|
||||
* some_error is an instance of |PromiseWorker.WorkerError|
|
||||
*
|
||||
* Messages may also contain a field |id| to help
|
||||
* with debugging.
|
||||
*
|
||||
* Messages may also optionally contain a field |durationMs|, holding
|
||||
* the duration of the function call in milliseconds.
|
||||
*
|
||||
* @param {*} msg The message received from the worker.
|
||||
*/
|
||||
worker.onmessage = msg => {
|
||||
this.log("Received message from worker", msg.data);
|
||||
let handler = this._queue.pop();
|
||||
let deferred = handler.deferred;
|
||||
let data = msg.data;
|
||||
if (data.id != handler.id) {
|
||||
throw new Error("Internal error: expecting msg " + handler.id + ", " +
|
||||
" got " + data.id + ": " + JSON.stringify(msg.data));
|
||||
}
|
||||
if ("timeStamps" in data) {
|
||||
this.workerTimeStamps = data.timeStamps;
|
||||
}
|
||||
if ("ok" in data) {
|
||||
// Pass the data to the listeners.
|
||||
deferred.resolve(data);
|
||||
} else if ("fail" in data) {
|
||||
// We have received an error that was serialized by the
|
||||
// worker.
|
||||
deferred.reject(new WorkerError(data.fail));
|
||||
}
|
||||
};
|
||||
return worker;
|
||||
},
|
||||
|
||||
/**
|
||||
* Post a message to a worker.
|
||||
*
|
||||
* @param {string} fun The name of the function to call.
|
||||
* @param {Array} args The arguments to pass to `fun`. By convention,
|
||||
* the last argument may be an object `options` with some of the following
|
||||
* fields:
|
||||
* - {number|null} outExecutionDuration A parameter to be filled with the
|
||||
* duration of the off main thread execution for this call.
|
||||
* @param {*=} closure An object holding references that should not be
|
||||
* garbage-collected before the message treatment is complete.
|
||||
*
|
||||
* @return {promise}
|
||||
*/
|
||||
post: function(fun, args, closure) {
|
||||
return Task.spawn(function* postMessage() {
|
||||
let id = ++this._id;
|
||||
let message = {fun: fun, args: args, id: id};
|
||||
this.log("Posting message", message);
|
||||
try {
|
||||
this._worker.postMessage(message);
|
||||
} catch (ex if typeof ex == "number") {
|
||||
this.log("Could not post message", message, "due to xpcom error", ex);
|
||||
// handle raw xpcom errors (see eg bug 961317)
|
||||
throw new Components.Exception("Error in postMessage", ex);
|
||||
} catch (ex) {
|
||||
this.log("Could not post message", message, "due to error", ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
let deferred = Promise.defer();
|
||||
this._queue.push({deferred:deferred, closure: closure, id: id});
|
||||
this.log("Message posted");
|
||||
|
||||
let reply;
|
||||
let isError = false;
|
||||
try {
|
||||
this.log("Expecting reply");
|
||||
reply = yield deferred.promise;
|
||||
} catch (error) {
|
||||
this.log("Got error", error);
|
||||
reply = error;
|
||||
isError = true;
|
||||
|
||||
if (error instanceof WorkerError) {
|
||||
// We know how to deserialize most well-known errors
|
||||
throw this.ExceptionHandlers[error.data.exn](error.data);
|
||||
}
|
||||
|
||||
if (error instanceof ErrorEvent) {
|
||||
// Other errors get propagated as instances of ErrorEvent
|
||||
this.log("Error serialized by DOM", error.message, error.filename, error.lineno);
|
||||
throw new Error(error.message, error.filename, error.lineno);
|
||||
}
|
||||
|
||||
// We don't know about this kind of error
|
||||
throw error;
|
||||
}
|
||||
|
||||
// By convention, the last argument may be an object `options`.
|
||||
let options = null;
|
||||
if (args) {
|
||||
options = args[args.length - 1];
|
||||
}
|
||||
|
||||
// Check for duration and return result.
|
||||
if (!options ||
|
||||
typeof options !== "object" ||
|
||||
!("outExecutionDuration" in options)) {
|
||||
return reply.ok;
|
||||
}
|
||||
// If reply.durationMs is not present, just return the result,
|
||||
// without updating durations (there was an error in the method
|
||||
// dispatch).
|
||||
if (!("durationMs" in reply)) {
|
||||
return reply.ok;
|
||||
}
|
||||
// Bug 874425 demonstrates that two successive calls to Date.now()
|
||||
// can actually produce an interval with negative duration.
|
||||
// We assume that this is due to an operation that is so short
|
||||
// that Date.now() is not monotonic, so we round this up to 0.
|
||||
let durationMs = Math.max(0, reply.durationMs);
|
||||
// Accumulate (or initialize) outExecutionDuration
|
||||
if (typeof options.outExecutionDuration == "number") {
|
||||
options.outExecutionDuration += durationMs;
|
||||
} else {
|
||||
options.outExecutionDuration = durationMs;
|
||||
}
|
||||
return reply.ok;
|
||||
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An error that has been serialized by the worker.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function WorkerError(data) {
|
||||
this.data = data;
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
PARALLEL_DIRS += [
|
||||
'worker'
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES = [
|
||||
'PromiseWorker.jsm',
|
||||
]
|
|
@ -0,0 +1,206 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* A wrapper around `self` with extended capabilities designed
|
||||
* to simplify main thread-to-worker thread asynchronous function calls.
|
||||
*
|
||||
* This wrapper:
|
||||
* - groups requests and responses as a method `post` that returns a `Promise`;
|
||||
* - ensures that exceptions thrown on the worker thread are correctly serialized;
|
||||
* - provides some utilities for benchmarking various operations.
|
||||
*
|
||||
* Generally, you should use PromiseWorker.js along with its main thread-side
|
||||
* counterpart PromiseWorker.jsm.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof Components != "undefined") {
|
||||
throw new Error("This module is meant to be used from the worker thread");
|
||||
}
|
||||
if (typeof require == "undefined" || typeof module == "undefined") {
|
||||
throw new Error("this module is meant to be imported using the implementation of require() at resource://gre/modules/workers/require.js");
|
||||
}
|
||||
|
||||
importScripts("resource://gre/modules/workers/require.js");
|
||||
|
||||
/**
|
||||
* Built-in JavaScript exceptions that may be serialized without
|
||||
* loss of information.
|
||||
*/
|
||||
const EXCEPTION_NAMES = {
|
||||
EvalError: "EvalError",
|
||||
InternalError: "InternalError",
|
||||
RangeError: "RangeError",
|
||||
ReferenceError: "ReferenceError",
|
||||
SyntaxError: "SyntaxError",
|
||||
TypeError: "TypeError",
|
||||
URIError: "URIError",
|
||||
};
|
||||
|
||||
/**
|
||||
* A constructor used to return data to the caller thread while
|
||||
* also executing some specific treatment (e.g. shutting down
|
||||
* the current thread, transmitting data instead of copying it).
|
||||
*
|
||||
* @param {object=} data The data to return to the caller thread.
|
||||
* @param {object=} meta Additional instructions, as an object
|
||||
* that may contain the following fields:
|
||||
* - {bool} shutdown If |true|, shut down the current thread after
|
||||
* having sent the result.
|
||||
* - {Array} transfers An array of objects that should be transferred
|
||||
* instead of being copied.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function Meta(data, meta) {
|
||||
this.data = data;
|
||||
this.meta = meta;
|
||||
};
|
||||
exports.Meta = Meta;
|
||||
|
||||
/**
|
||||
* Base class for a worker.
|
||||
*
|
||||
* Derived classes are expected to provide the following methods:
|
||||
* {
|
||||
* dispatch: function(method, args) {
|
||||
* // Dispatch a call to method `method` with args `args`
|
||||
* },
|
||||
* log: function(...msg) {
|
||||
* // Log (or discard) messages (optional)
|
||||
* },
|
||||
* postMessage: function(message, ...transfers) {
|
||||
* // Post a message to the main thread
|
||||
* },
|
||||
* close: function() {
|
||||
* // Close the worker
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* By default, the AbstractWorker is not connected to a message port,
|
||||
* hence will not receive anything.
|
||||
*
|
||||
* To connect it, use `onmessage`, as follows:
|
||||
* self.addEventListener("message", msg => myWorkerInstance.handleMessage(msg));
|
||||
*/
|
||||
function AbstractWorker(agent) {
|
||||
this._agent = agent;
|
||||
};
|
||||
AbstractWorker.prototype = {
|
||||
// Default logger: discard all messages
|
||||
log: function() {
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a message.
|
||||
*/
|
||||
handleMessage: function(msg) {
|
||||
let data = msg.data;
|
||||
this.log("Received message", data);
|
||||
let id = data.id;
|
||||
|
||||
let start;
|
||||
let options;
|
||||
if (data.args) {
|
||||
options = data.args[data.args.length - 1];
|
||||
}
|
||||
// If |outExecutionDuration| option was supplied, start measuring the
|
||||
// duration of the operation.
|
||||
if (options && typeof options === "object" && "outExecutionDuration" in options) {
|
||||
start = Date.now();
|
||||
}
|
||||
|
||||
let result;
|
||||
let exn;
|
||||
let durationMs;
|
||||
let method = data.fun;
|
||||
try {
|
||||
this.log("Calling method", method);
|
||||
result = this.dispatch(method, data.args);
|
||||
this.log("Method", method, "succeeded");
|
||||
} catch (ex) {
|
||||
exn = ex;
|
||||
this.log("Error while calling agent method", method, exn, exn.moduleStack || exn.stack || "");
|
||||
}
|
||||
|
||||
if (start) {
|
||||
// Record duration
|
||||
durationMs = Date.now() - start;
|
||||
this.log("Method took", durationMs, "ms");
|
||||
}
|
||||
|
||||
// Now, post a reply, possibly as an uncaught error.
|
||||
// We post this message from outside the |try ... catch| block
|
||||
// to avoid capturing errors that take place during |postMessage| and
|
||||
// built-in serialization.
|
||||
if (!exn) {
|
||||
this.log("Sending positive reply", result, "id is", id);
|
||||
if (result instanceof Meta) {
|
||||
if ("transfers" in result.meta) {
|
||||
// Take advantage of zero-copy transfers
|
||||
this.postMessage({ok: result.data, id: id, durationMs: durationMs},
|
||||
result.meta.transfers);
|
||||
} else {
|
||||
this.postMessage({ok: result.data, id:id, durationMs: durationMs});
|
||||
}
|
||||
if (result.meta.shutdown || false) {
|
||||
// Time to close the worker
|
||||
this.close();
|
||||
}
|
||||
} else {
|
||||
this.postMessage({ok: result, id:id, durationMs: durationMs});
|
||||
}
|
||||
} else if (exn.constructor.name in EXCEPTION_NAMES) {
|
||||
// Rather than letting the DOM mechanism [de]serialize built-in
|
||||
// JS errors, which loses lots of information (in particular,
|
||||
// the constructor name, the moduleName and the moduleStack),
|
||||
// we [de]serialize them manually with a little more care.
|
||||
this.log("Sending back exception", exn.constructor.name, "id is", id);
|
||||
let error = {
|
||||
exn: exn.constructor.name,
|
||||
message: exn.message,
|
||||
fileName: exn.moduleName || exn.fileName,
|
||||
lineNumber: exn.lineNumber,
|
||||
stack: exn.moduleStack
|
||||
};
|
||||
this.postMessage({fail: error, id: id, durationMs: durationMs});
|
||||
} else if (exn == StopIteration) {
|
||||
// StopIteration is a well-known singleton, and requires a
|
||||
// slightly different treatment.
|
||||
this.log("Sending back StopIteration, id is", id);
|
||||
let error = {
|
||||
exn: "StopIteration"
|
||||
};
|
||||
this.postMessage({fail: error, id: id, durationMs: durationMs});
|
||||
} else if ("toMsg" in exn) {
|
||||
// Extension mechanism for exception [de]serialization. We
|
||||
// assume that any exception with a method `toMsg()` knows how
|
||||
// to serialize itself. The other side is expected to have
|
||||
// registered a deserializer using the `ExceptionHandlers`
|
||||
// object.
|
||||
this.log("Sending back an error that knows how to serialize itself", exn, "id is", id);
|
||||
let msg = exn.toMsg();
|
||||
this.postMessage({fail: msg, id:id, durationMs: durationMs});
|
||||
} else {
|
||||
// If we encounter an exception for which we have no
|
||||
// serialization mechanism in place, we have no choice but to
|
||||
// let the DOM handle said [de]serialization. We can just
|
||||
// attempt to mitigate the data loss by injecting `moduleName` and
|
||||
// `moduleStack`.
|
||||
this.log("Sending back regular error", exn, exn.moduleStack || exn.stack, "id is", id);
|
||||
|
||||
try {
|
||||
// Attempt to introduce human-readable filename and stack
|
||||
exn.filename = exn.moduleName;
|
||||
exn.stack = exn.moduleStack;
|
||||
} catch (_) {
|
||||
// Nothing we can do
|
||||
}
|
||||
throw exn;
|
||||
}
|
||||
}
|
||||
};
|
||||
exports.AbstractWorker = AbstractWorker;
|
|
@ -0,0 +1,11 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
JS_MODULES_PATH = 'modules/workers'
|
||||
|
||||
EXTRA_JS_MODULES = [
|
||||
'PromiseWorker.js',
|
||||
]
|
Загрузка…
Ссылка в новой задаче