michael@0: if (this.Components) { michael@0: throw new Error("This worker can only be loaded from a worker thread"); michael@0: } michael@0: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: michael@0: // Worker thread for osfile asynchronous front-end michael@0: michael@0: // Exception names to be posted from the worker thread to main thread michael@0: const EXCEPTION_NAMES = { michael@0: EvalError: "EvalError", michael@0: InternalError: "InternalError", michael@0: RangeError: "RangeError", michael@0: ReferenceError: "ReferenceError", michael@0: SyntaxError: "SyntaxError", michael@0: TypeError: "TypeError", michael@0: URIError: "URIError" michael@0: }; michael@0: michael@0: (function(exports) { michael@0: "use strict"; michael@0: michael@0: // Timestamps, for use in Telemetry. michael@0: // The object is set to |null| once it has been sent michael@0: // to the main thread. michael@0: let timeStamps = { michael@0: entered: Date.now(), michael@0: loaded: null michael@0: }; michael@0: michael@0: importScripts("resource://gre/modules/osfile.jsm"); michael@0: michael@0: let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); michael@0: let LOG = SharedAll.LOG.bind(SharedAll, "Agent"); michael@0: michael@0: // Post a message to the parent, decorate it with statistics if michael@0: // necessary. Use this instead of self.postMessage. michael@0: function post(message, ...transfers) { michael@0: if (timeStamps) { michael@0: message.timeStamps = timeStamps; michael@0: timeStamps = null; michael@0: } michael@0: self.postMessage(message, ...transfers); michael@0: } michael@0: michael@0: /** michael@0: * Communications with the controller. michael@0: * michael@0: * Accepts messages: michael@0: * {fun:function_name, args:array_of_arguments_or_null, id:id} michael@0: * michael@0: * Sends messages: michael@0: * {ok: result, id:id} / {fail: serialized_form_of_OS.File.Error, id:id} michael@0: */ michael@0: self.onmessage = function onmessage(msg) { michael@0: let data = msg.data; michael@0: LOG("Received message", data); michael@0: let id = data.id; michael@0: michael@0: let start; michael@0: let options; michael@0: if (data.args) { michael@0: options = data.args[data.args.length - 1]; michael@0: } michael@0: // If |outExecutionDuration| option was supplied, start measuring the michael@0: // duration of the operation. michael@0: if (options && typeof options === "object" && "outExecutionDuration" in options) { michael@0: start = Date.now(); michael@0: } michael@0: michael@0: let result; michael@0: let exn; michael@0: let durationMs; michael@0: try { michael@0: let method = data.fun; michael@0: LOG("Calling method", method); michael@0: result = Agent[method].apply(Agent, data.args); michael@0: LOG("Method", method, "succeeded"); michael@0: } catch (ex) { michael@0: exn = ex; michael@0: LOG("Error while calling agent method", exn, exn.moduleStack || exn.stack || ""); michael@0: } michael@0: michael@0: if (start) { michael@0: // Record duration michael@0: durationMs = Date.now() - start; michael@0: LOG("Method took", durationMs, "ms"); michael@0: } michael@0: michael@0: // Now, post a reply, possibly as an uncaught error. michael@0: // We post this message from outside the |try ... catch| block michael@0: // to avoid capturing errors that take place during |postMessage| and michael@0: // built-in serialization. michael@0: if (!exn) { michael@0: LOG("Sending positive reply", result, "id is", id); michael@0: if (result instanceof Meta) { michael@0: if ("transfers" in result.meta) { michael@0: // Take advantage of zero-copy transfers michael@0: post({ok: result.data, id: id, durationMs: durationMs}, michael@0: result.meta.transfers); michael@0: } else { michael@0: post({ok: result.data, id:id, durationMs: durationMs}); michael@0: } michael@0: if (result.meta.shutdown || false) { michael@0: // Time to close the worker michael@0: self.close(); michael@0: } michael@0: } else { michael@0: post({ok: result, id:id, durationMs: durationMs}); michael@0: } michael@0: } else if (exn == StopIteration) { michael@0: // StopIteration cannot be serialized automatically michael@0: LOG("Sending back StopIteration"); michael@0: post({StopIteration: true, id: id, durationMs: durationMs}); michael@0: } else if (exn instanceof exports.OS.File.Error) { michael@0: LOG("Sending back OS.File error", exn, "id is", id); michael@0: // Instances of OS.File.Error know how to serialize themselves michael@0: // (deserialization ensures that we end up with OS-specific michael@0: // instances of |OS.File.Error|) michael@0: post({fail: exports.OS.File.Error.toMsg(exn), id:id, durationMs: durationMs}); michael@0: } else if (exn.constructor.name in EXCEPTION_NAMES) { michael@0: LOG("Sending back exception", exn.constructor.name); michael@0: post({fail: {exn: exn.constructor.name, message: exn.message, michael@0: fileName: exn.moduleName || exn.fileName, lineNumber: exn.lineNumber}, michael@0: id: id, durationMs: durationMs}); michael@0: } else { michael@0: // Other exceptions do not, and should be propagated through DOM's michael@0: // built-in mechanism for uncaught errors, although this mechanism michael@0: // may lose interesting information. michael@0: LOG("Sending back regular error", exn, exn.moduleStack || exn.stack, "id is", id); michael@0: michael@0: throw exn; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * A data structure used to track opened resources michael@0: */ michael@0: let ResourceTracker = function ResourceTracker() { michael@0: // A number used to generate ids michael@0: this._idgen = 0; michael@0: // A map from id to resource michael@0: this._map = new Map(); michael@0: }; michael@0: ResourceTracker.prototype = { michael@0: /** michael@0: * Get a resource from its unique identifier. michael@0: */ michael@0: get: function(id) { michael@0: let result = this._map.get(id); michael@0: if (result == null) { michael@0: return result; michael@0: } michael@0: return result.resource; michael@0: }, michael@0: /** michael@0: * Remove a resource from its unique identifier. michael@0: */ michael@0: remove: function(id) { michael@0: if (!this._map.has(id)) { michael@0: throw new Error("Cannot find resource id " + id); michael@0: } michael@0: this._map.delete(id); michael@0: }, michael@0: /** michael@0: * Add a resource, return a new unique identifier michael@0: * michael@0: * @param {*} resource A resource. michael@0: * @param {*=} info Optional information. For debugging purposes. michael@0: * michael@0: * @return {*} A unique identifier. For the moment, this is a number, michael@0: * but this might not remain the case forever. michael@0: */ michael@0: add: function(resource, info) { michael@0: let id = this._idgen++; michael@0: this._map.set(id, {resource:resource, info:info}); michael@0: return id; michael@0: }, michael@0: /** michael@0: * Return a list of all open resources i.e. the ones still present in michael@0: * ResourceTracker's _map. michael@0: */ michael@0: listOpenedResources: function listOpenedResources() { michael@0: return [resource.info.path for ([id, resource] of this._map)]; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * A map of unique identifiers to opened files. michael@0: */ michael@0: let OpenedFiles = new ResourceTracker(); michael@0: michael@0: /** michael@0: * Execute a function in the context of a given file. michael@0: * michael@0: * @param {*} id A unique identifier, as used by |OpenFiles|. michael@0: * @param {Function} f A function to call. michael@0: * @param {boolean} ignoreAbsent If |true|, the error is ignored. Otherwise, the error causes an exception. michael@0: * @return The return value of |f()| michael@0: * michael@0: * This function attempts to get the file matching |id|. If michael@0: * the file exists, it executes |f| within the |this| set michael@0: * to the corresponding file. Otherwise, it throws an error. michael@0: */ michael@0: let withFile = function withFile(id, f, ignoreAbsent) { michael@0: let file = OpenedFiles.get(id); michael@0: if (file == null) { michael@0: if (!ignoreAbsent) { michael@0: throw OS.File.Error.closed("accessing file"); michael@0: } michael@0: return undefined; michael@0: } michael@0: return f.call(file); michael@0: }; michael@0: michael@0: let OpenedDirectoryIterators = new ResourceTracker(); michael@0: let withDir = function withDir(fd, f, ignoreAbsent) { michael@0: let file = OpenedDirectoryIterators.get(fd); michael@0: if (file == null) { michael@0: if (!ignoreAbsent) { michael@0: throw OS.File.Error.closed("accessing directory"); michael@0: } michael@0: return undefined; michael@0: } michael@0: if (!(file instanceof File.DirectoryIterator)) { michael@0: throw new Error("file is not a directory iterator " + file.__proto__.toSource()); michael@0: } michael@0: return f.call(file); michael@0: }; michael@0: michael@0: let Type = exports.OS.Shared.Type; michael@0: michael@0: let File = exports.OS.File; michael@0: michael@0: /** michael@0: * A constructor used to return data to the caller thread while michael@0: * also executing some specific treatment (e.g. shutting down michael@0: * the current thread, transmitting data instead of copying it). michael@0: * michael@0: * @param {object=} data The data to return to the caller thread. michael@0: * @param {object=} meta Additional instructions, as an object michael@0: * that may contain the following fields: michael@0: * - {bool} shutdown If |true|, shut down the current thread after michael@0: * having sent the result. michael@0: * - {Array} transfers An array of objects that should be transferred michael@0: * instead of being copied. michael@0: * michael@0: * @constructor michael@0: */ michael@0: let Meta = function Meta(data, meta) { michael@0: this.data = data; michael@0: this.meta = meta; michael@0: }; michael@0: michael@0: /** michael@0: * The agent. michael@0: * michael@0: * It is in charge of performing method-specific deserialization michael@0: * of messages, calling the function/method of OS.File and serializing michael@0: * back the results. michael@0: */ michael@0: let Agent = { michael@0: // Update worker's OS.Shared.DEBUG flag message from controller. michael@0: SET_DEBUG: function(aDEBUG) { michael@0: SharedAll.Config.DEBUG = aDEBUG; michael@0: }, michael@0: // Return worker's current OS.Shared.DEBUG value to controller. michael@0: // Note: This is used for testing purposes. michael@0: GET_DEBUG: function() { michael@0: return SharedAll.Config.DEBUG; michael@0: }, michael@0: /** michael@0: * Execute shutdown sequence, returning data on leaked file descriptors. michael@0: * michael@0: * @param {bool} If |true|, kill the worker if this would not cause michael@0: * leaks. michael@0: */ michael@0: Meta_shutdown: function(kill) { michael@0: let result = { michael@0: openedFiles: OpenedFiles.listOpenedResources(), michael@0: openedDirectoryIterators: OpenedDirectoryIterators.listOpenedResources(), michael@0: killed: false // Placeholder michael@0: }; michael@0: michael@0: // Is it safe to kill the worker? michael@0: let safe = result.openedFiles.length == 0 michael@0: && result.openedDirectoryIterators.length == 0; michael@0: result.killed = safe && kill; michael@0: michael@0: return new Meta(result, {shutdown: result.killed}); michael@0: }, michael@0: // Functions of OS.File michael@0: stat: function stat(path, options) { michael@0: return exports.OS.File.Info.toMsg( michael@0: exports.OS.File.stat(Type.path.fromMsg(path), options)); michael@0: }, michael@0: setPermissions: function setPermissions(path, options = {}) { michael@0: return exports.OS.File.setPermissions(Type.path.fromMsg(path), options); michael@0: }, michael@0: setDates: function setDates(path, accessDate, modificationDate) { michael@0: return exports.OS.File.setDates(Type.path.fromMsg(path), accessDate, michael@0: modificationDate); michael@0: }, michael@0: getCurrentDirectory: function getCurrentDirectory() { michael@0: return exports.OS.Shared.Type.path.toMsg(File.getCurrentDirectory()); michael@0: }, michael@0: setCurrentDirectory: function setCurrentDirectory(path) { michael@0: File.setCurrentDirectory(exports.OS.Shared.Type.path.fromMsg(path)); michael@0: }, michael@0: copy: function copy(sourcePath, destPath, options) { michael@0: return File.copy(Type.path.fromMsg(sourcePath), michael@0: Type.path.fromMsg(destPath), options); michael@0: }, michael@0: move: function move(sourcePath, destPath, options) { michael@0: return File.move(Type.path.fromMsg(sourcePath), michael@0: Type.path.fromMsg(destPath), options); michael@0: }, michael@0: getAvailableFreeSpace: function getAvailableFreeSpace(sourcePath) { michael@0: return Type.uint64_t.toMsg( michael@0: File.getAvailableFreeSpace(Type.path.fromMsg(sourcePath))); michael@0: }, michael@0: makeDir: function makeDir(path, options) { michael@0: return File.makeDir(Type.path.fromMsg(path), options); michael@0: }, michael@0: removeEmptyDir: function removeEmptyDir(path, options) { michael@0: return File.removeEmptyDir(Type.path.fromMsg(path), options); michael@0: }, michael@0: remove: function remove(path) { michael@0: return File.remove(Type.path.fromMsg(path)); michael@0: }, michael@0: open: function open(path, mode, options) { michael@0: let filePath = Type.path.fromMsg(path); michael@0: let file = File.open(filePath, mode, options); michael@0: return OpenedFiles.add(file, { michael@0: // Adding path information to keep track of opened files michael@0: // to report leaks when debugging. michael@0: path: filePath michael@0: }); michael@0: }, michael@0: openUnique: function openUnique(path, options) { michael@0: let filePath = Type.path.fromMsg(path); michael@0: let openedFile = OS.Shared.AbstractFile.openUnique(filePath, options); michael@0: let resourceId = OpenedFiles.add(openedFile.file, { michael@0: // Adding path information to keep track of opened files michael@0: // to report leaks when debugging. michael@0: path: openedFile.path michael@0: }); michael@0: michael@0: return { michael@0: path: openedFile.path, michael@0: file: resourceId michael@0: }; michael@0: }, michael@0: read: function read(path, bytes, options) { michael@0: let data = File.read(Type.path.fromMsg(path), bytes, options); michael@0: if (typeof data == "string") { michael@0: return data; michael@0: } michael@0: return new Meta({ michael@0: buffer: data.buffer, michael@0: byteOffset: data.byteOffset, michael@0: byteLength: data.byteLength michael@0: }, { michael@0: transfers: [data.buffer] michael@0: }); michael@0: }, michael@0: exists: function exists(path) { michael@0: return File.exists(Type.path.fromMsg(path)); michael@0: }, michael@0: writeAtomic: function writeAtomic(path, buffer, options) { michael@0: if (options.tmpPath) { michael@0: options.tmpPath = Type.path.fromMsg(options.tmpPath); michael@0: } michael@0: return File.writeAtomic(Type.path.fromMsg(path), michael@0: Type.voidptr_t.fromMsg(buffer), michael@0: options michael@0: ); michael@0: }, michael@0: removeDir: function(path, options) { michael@0: return File.removeDir(Type.path.fromMsg(path), options); michael@0: }, michael@0: new_DirectoryIterator: function new_DirectoryIterator(path, options) { michael@0: let directoryPath = Type.path.fromMsg(path); michael@0: let iterator = new File.DirectoryIterator(directoryPath, options); michael@0: return OpenedDirectoryIterators.add(iterator, { michael@0: // Adding path information to keep track of opened directory michael@0: // iterators to report leaks when debugging. michael@0: path: directoryPath michael@0: }); michael@0: }, michael@0: // Methods of OS.File michael@0: File_prototype_close: function close(fd) { michael@0: return withFile(fd, michael@0: function do_close() { michael@0: try { michael@0: return this.close(); michael@0: } finally { michael@0: OpenedFiles.remove(fd); michael@0: } michael@0: }); michael@0: }, michael@0: File_prototype_stat: function stat(fd) { michael@0: return withFile(fd, michael@0: function do_stat() { michael@0: return exports.OS.File.Info.toMsg(this.stat()); michael@0: }); michael@0: }, michael@0: File_prototype_setPermissions: function setPermissions(fd, options = {}) { michael@0: return withFile(fd, michael@0: function do_setPermissions() { michael@0: return this.setPermissions(options); michael@0: }); michael@0: }, michael@0: File_prototype_setDates: function setDates(fd, accessTime, modificationTime) { michael@0: return withFile(fd, michael@0: function do_setDates() { michael@0: return this.setDates(accessTime, modificationTime); michael@0: }); michael@0: }, michael@0: File_prototype_read: function read(fd, nbytes, options) { michael@0: return withFile(fd, michael@0: function do_read() { michael@0: let data = this.read(nbytes, options); michael@0: return new Meta({ michael@0: buffer: data.buffer, michael@0: byteOffset: data.byteOffset, michael@0: byteLength: data.byteLength michael@0: }, { michael@0: transfers: [data.buffer] michael@0: }); michael@0: } michael@0: ); michael@0: }, michael@0: File_prototype_readTo: function readTo(fd, buffer, options) { michael@0: return withFile(fd, michael@0: function do_readTo() { michael@0: return this.readTo(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer), michael@0: options); michael@0: }); michael@0: }, michael@0: File_prototype_write: function write(fd, buffer, options) { michael@0: return withFile(fd, michael@0: function do_write() { michael@0: return this.write(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer), michael@0: options); michael@0: }); michael@0: }, michael@0: File_prototype_setPosition: function setPosition(fd, pos, whence) { michael@0: return withFile(fd, michael@0: function do_setPosition() { michael@0: return this.setPosition(pos, whence); michael@0: }); michael@0: }, michael@0: File_prototype_getPosition: function getPosition(fd) { michael@0: return withFile(fd, michael@0: function do_getPosition() { michael@0: return this.getPosition(); michael@0: }); michael@0: }, michael@0: File_prototype_flush: function flush(fd) { michael@0: return withFile(fd, michael@0: function do_flush() { michael@0: return this.flush(); michael@0: }); michael@0: }, michael@0: // Methods of OS.File.DirectoryIterator michael@0: DirectoryIterator_prototype_next: function next(dir) { michael@0: return withDir(dir, michael@0: function do_next() { michael@0: try { michael@0: return File.DirectoryIterator.Entry.toMsg(this.next()); michael@0: } catch (x) { michael@0: if (x == StopIteration) { michael@0: OpenedDirectoryIterators.remove(dir); michael@0: } michael@0: throw x; michael@0: } michael@0: }, false); michael@0: }, michael@0: DirectoryIterator_prototype_nextBatch: function nextBatch(dir, size) { michael@0: return withDir(dir, michael@0: function do_nextBatch() { michael@0: let result; michael@0: try { michael@0: result = this.nextBatch(size); michael@0: } catch (x) { michael@0: OpenedDirectoryIterators.remove(dir); michael@0: throw x; michael@0: } michael@0: return result.map(File.DirectoryIterator.Entry.toMsg); michael@0: }, false); michael@0: }, michael@0: DirectoryIterator_prototype_close: function close(dir) { michael@0: return withDir(dir, michael@0: function do_close() { michael@0: this.close(); michael@0: OpenedDirectoryIterators.remove(dir); michael@0: }, true);// ignore error to support double-closing |DirectoryIterator| michael@0: }, michael@0: DirectoryIterator_prototype_exists: function exists(dir) { michael@0: return withDir(dir, michael@0: function do_exists() { michael@0: return this.exists(); michael@0: }); michael@0: } michael@0: }; michael@0: if (!SharedAll.Constants.Win) { michael@0: Agent.unixSymLink = function unixSymLink(sourcePath, destPath) { michael@0: return File.unixSymLink(Type.path.fromMsg(sourcePath), michael@0: Type.path.fromMsg(destPath)); michael@0: }; michael@0: } michael@0: michael@0: timeStamps.loaded = Date.now(); michael@0: })(this);