1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/osfile/modules/_PromiseWorker.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,205 @@ 1.4 +/** 1.5 + * Thin wrapper around a ChromeWorker that wraps postMessage/onmessage/onerror 1.6 + * as promises. 1.7 + * 1.8 + * Not for public use yet. 1.9 + */ 1.10 + 1.11 +"use strict"; 1.12 + 1.13 +this.EXPORTED_SYMBOLS = ["PromiseWorker"]; 1.14 + 1.15 +// The library of promises. 1.16 +Components.utils.import("resource://gre/modules/Promise.jsm", this); 1.17 + 1.18 +/** 1.19 + * An implementation of queues (FIFO). 1.20 + * 1.21 + * The current implementation uses one array, runs in O(n ^ 2), and is optimized 1.22 + * for the case in which queues are generally short. 1.23 + */ 1.24 +let Queue = function Queue() { 1.25 + this._array = []; 1.26 +}; 1.27 +Queue.prototype = { 1.28 + pop: function pop() { 1.29 + return this._array.shift(); 1.30 + }, 1.31 + push: function push(x) { 1.32 + return this._array.push(x); 1.33 + }, 1.34 + isEmpty: function isEmpty() { 1.35 + return this._array.length == 0; 1.36 + } 1.37 +}; 1.38 + 1.39 +/** 1.40 + * An object responsible for dispatching messages to 1.41 + * a chrome worker and routing the responses. 1.42 + * 1.43 + * @param {string} url The url containing the source code for this worker, 1.44 + * as in constructor ChromeWorker. 1.45 + * @param {Function} log A logging function. 1.46 + * 1.47 + * @constructor 1.48 + */ 1.49 +function PromiseWorker(url, log) { 1.50 + if (typeof url != "string") { 1.51 + throw new TypeError("Expecting a string"); 1.52 + } 1.53 + if (typeof log !== "function") { 1.54 + throw new TypeError("log is expected to be a function"); 1.55 + } 1.56 + this._log = log; 1.57 + this._url = url; 1.58 + 1.59 + /** 1.60 + * The queue of deferred, waiting for the completion of their 1.61 + * respective job by the worker. 1.62 + * 1.63 + * Each item in the list may contain an additional field |closure|, 1.64 + * used to store strong references to value that must not be 1.65 + * garbage-collected before the reply has been received (e.g. 1.66 + * arrays). 1.67 + * 1.68 + * @type {Queue<{deferred:deferred, closure:*=}>} 1.69 + */ 1.70 + this._queue = new Queue(); 1.71 + 1.72 + /** 1.73 + * The number of the current message. 1.74 + * 1.75 + * Used for debugging purposes. 1.76 + */ 1.77 + this._id = 0; 1.78 + 1.79 + /** 1.80 + * The instant at which the worker was launched. 1.81 + */ 1.82 + this.launchTimeStamp = null; 1.83 + 1.84 + /** 1.85 + * Timestamps provided by the worker for statistics purposes. 1.86 + */ 1.87 + this.workerTimeStamps = null; 1.88 +} 1.89 +PromiseWorker.prototype = { 1.90 + /** 1.91 + * Instantiate the worker lazily. 1.92 + */ 1.93 + get _worker() { 1.94 + delete this._worker; 1.95 + let worker = new ChromeWorker(this._url); 1.96 + let self = this; 1.97 + Object.defineProperty(this, "_worker", {value: 1.98 + worker 1.99 + }); 1.100 + 1.101 + // We assume that we call to _worker for the purpose of calling 1.102 + // postMessage(). 1.103 + this.launchTimeStamp = Date.now(); 1.104 + 1.105 + /** 1.106 + * Receive errors that are not instances of OS.File.Error, propagate 1.107 + * them to the listeners. 1.108 + * 1.109 + * The worker knows how to serialize errors that are instances 1.110 + * of |OS.File.Error|. These are treated by |worker.onmessage|. 1.111 + * However, for other errors, we rely on DOM's mechanism for 1.112 + * serializing errors, which transmits these errors through 1.113 + * |worker.onerror|. 1.114 + * 1.115 + * @param {Error} error Some JS error. 1.116 + */ 1.117 + worker.onerror = function onerror(error) { 1.118 + self._log("Received uncaught error from worker", error.message, error.filename, error.lineno); 1.119 + error.preventDefault(); 1.120 + let {deferred} = self._queue.pop(); 1.121 + deferred.reject(error); 1.122 + }; 1.123 + 1.124 + /** 1.125 + * Receive messages from the worker, propagate them to the listeners. 1.126 + * 1.127 + * Messages must have one of the following shapes: 1.128 + * - {ok: some_value} in case of success 1.129 + * - {fail: some_error} in case of error, where 1.130 + * some_error is an instance of |PromiseWorker.WorkerError| 1.131 + * 1.132 + * Messages may also contain a field |id| to help 1.133 + * with debugging. 1.134 + * 1.135 + * Messages may also optionally contain a field |durationMs|, holding 1.136 + * the duration of the function call in milliseconds. 1.137 + * 1.138 + * @param {*} msg The message received from the worker. 1.139 + */ 1.140 + worker.onmessage = function onmessage(msg) { 1.141 + self._log("Received message from worker", msg.data); 1.142 + let handler = self._queue.pop(); 1.143 + let deferred = handler.deferred; 1.144 + let data = msg.data; 1.145 + if (data.id != handler.id) { 1.146 + throw new Error("Internal error: expecting msg " + handler.id + ", " + 1.147 + " got " + data.id + ": " + JSON.stringify(msg.data)); 1.148 + } 1.149 + if ("timeStamps" in data) { 1.150 + self.workerTimeStamps = data.timeStamps; 1.151 + } 1.152 + if ("ok" in data) { 1.153 + // Pass the data to the listeners. 1.154 + deferred.resolve(data); 1.155 + } else if ("StopIteration" in data) { 1.156 + // We have received a StopIteration error 1.157 + deferred.reject(StopIteration); 1.158 + } if ("fail" in data) { 1.159 + // We have received an error that was serialized by the 1.160 + // worker. 1.161 + deferred.reject(new PromiseWorker.WorkerError(data.fail)); 1.162 + } 1.163 + }; 1.164 + return worker; 1.165 + }, 1.166 + 1.167 + /** 1.168 + * Post a message to a worker. 1.169 + * 1.170 + * @param {string} fun The name of the function to call. 1.171 + * @param {Array} array The contents of the message. 1.172 + * @param {*=} closure An object holding references that should not be 1.173 + * garbage-collected before the message treatment is complete. 1.174 + * 1.175 + * @return {promise} 1.176 + */ 1.177 + post: function post(fun, array, closure) { 1.178 + let deferred = Promise.defer(); 1.179 + let id = ++this._id; 1.180 + let message = {fun: fun, args: array, id: id}; 1.181 + this._log("Posting message", message); 1.182 + try { 1.183 + this._worker.postMessage(message); 1.184 + } catch (ex if typeof ex == "number") { 1.185 + this._log("Could not post message", message, "due to xpcom error", ex); 1.186 + // handle raw xpcom errors (see eg bug 961317) 1.187 + return Promise.reject(new Components.Exception("Error in postMessage", ex)); 1.188 + } catch (ex) { 1.189 + this._log("Could not post message", message, "due to error", ex); 1.190 + return Promise.reject(ex); 1.191 + } 1.192 + 1.193 + this._queue.push({deferred:deferred, closure: closure, id: id}); 1.194 + this._log("Message posted"); 1.195 + return deferred.promise; 1.196 + } 1.197 +}; 1.198 + 1.199 +/** 1.200 + * An error that has been serialized by the worker. 1.201 + * 1.202 + * @constructor 1.203 + */ 1.204 +PromiseWorker.WorkerError = function WorkerError(data) { 1.205 + this.data = data; 1.206 +}; 1.207 + 1.208 +this.PromiseWorker = PromiseWorker;