/* -*- 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"; // This file is loaded into the same context as subprocess_worker_unix.js // and subprocess_worker_win.js /* import-globals-from subprocess_worker_unix.js */ /* exported BasePipe, BaseProcess, debug */ 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) { 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.spawn(options); } /** * Waits for the process to exit and all of its pending IO operations to * complete. * * @returns {Promise} */ awaitFinished() { return Promise.all([ this.exitPromise, ...this.pipes.map(pipe => pipe.closedPromise), ]); } } 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: {}, }); }); };