Bug 1402267 - Allow PromiseWorkers to be 'restarted', which is terminate the current worker and instantiate a new one when needed. r?Yoric draft
authorMike de Boer <mdeboer@mozilla.com>
Tue, 17 Oct 2017 11:59:27 +0200
changeset 681452 0cbf7659af5a3838da4abb6bfbcc58ad36c7e801
parent 681384 ca068118abc506b1a1753b754fa8521fd4f3c57f
child 681453 6f23983c4b3763f756aa3d25ff2d978bb5dad566
push id84839
push usermdeboer@mozilla.com
push dateTue, 17 Oct 2017 10:05:48 +0000
reviewersYoric
bugs1402267
milestone58.0a1
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
toolkit/components/promiseworker/PromiseWorker.jsm
toolkit/components/promiseworker/tests/xpcshell/test_Promise.js
--- 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");
+});