|
1 if (this.Components) { |
|
2 throw new Error("This worker can only be loaded from a worker thread"); |
|
3 } |
|
4 |
|
5 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
6 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
7 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
8 |
|
9 |
|
10 // Worker thread for osfile asynchronous front-end |
|
11 |
|
12 // Exception names to be posted from the worker thread to main thread |
|
13 const EXCEPTION_NAMES = { |
|
14 EvalError: "EvalError", |
|
15 InternalError: "InternalError", |
|
16 RangeError: "RangeError", |
|
17 ReferenceError: "ReferenceError", |
|
18 SyntaxError: "SyntaxError", |
|
19 TypeError: "TypeError", |
|
20 URIError: "URIError" |
|
21 }; |
|
22 |
|
23 (function(exports) { |
|
24 "use strict"; |
|
25 |
|
26 // Timestamps, for use in Telemetry. |
|
27 // The object is set to |null| once it has been sent |
|
28 // to the main thread. |
|
29 let timeStamps = { |
|
30 entered: Date.now(), |
|
31 loaded: null |
|
32 }; |
|
33 |
|
34 importScripts("resource://gre/modules/osfile.jsm"); |
|
35 |
|
36 let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); |
|
37 let LOG = SharedAll.LOG.bind(SharedAll, "Agent"); |
|
38 |
|
39 // Post a message to the parent, decorate it with statistics if |
|
40 // necessary. Use this instead of self.postMessage. |
|
41 function post(message, ...transfers) { |
|
42 if (timeStamps) { |
|
43 message.timeStamps = timeStamps; |
|
44 timeStamps = null; |
|
45 } |
|
46 self.postMessage(message, ...transfers); |
|
47 } |
|
48 |
|
49 /** |
|
50 * Communications with the controller. |
|
51 * |
|
52 * Accepts messages: |
|
53 * {fun:function_name, args:array_of_arguments_or_null, id:id} |
|
54 * |
|
55 * Sends messages: |
|
56 * {ok: result, id:id} / {fail: serialized_form_of_OS.File.Error, id:id} |
|
57 */ |
|
58 self.onmessage = function onmessage(msg) { |
|
59 let data = msg.data; |
|
60 LOG("Received message", data); |
|
61 let id = data.id; |
|
62 |
|
63 let start; |
|
64 let options; |
|
65 if (data.args) { |
|
66 options = data.args[data.args.length - 1]; |
|
67 } |
|
68 // If |outExecutionDuration| option was supplied, start measuring the |
|
69 // duration of the operation. |
|
70 if (options && typeof options === "object" && "outExecutionDuration" in options) { |
|
71 start = Date.now(); |
|
72 } |
|
73 |
|
74 let result; |
|
75 let exn; |
|
76 let durationMs; |
|
77 try { |
|
78 let method = data.fun; |
|
79 LOG("Calling method", method); |
|
80 result = Agent[method].apply(Agent, data.args); |
|
81 LOG("Method", method, "succeeded"); |
|
82 } catch (ex) { |
|
83 exn = ex; |
|
84 LOG("Error while calling agent method", exn, exn.moduleStack || exn.stack || ""); |
|
85 } |
|
86 |
|
87 if (start) { |
|
88 // Record duration |
|
89 durationMs = Date.now() - start; |
|
90 LOG("Method took", durationMs, "ms"); |
|
91 } |
|
92 |
|
93 // Now, post a reply, possibly as an uncaught error. |
|
94 // We post this message from outside the |try ... catch| block |
|
95 // to avoid capturing errors that take place during |postMessage| and |
|
96 // built-in serialization. |
|
97 if (!exn) { |
|
98 LOG("Sending positive reply", result, "id is", id); |
|
99 if (result instanceof Meta) { |
|
100 if ("transfers" in result.meta) { |
|
101 // Take advantage of zero-copy transfers |
|
102 post({ok: result.data, id: id, durationMs: durationMs}, |
|
103 result.meta.transfers); |
|
104 } else { |
|
105 post({ok: result.data, id:id, durationMs: durationMs}); |
|
106 } |
|
107 if (result.meta.shutdown || false) { |
|
108 // Time to close the worker |
|
109 self.close(); |
|
110 } |
|
111 } else { |
|
112 post({ok: result, id:id, durationMs: durationMs}); |
|
113 } |
|
114 } else if (exn == StopIteration) { |
|
115 // StopIteration cannot be serialized automatically |
|
116 LOG("Sending back StopIteration"); |
|
117 post({StopIteration: true, id: id, durationMs: durationMs}); |
|
118 } else if (exn instanceof exports.OS.File.Error) { |
|
119 LOG("Sending back OS.File error", exn, "id is", id); |
|
120 // Instances of OS.File.Error know how to serialize themselves |
|
121 // (deserialization ensures that we end up with OS-specific |
|
122 // instances of |OS.File.Error|) |
|
123 post({fail: exports.OS.File.Error.toMsg(exn), id:id, durationMs: durationMs}); |
|
124 } else if (exn.constructor.name in EXCEPTION_NAMES) { |
|
125 LOG("Sending back exception", exn.constructor.name); |
|
126 post({fail: {exn: exn.constructor.name, message: exn.message, |
|
127 fileName: exn.moduleName || exn.fileName, lineNumber: exn.lineNumber}, |
|
128 id: id, durationMs: durationMs}); |
|
129 } else { |
|
130 // Other exceptions do not, and should be propagated through DOM's |
|
131 // built-in mechanism for uncaught errors, although this mechanism |
|
132 // may lose interesting information. |
|
133 LOG("Sending back regular error", exn, exn.moduleStack || exn.stack, "id is", id); |
|
134 |
|
135 throw exn; |
|
136 } |
|
137 }; |
|
138 |
|
139 /** |
|
140 * A data structure used to track opened resources |
|
141 */ |
|
142 let ResourceTracker = function ResourceTracker() { |
|
143 // A number used to generate ids |
|
144 this._idgen = 0; |
|
145 // A map from id to resource |
|
146 this._map = new Map(); |
|
147 }; |
|
148 ResourceTracker.prototype = { |
|
149 /** |
|
150 * Get a resource from its unique identifier. |
|
151 */ |
|
152 get: function(id) { |
|
153 let result = this._map.get(id); |
|
154 if (result == null) { |
|
155 return result; |
|
156 } |
|
157 return result.resource; |
|
158 }, |
|
159 /** |
|
160 * Remove a resource from its unique identifier. |
|
161 */ |
|
162 remove: function(id) { |
|
163 if (!this._map.has(id)) { |
|
164 throw new Error("Cannot find resource id " + id); |
|
165 } |
|
166 this._map.delete(id); |
|
167 }, |
|
168 /** |
|
169 * Add a resource, return a new unique identifier |
|
170 * |
|
171 * @param {*} resource A resource. |
|
172 * @param {*=} info Optional information. For debugging purposes. |
|
173 * |
|
174 * @return {*} A unique identifier. For the moment, this is a number, |
|
175 * but this might not remain the case forever. |
|
176 */ |
|
177 add: function(resource, info) { |
|
178 let id = this._idgen++; |
|
179 this._map.set(id, {resource:resource, info:info}); |
|
180 return id; |
|
181 }, |
|
182 /** |
|
183 * Return a list of all open resources i.e. the ones still present in |
|
184 * ResourceTracker's _map. |
|
185 */ |
|
186 listOpenedResources: function listOpenedResources() { |
|
187 return [resource.info.path for ([id, resource] of this._map)]; |
|
188 } |
|
189 }; |
|
190 |
|
191 /** |
|
192 * A map of unique identifiers to opened files. |
|
193 */ |
|
194 let OpenedFiles = new ResourceTracker(); |
|
195 |
|
196 /** |
|
197 * Execute a function in the context of a given file. |
|
198 * |
|
199 * @param {*} id A unique identifier, as used by |OpenFiles|. |
|
200 * @param {Function} f A function to call. |
|
201 * @param {boolean} ignoreAbsent If |true|, the error is ignored. Otherwise, the error causes an exception. |
|
202 * @return The return value of |f()| |
|
203 * |
|
204 * This function attempts to get the file matching |id|. If |
|
205 * the file exists, it executes |f| within the |this| set |
|
206 * to the corresponding file. Otherwise, it throws an error. |
|
207 */ |
|
208 let withFile = function withFile(id, f, ignoreAbsent) { |
|
209 let file = OpenedFiles.get(id); |
|
210 if (file == null) { |
|
211 if (!ignoreAbsent) { |
|
212 throw OS.File.Error.closed("accessing file"); |
|
213 } |
|
214 return undefined; |
|
215 } |
|
216 return f.call(file); |
|
217 }; |
|
218 |
|
219 let OpenedDirectoryIterators = new ResourceTracker(); |
|
220 let withDir = function withDir(fd, f, ignoreAbsent) { |
|
221 let file = OpenedDirectoryIterators.get(fd); |
|
222 if (file == null) { |
|
223 if (!ignoreAbsent) { |
|
224 throw OS.File.Error.closed("accessing directory"); |
|
225 } |
|
226 return undefined; |
|
227 } |
|
228 if (!(file instanceof File.DirectoryIterator)) { |
|
229 throw new Error("file is not a directory iterator " + file.__proto__.toSource()); |
|
230 } |
|
231 return f.call(file); |
|
232 }; |
|
233 |
|
234 let Type = exports.OS.Shared.Type; |
|
235 |
|
236 let File = exports.OS.File; |
|
237 |
|
238 /** |
|
239 * A constructor used to return data to the caller thread while |
|
240 * also executing some specific treatment (e.g. shutting down |
|
241 * the current thread, transmitting data instead of copying it). |
|
242 * |
|
243 * @param {object=} data The data to return to the caller thread. |
|
244 * @param {object=} meta Additional instructions, as an object |
|
245 * that may contain the following fields: |
|
246 * - {bool} shutdown If |true|, shut down the current thread after |
|
247 * having sent the result. |
|
248 * - {Array} transfers An array of objects that should be transferred |
|
249 * instead of being copied. |
|
250 * |
|
251 * @constructor |
|
252 */ |
|
253 let Meta = function Meta(data, meta) { |
|
254 this.data = data; |
|
255 this.meta = meta; |
|
256 }; |
|
257 |
|
258 /** |
|
259 * The agent. |
|
260 * |
|
261 * It is in charge of performing method-specific deserialization |
|
262 * of messages, calling the function/method of OS.File and serializing |
|
263 * back the results. |
|
264 */ |
|
265 let Agent = { |
|
266 // Update worker's OS.Shared.DEBUG flag message from controller. |
|
267 SET_DEBUG: function(aDEBUG) { |
|
268 SharedAll.Config.DEBUG = aDEBUG; |
|
269 }, |
|
270 // Return worker's current OS.Shared.DEBUG value to controller. |
|
271 // Note: This is used for testing purposes. |
|
272 GET_DEBUG: function() { |
|
273 return SharedAll.Config.DEBUG; |
|
274 }, |
|
275 /** |
|
276 * Execute shutdown sequence, returning data on leaked file descriptors. |
|
277 * |
|
278 * @param {bool} If |true|, kill the worker if this would not cause |
|
279 * leaks. |
|
280 */ |
|
281 Meta_shutdown: function(kill) { |
|
282 let result = { |
|
283 openedFiles: OpenedFiles.listOpenedResources(), |
|
284 openedDirectoryIterators: OpenedDirectoryIterators.listOpenedResources(), |
|
285 killed: false // Placeholder |
|
286 }; |
|
287 |
|
288 // Is it safe to kill the worker? |
|
289 let safe = result.openedFiles.length == 0 |
|
290 && result.openedDirectoryIterators.length == 0; |
|
291 result.killed = safe && kill; |
|
292 |
|
293 return new Meta(result, {shutdown: result.killed}); |
|
294 }, |
|
295 // Functions of OS.File |
|
296 stat: function stat(path, options) { |
|
297 return exports.OS.File.Info.toMsg( |
|
298 exports.OS.File.stat(Type.path.fromMsg(path), options)); |
|
299 }, |
|
300 setPermissions: function setPermissions(path, options = {}) { |
|
301 return exports.OS.File.setPermissions(Type.path.fromMsg(path), options); |
|
302 }, |
|
303 setDates: function setDates(path, accessDate, modificationDate) { |
|
304 return exports.OS.File.setDates(Type.path.fromMsg(path), accessDate, |
|
305 modificationDate); |
|
306 }, |
|
307 getCurrentDirectory: function getCurrentDirectory() { |
|
308 return exports.OS.Shared.Type.path.toMsg(File.getCurrentDirectory()); |
|
309 }, |
|
310 setCurrentDirectory: function setCurrentDirectory(path) { |
|
311 File.setCurrentDirectory(exports.OS.Shared.Type.path.fromMsg(path)); |
|
312 }, |
|
313 copy: function copy(sourcePath, destPath, options) { |
|
314 return File.copy(Type.path.fromMsg(sourcePath), |
|
315 Type.path.fromMsg(destPath), options); |
|
316 }, |
|
317 move: function move(sourcePath, destPath, options) { |
|
318 return File.move(Type.path.fromMsg(sourcePath), |
|
319 Type.path.fromMsg(destPath), options); |
|
320 }, |
|
321 getAvailableFreeSpace: function getAvailableFreeSpace(sourcePath) { |
|
322 return Type.uint64_t.toMsg( |
|
323 File.getAvailableFreeSpace(Type.path.fromMsg(sourcePath))); |
|
324 }, |
|
325 makeDir: function makeDir(path, options) { |
|
326 return File.makeDir(Type.path.fromMsg(path), options); |
|
327 }, |
|
328 removeEmptyDir: function removeEmptyDir(path, options) { |
|
329 return File.removeEmptyDir(Type.path.fromMsg(path), options); |
|
330 }, |
|
331 remove: function remove(path) { |
|
332 return File.remove(Type.path.fromMsg(path)); |
|
333 }, |
|
334 open: function open(path, mode, options) { |
|
335 let filePath = Type.path.fromMsg(path); |
|
336 let file = File.open(filePath, mode, options); |
|
337 return OpenedFiles.add(file, { |
|
338 // Adding path information to keep track of opened files |
|
339 // to report leaks when debugging. |
|
340 path: filePath |
|
341 }); |
|
342 }, |
|
343 openUnique: function openUnique(path, options) { |
|
344 let filePath = Type.path.fromMsg(path); |
|
345 let openedFile = OS.Shared.AbstractFile.openUnique(filePath, options); |
|
346 let resourceId = OpenedFiles.add(openedFile.file, { |
|
347 // Adding path information to keep track of opened files |
|
348 // to report leaks when debugging. |
|
349 path: openedFile.path |
|
350 }); |
|
351 |
|
352 return { |
|
353 path: openedFile.path, |
|
354 file: resourceId |
|
355 }; |
|
356 }, |
|
357 read: function read(path, bytes, options) { |
|
358 let data = File.read(Type.path.fromMsg(path), bytes, options); |
|
359 if (typeof data == "string") { |
|
360 return data; |
|
361 } |
|
362 return new Meta({ |
|
363 buffer: data.buffer, |
|
364 byteOffset: data.byteOffset, |
|
365 byteLength: data.byteLength |
|
366 }, { |
|
367 transfers: [data.buffer] |
|
368 }); |
|
369 }, |
|
370 exists: function exists(path) { |
|
371 return File.exists(Type.path.fromMsg(path)); |
|
372 }, |
|
373 writeAtomic: function writeAtomic(path, buffer, options) { |
|
374 if (options.tmpPath) { |
|
375 options.tmpPath = Type.path.fromMsg(options.tmpPath); |
|
376 } |
|
377 return File.writeAtomic(Type.path.fromMsg(path), |
|
378 Type.voidptr_t.fromMsg(buffer), |
|
379 options |
|
380 ); |
|
381 }, |
|
382 removeDir: function(path, options) { |
|
383 return File.removeDir(Type.path.fromMsg(path), options); |
|
384 }, |
|
385 new_DirectoryIterator: function new_DirectoryIterator(path, options) { |
|
386 let directoryPath = Type.path.fromMsg(path); |
|
387 let iterator = new File.DirectoryIterator(directoryPath, options); |
|
388 return OpenedDirectoryIterators.add(iterator, { |
|
389 // Adding path information to keep track of opened directory |
|
390 // iterators to report leaks when debugging. |
|
391 path: directoryPath |
|
392 }); |
|
393 }, |
|
394 // Methods of OS.File |
|
395 File_prototype_close: function close(fd) { |
|
396 return withFile(fd, |
|
397 function do_close() { |
|
398 try { |
|
399 return this.close(); |
|
400 } finally { |
|
401 OpenedFiles.remove(fd); |
|
402 } |
|
403 }); |
|
404 }, |
|
405 File_prototype_stat: function stat(fd) { |
|
406 return withFile(fd, |
|
407 function do_stat() { |
|
408 return exports.OS.File.Info.toMsg(this.stat()); |
|
409 }); |
|
410 }, |
|
411 File_prototype_setPermissions: function setPermissions(fd, options = {}) { |
|
412 return withFile(fd, |
|
413 function do_setPermissions() { |
|
414 return this.setPermissions(options); |
|
415 }); |
|
416 }, |
|
417 File_prototype_setDates: function setDates(fd, accessTime, modificationTime) { |
|
418 return withFile(fd, |
|
419 function do_setDates() { |
|
420 return this.setDates(accessTime, modificationTime); |
|
421 }); |
|
422 }, |
|
423 File_prototype_read: function read(fd, nbytes, options) { |
|
424 return withFile(fd, |
|
425 function do_read() { |
|
426 let data = this.read(nbytes, options); |
|
427 return new Meta({ |
|
428 buffer: data.buffer, |
|
429 byteOffset: data.byteOffset, |
|
430 byteLength: data.byteLength |
|
431 }, { |
|
432 transfers: [data.buffer] |
|
433 }); |
|
434 } |
|
435 ); |
|
436 }, |
|
437 File_prototype_readTo: function readTo(fd, buffer, options) { |
|
438 return withFile(fd, |
|
439 function do_readTo() { |
|
440 return this.readTo(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer), |
|
441 options); |
|
442 }); |
|
443 }, |
|
444 File_prototype_write: function write(fd, buffer, options) { |
|
445 return withFile(fd, |
|
446 function do_write() { |
|
447 return this.write(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer), |
|
448 options); |
|
449 }); |
|
450 }, |
|
451 File_prototype_setPosition: function setPosition(fd, pos, whence) { |
|
452 return withFile(fd, |
|
453 function do_setPosition() { |
|
454 return this.setPosition(pos, whence); |
|
455 }); |
|
456 }, |
|
457 File_prototype_getPosition: function getPosition(fd) { |
|
458 return withFile(fd, |
|
459 function do_getPosition() { |
|
460 return this.getPosition(); |
|
461 }); |
|
462 }, |
|
463 File_prototype_flush: function flush(fd) { |
|
464 return withFile(fd, |
|
465 function do_flush() { |
|
466 return this.flush(); |
|
467 }); |
|
468 }, |
|
469 // Methods of OS.File.DirectoryIterator |
|
470 DirectoryIterator_prototype_next: function next(dir) { |
|
471 return withDir(dir, |
|
472 function do_next() { |
|
473 try { |
|
474 return File.DirectoryIterator.Entry.toMsg(this.next()); |
|
475 } catch (x) { |
|
476 if (x == StopIteration) { |
|
477 OpenedDirectoryIterators.remove(dir); |
|
478 } |
|
479 throw x; |
|
480 } |
|
481 }, false); |
|
482 }, |
|
483 DirectoryIterator_prototype_nextBatch: function nextBatch(dir, size) { |
|
484 return withDir(dir, |
|
485 function do_nextBatch() { |
|
486 let result; |
|
487 try { |
|
488 result = this.nextBatch(size); |
|
489 } catch (x) { |
|
490 OpenedDirectoryIterators.remove(dir); |
|
491 throw x; |
|
492 } |
|
493 return result.map(File.DirectoryIterator.Entry.toMsg); |
|
494 }, false); |
|
495 }, |
|
496 DirectoryIterator_prototype_close: function close(dir) { |
|
497 return withDir(dir, |
|
498 function do_close() { |
|
499 this.close(); |
|
500 OpenedDirectoryIterators.remove(dir); |
|
501 }, true);// ignore error to support double-closing |DirectoryIterator| |
|
502 }, |
|
503 DirectoryIterator_prototype_exists: function exists(dir) { |
|
504 return withDir(dir, |
|
505 function do_exists() { |
|
506 return this.exists(); |
|
507 }); |
|
508 } |
|
509 }; |
|
510 if (!SharedAll.Constants.Win) { |
|
511 Agent.unixSymLink = function unixSymLink(sourcePath, destPath) { |
|
512 return File.unixSymLink(Type.path.fromMsg(sourcePath), |
|
513 Type.path.fromMsg(destPath)); |
|
514 }; |
|
515 } |
|
516 |
|
517 timeStamps.loaded = Date.now(); |
|
518 })(this); |