Bug 1402267 - Allow PromiseWorkers to be 'restarted', which is terminate the current worker and instantiate a new one when needed. r?Yoric
MozReview-Commit-ID: KC5xnDwqQK8
--- a/toolkit/components/promiseworker/PromiseWorker.jsm
+++ b/toolkit/components/promiseworker/PromiseWorker.jsm
@@ -162,21 +162,21 @@ this.BasePromiseWorker.prototype = {
log() {
// 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
- });
+ if (this.__worker) {
+ return this.__worker;
+ }
+
+ let worker = this.__worker = new ChromeWorker(this._url);
// 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.
@@ -352,16 +352,45 @@ this.BasePromiseWorker.prototype = {
if (typeof options.outExecutionDuration == "number") {
options.outExecutionDuration += durationMs;
} else {
options.outExecutionDuration = durationMs;
}
return reply.ok;
}.bind(this))();
+ },
+
+ /**
+ * Terminate the worker, if it has been created at all, and set things up to
+ * be instantiated lazily again on the next `post()`.
+ * If there are pending Promises in the queue, we'll reject them and clear it.
+ */
+ terminate() {
+ if (!this.__worker) {
+ return;
+ }
+
+ try {
+ this.__worker.terminate();
+ delete this.__worker;
+ } catch (ex) {
+ // Ignore exceptions, only log them.
+ this.log("Error whilst terminating ChromeWorker: " + ex.message);
+ }
+
+ let error;
+ while (!this._queue.isEmpty()) {
+ if (!error) {
+ // We create this lazily, because error objects are not cheap.
+ error = new Error("Internal error: worker terminated");
+ }
+ let {deferred} = this._queue.pop();
+ deferred.reject(error);
+ }
}
};
/**
* An error that has been serialized by the worker.
*
* @constructor
*/
--- a/toolkit/components/promiseworker/tests/xpcshell/test_Promise.js
+++ b/toolkit/components/promiseworker/tests/xpcshell/test_Promise.js
@@ -111,8 +111,33 @@ add_task(async function test_transfer_wi
add_task(async function test_throw_error() {
try {
await worker.post("throwError", ["error message"]);
Assert.ok(false, "should have thrown");
} catch (ex) {
Assert.equal(ex.message, "Error: error message");
}
});
+
+add_task(async function test_terminate() {
+ let previousWorker = worker._worker;
+
+ // Send two messages that we'll expect to be rejected.
+ let message = ["test_simple_args", Math.random()];
+ let promise1 = worker.post("bounce", message);
+ let promise2 = worker.post("throwError", ["error message"]);
+ // Skip a beat so we can be sure that the two messages are in the queue.
+ await Promise.resolve();
+
+ worker.terminate();
+
+ await Assert.rejects(promise1, /worker terminated/, "Pending promise should be rejected");
+ await Assert.rejects(promise2, /worker terminated/, "Pending promise should be rejected");
+
+ // Unfortunately, there's no real way to check whether a terminate worked from
+ // the JS API. We'll just have to assume it worked.
+
+ // Post and test a simple message to ensure that the worker has been re-instantiated.
+ message = ["test_simple_args", Math.random()];
+ let result = await worker.post("bounce", message);
+ Assert.equal(JSON.stringify(result), JSON.stringify(message));
+ Assert.notEqual(worker._worker, previousWorker, "ChromeWorker instances should differ");
+});