/* -*- 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: {}, }); }); };