|
1 /** |
|
2 * Thin wrapper around a ChromeWorker that wraps postMessage/onmessage/onerror |
|
3 * as promises. |
|
4 * |
|
5 * Not for public use yet. |
|
6 */ |
|
7 |
|
8 "use strict"; |
|
9 |
|
10 this.EXPORTED_SYMBOLS = ["PromiseWorker"]; |
|
11 |
|
12 // The library of promises. |
|
13 Components.utils.import("resource://gre/modules/Promise.jsm", this); |
|
14 |
|
15 /** |
|
16 * An implementation of queues (FIFO). |
|
17 * |
|
18 * The current implementation uses one array, runs in O(n ^ 2), and is optimized |
|
19 * for the case in which queues are generally short. |
|
20 */ |
|
21 let Queue = function Queue() { |
|
22 this._array = []; |
|
23 }; |
|
24 Queue.prototype = { |
|
25 pop: function pop() { |
|
26 return this._array.shift(); |
|
27 }, |
|
28 push: function push(x) { |
|
29 return this._array.push(x); |
|
30 }, |
|
31 isEmpty: function isEmpty() { |
|
32 return this._array.length == 0; |
|
33 } |
|
34 }; |
|
35 |
|
36 /** |
|
37 * An object responsible for dispatching messages to |
|
38 * a chrome worker and routing the responses. |
|
39 * |
|
40 * @param {string} url The url containing the source code for this worker, |
|
41 * as in constructor ChromeWorker. |
|
42 * @param {Function} log A logging function. |
|
43 * |
|
44 * @constructor |
|
45 */ |
|
46 function PromiseWorker(url, log) { |
|
47 if (typeof url != "string") { |
|
48 throw new TypeError("Expecting a string"); |
|
49 } |
|
50 if (typeof log !== "function") { |
|
51 throw new TypeError("log is expected to be a function"); |
|
52 } |
|
53 this._log = log; |
|
54 this._url = url; |
|
55 |
|
56 /** |
|
57 * The queue of deferred, waiting for the completion of their |
|
58 * respective job by the worker. |
|
59 * |
|
60 * Each item in the list may contain an additional field |closure|, |
|
61 * used to store strong references to value that must not be |
|
62 * garbage-collected before the reply has been received (e.g. |
|
63 * arrays). |
|
64 * |
|
65 * @type {Queue<{deferred:deferred, closure:*=}>} |
|
66 */ |
|
67 this._queue = new Queue(); |
|
68 |
|
69 /** |
|
70 * The number of the current message. |
|
71 * |
|
72 * Used for debugging purposes. |
|
73 */ |
|
74 this._id = 0; |
|
75 |
|
76 /** |
|
77 * The instant at which the worker was launched. |
|
78 */ |
|
79 this.launchTimeStamp = null; |
|
80 |
|
81 /** |
|
82 * Timestamps provided by the worker for statistics purposes. |
|
83 */ |
|
84 this.workerTimeStamps = null; |
|
85 } |
|
86 PromiseWorker.prototype = { |
|
87 /** |
|
88 * Instantiate the worker lazily. |
|
89 */ |
|
90 get _worker() { |
|
91 delete this._worker; |
|
92 let worker = new ChromeWorker(this._url); |
|
93 let self = this; |
|
94 Object.defineProperty(this, "_worker", {value: |
|
95 worker |
|
96 }); |
|
97 |
|
98 // We assume that we call to _worker for the purpose of calling |
|
99 // postMessage(). |
|
100 this.launchTimeStamp = Date.now(); |
|
101 |
|
102 /** |
|
103 * Receive errors that are not instances of OS.File.Error, propagate |
|
104 * them to the listeners. |
|
105 * |
|
106 * The worker knows how to serialize errors that are instances |
|
107 * of |OS.File.Error|. These are treated by |worker.onmessage|. |
|
108 * However, for other errors, we rely on DOM's mechanism for |
|
109 * serializing errors, which transmits these errors through |
|
110 * |worker.onerror|. |
|
111 * |
|
112 * @param {Error} error Some JS error. |
|
113 */ |
|
114 worker.onerror = function onerror(error) { |
|
115 self._log("Received uncaught error from worker", error.message, error.filename, error.lineno); |
|
116 error.preventDefault(); |
|
117 let {deferred} = self._queue.pop(); |
|
118 deferred.reject(error); |
|
119 }; |
|
120 |
|
121 /** |
|
122 * Receive messages from the worker, propagate them to the listeners. |
|
123 * |
|
124 * Messages must have one of the following shapes: |
|
125 * - {ok: some_value} in case of success |
|
126 * - {fail: some_error} in case of error, where |
|
127 * some_error is an instance of |PromiseWorker.WorkerError| |
|
128 * |
|
129 * Messages may also contain a field |id| to help |
|
130 * with debugging. |
|
131 * |
|
132 * Messages may also optionally contain a field |durationMs|, holding |
|
133 * the duration of the function call in milliseconds. |
|
134 * |
|
135 * @param {*} msg The message received from the worker. |
|
136 */ |
|
137 worker.onmessage = function onmessage(msg) { |
|
138 self._log("Received message from worker", msg.data); |
|
139 let handler = self._queue.pop(); |
|
140 let deferred = handler.deferred; |
|
141 let data = msg.data; |
|
142 if (data.id != handler.id) { |
|
143 throw new Error("Internal error: expecting msg " + handler.id + ", " + |
|
144 " got " + data.id + ": " + JSON.stringify(msg.data)); |
|
145 } |
|
146 if ("timeStamps" in data) { |
|
147 self.workerTimeStamps = data.timeStamps; |
|
148 } |
|
149 if ("ok" in data) { |
|
150 // Pass the data to the listeners. |
|
151 deferred.resolve(data); |
|
152 } else if ("StopIteration" in data) { |
|
153 // We have received a StopIteration error |
|
154 deferred.reject(StopIteration); |
|
155 } if ("fail" in data) { |
|
156 // We have received an error that was serialized by the |
|
157 // worker. |
|
158 deferred.reject(new PromiseWorker.WorkerError(data.fail)); |
|
159 } |
|
160 }; |
|
161 return worker; |
|
162 }, |
|
163 |
|
164 /** |
|
165 * Post a message to a worker. |
|
166 * |
|
167 * @param {string} fun The name of the function to call. |
|
168 * @param {Array} array The contents of the message. |
|
169 * @param {*=} closure An object holding references that should not be |
|
170 * garbage-collected before the message treatment is complete. |
|
171 * |
|
172 * @return {promise} |
|
173 */ |
|
174 post: function post(fun, array, closure) { |
|
175 let deferred = Promise.defer(); |
|
176 let id = ++this._id; |
|
177 let message = {fun: fun, args: array, id: id}; |
|
178 this._log("Posting message", message); |
|
179 try { |
|
180 this._worker.postMessage(message); |
|
181 } catch (ex if typeof ex == "number") { |
|
182 this._log("Could not post message", message, "due to xpcom error", ex); |
|
183 // handle raw xpcom errors (see eg bug 961317) |
|
184 return Promise.reject(new Components.Exception("Error in postMessage", ex)); |
|
185 } catch (ex) { |
|
186 this._log("Could not post message", message, "due to error", ex); |
|
187 return Promise.reject(ex); |
|
188 } |
|
189 |
|
190 this._queue.push({deferred:deferred, closure: closure, id: id}); |
|
191 this._log("Message posted"); |
|
192 return deferred.promise; |
|
193 } |
|
194 }; |
|
195 |
|
196 /** |
|
197 * An error that has been serialized by the worker. |
|
198 * |
|
199 * @constructor |
|
200 */ |
|
201 PromiseWorker.WorkerError = function WorkerError(data) { |
|
202 this.data = data; |
|
203 }; |
|
204 |
|
205 this.PromiseWorker = PromiseWorker; |