diff options
Diffstat (limited to 'toolkit/modules/subprocess/subprocess_worker_common.js')
-rw-r--r-- | toolkit/modules/subprocess/subprocess_worker_common.js | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/toolkit/modules/subprocess/subprocess_worker_common.js b/toolkit/modules/subprocess/subprocess_worker_common.js new file mode 100644 index 000000000..2b681e737 --- /dev/null +++ b/toolkit/modules/subprocess/subprocess_worker_common.js @@ -0,0 +1,229 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +/* exported BasePipe, BaseProcess, debug */ +/* globals Process, io */ + +function debug(message) { + self.postMessage({msg: "debug", message}); +} + +class BasePipe { + constructor() { + this.closing = false; + this.closed = false; + + this.closedPromise = new Promise(resolve => { + this.resolveClosed = resolve; + }); + + this.pending = []; + } + + shiftPending() { + let result = this.pending.shift(); + + if (this.closing && this.pending.length == 0) { + this.close(); + } + + return result; + } +} + +let nextProcessId = 0; + +class BaseProcess { + constructor(options) { + this.id = nextProcessId++; + + this.exitCode = null; + + this.exitPromise = new Promise(resolve => { + this.resolveExit = resolve; + }); + this.exitPromise.then(() => { + // The input file descriptors will be closed after poll + // reports that their input buffers are empty. If we close + // them now, we may lose output. + this.pipes[0].close(true); + }); + + this.pid = null; + this.pipes = []; + + this.stringArrays = []; + + this.spawn(options); + } + + /** + * Waits for the process to exit and all of its pending IO operations to + * complete. + * + * @returns {Promise<void>} + */ + awaitFinished() { + return Promise.all([ + this.exitPromise, + ...this.pipes.map(pipe => pipe.closedPromise), + ]); + } + + /** + * Creates a null-terminated array of pointers to null-terminated C-strings, + * and returns it. + * + * @param {string[]} strings + * The strings to convert into a C string array. + * + * @returns {ctypes.char.ptr.array} + */ + stringArray(strings) { + let result = ctypes.char.ptr.array(strings.length + 1)(); + + let cstrings = strings.map(str => ctypes.char.array()(str)); + for (let [i, cstring] of cstrings.entries()) { + result[i] = cstring; + } + + // Char arrays used in char arg and environment vectors must be + // explicitly kept alive in a JS object, or they will be reaped + // by the GC if it runs before our process is started. + this.stringArrays.push(cstrings); + + return result; + } +} + +let requests = { + init(details) { + io.init(details); + + return {data: {}}; + }, + + shutdown() { + io.shutdown(); + + return {data: {}}; + }, + + close(pipeId, force = false) { + let pipe = io.getPipe(pipeId); + + return pipe.close(force).then(() => ({data: {}})); + }, + + spawn(options) { + let process = new Process(options); + let processId = process.id; + + io.addProcess(process); + + let fds = process.pipes.map(pipe => pipe.id); + + return {data: {processId, fds, pid: process.pid}}; + }, + + kill(processId, force = false) { + let process = io.getProcess(processId); + + process.kill(force ? 9 : 15); + + return {data: {}}; + }, + + wait(processId) { + let process = io.getProcess(processId); + + process.wait(); + + process.awaitFinished().then(() => { + io.cleanupProcess(process); + }); + + return process.exitPromise.then(exitCode => { + return {data: {exitCode}}; + }); + }, + + read(pipeId, count) { + let pipe = io.getPipe(pipeId); + + return pipe.read(count).then(buffer => { + return {data: {buffer}}; + }); + }, + + write(pipeId, buffer) { + let pipe = io.getPipe(pipeId); + + return pipe.write(buffer).then(bytesWritten => { + return {data: {bytesWritten}}; + }); + }, + + getOpenFiles() { + return {data: new Set(io.pipes.keys())}; + }, + + getProcesses() { + let data = new Map(Array.from(io.processes.values()) + .filter(proc => proc.exitCode == null) + .map(proc => [proc.id, proc.pid])); + return {data}; + }, + + waitForNoProcesses() { + return Promise.all(Array.from(io.processes.values(), + proc => proc.awaitFinished())); + }, +}; + +onmessage = event => { + io.messageCount--; + + let {msg, msgId, args} = event.data; + + new Promise(resolve => { + resolve(requests[msg](...args)); + }).then(result => { + let response = { + msg: "success", + msgId, + data: result.data, + }; + + self.postMessage(response, result.transfer || []); + }).catch(error => { + if (error instanceof Error) { + error = { + message: error.message, + fileName: error.fileName, + lineNumber: error.lineNumber, + column: error.column, + stack: error.stack, + errorCode: error.errorCode, + }; + } + + self.postMessage({ + msg: "failure", + msgId, + error, + }); + }).catch(error => { + console.error(error); + + self.postMessage({ + msg: "failure", + msgId, + error: {}, + }); + }); +}; |