Bug 1077354 - Extending PromiseWorker to simplify transfering values r=froydnj
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Thu, 20 Nov 2014 12:40:08 +0100
changeset 248570 e6ac6c211626ef78876625431e214df72f1f22ca
parent 248569 b45b110029225e98625bd7435d42c18bbb1e2f26
child 248571 b777166f71b006e0de2b016bc0d06e8efd1d103b
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1077354
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1077354 - Extending PromiseWorker to simplify transfering values r=froydnj
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
@@ -239,17 +239,20 @@ this.BasePromiseWorker.prototype = {
   },
 
   /**
    * 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`. If any
    * of the arguments is a Promise, it is resolved before posting the
-   * message. By convention, the last argument may be an object `options`
+   * message. If any of the arguments needs to be transfered instead
+   * of copied, this may be specified by making the argument an instance
+   * of `BasePromiseWorker.Meta` or by using the `transfers` argument.
+   * 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.
    * @param {Array=} transfers An array of objects that should be transfered
    * to the worker instead of being copied. If any of the objects is a Promise,
    * it is resolved before posting the message.
@@ -259,16 +262,32 @@ this.BasePromiseWorker.prototype = {
   post: function(fun, args, closure, transfers) {
     return Task.spawn(function* postMessage() {
       // Normalize in case any of the arguments is a promise
       if (args) {
         args = yield Promise.resolve(Promise.all(args));
       }
       if (transfers) {
         transfers = yield Promise.resolve(Promise.all(transfers));
+      } else {
+        transfers = [];
+      }
+
+      if (args) {
+        // Extract `Meta` data
+        args = args.map(arg => {
+          if (arg instanceof BasePromiseWorker.Meta) {
+            if (arg.meta && "transfers" in arg.meta) {
+              transfers.push(...arg.meta.transfers);
+            }
+            return arg.data;
+          } else {
+            return arg;
+          }
+        });
       }
 
       let id = ++this._id;
       let message = {fun: fun, args: args, id: id};
       this.log("Posting message", message);
       try {
         this._worker.postMessage(message, ...[transfers]);
       } catch (ex if typeof ex == "number") {
@@ -347,8 +366,26 @@ this.BasePromiseWorker.prototype = {
 /**
  * An error that has been serialized by the worker.
  *
  * @constructor
  */
 function WorkerError(data) {
   this.data = data;
 };
+
+/**
+ * A constructor used to send data to the worker thread while
+ * with special treatment (e.g. transmitting data instead of
+ * copying it).
+ *
+ * @param {object=} data The data to send to the caller thread.
+ * @param {object=} meta Additional instructions, as an object
+ * that may contain the following fields:
+ * - {Array} transfers An array of objects that should be transferred
+ *   instead of being copied.
+ *
+ * @constructor
+ */
+this.BasePromiseWorker.Meta = function(data, meta) {
+  this.data = data;
+  this.meta = meta;
+};
--- a/toolkit/components/promiseworker/tests/xpcshell/test_Promise.js
+++ b/toolkit/components/promiseworker/tests/xpcshell/test_Promise.js
@@ -55,16 +55,17 @@ add_task(function* test_rejected_promise
   try {
     yield worker.post("bounce", message);
     do_throw("I shound have thrown an error by now");
   } catch (ex if ex == error) {
     do_print("I threw the right error");
   }
 });
 
+// Test that we can transfer to the worker using argument `transfer`
 add_task(function* test_transfer_args() {
   let array = new Uint8Array(4);
   for (let i = 0; i < 4; ++i) {
     array[i] = i;
   }
   Assert.equal(array.buffer.byteLength, 4, "The buffer is not neutered yet");
 
   let result = (yield worker.post("bounce", [array.buffer], [], [array.buffer]))[0];
@@ -75,11 +76,34 @@ add_task(function* test_transfer_args() 
   // Check that the result is correct
   Assert.equal(result.byteLength, 4, "The result has the right size");
   let array2 = new Uint8Array(result);
   for (let i = 0; i < 4; ++i) {
     Assert.equal(array2[i], i);
   }
 });
 
+// Test that we can transfer to the worker using an instance of `Meta`
+add_task(function* test_transfer_with_meta() {
+  let array = new Uint8Array(4);
+  for (let i = 0; i < 4; ++i) {
+    array[i] = i;
+  }
+  Assert.equal(array.buffer.byteLength, 4, "The buffer is not neutered yet");
+
+  let message = new BasePromiseWorker.Meta(array, {transfers: [array.buffer]});
+  let result = (yield worker.post("bounce", [message]))[0];
+
+  // Check that the buffer has been sent
+  Assert.equal(array.buffer.byteLength, 0, "The buffer has been neutered");
+
+  // Check that the result is correct
+  Assert.equal(result.toString(), "[object Uint8Array]", "The result appears to be a Typed Array");
+  Assert.equal(result.byteLength, 4, "The result has the right size");
+
+  for (let i = 0; i < 4; ++i) {
+    Assert.equal(result[i], i);
+  }
+});
+
 function run_test() {
   run_next_test();
 }