nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
1,158 lines (973 loc) • 32.5 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/child_process.js
import * as __hoisted_internal_child_process_serialization__ from "nstdlib/lib/internal/child_process/serialization";
import {
ErrnoException,
codes as __codes__,
} from "nstdlib/lib/internal/errors";
import {
validateArray,
validateObject,
validateOneOf,
validateString,
} from "nstdlib/lib/internal/validators";
import EventEmitter from "nstdlib/lib/events";
import * as net from "nstdlib/lib/net";
import * as dgram from "nstdlib/lib/dgram";
import { inspect as inspect } from "nstdlib/lib/internal/util/inspect";
import * as assert from "nstdlib/lib/internal/assert";
import { Process } from "nstdlib/stub/binding/process_wrap";
import {
WriteWrap,
kReadBytesOrError,
kArrayBufferOffset,
kLastWriteWasAsync,
streamBaseState,
} from "nstdlib/stub/binding/stream_wrap";
import {
Pipe,
constants as PipeConstants,
} from "nstdlib/stub/binding/pipe_wrap";
import { TCP } from "nstdlib/stub/binding/tcp_wrap";
import { TTY } from "nstdlib/stub/binding/tty_wrap";
import { UDP } from "nstdlib/stub/binding/udp_wrap";
import * as SocketList from "nstdlib/lib/internal/socket_list";
import { symbols as __symbols__ } from "nstdlib/lib/internal/async_hooks";
import { convertToValidSignal, deprecate } from "nstdlib/lib/internal/util";
import { isArrayBufferView } from "nstdlib/lib/internal/util/types";
import * as spawn_sync from "nstdlib/stub/binding/spawn_sync";
import { kStateSymbol } from "nstdlib/lib/internal/dgram";
import * as dc from "nstdlib/lib/diagnostics_channel";
import {
UV_EACCES,
UV_EAGAIN,
UV_EINVAL,
UV_EMFILE,
UV_ENFILE,
UV_ENOENT,
UV_ENOSYS,
UV_ESRCH,
} from "nstdlib/stub/binding/uv";
import * as __hoisted__http_common__ from "nstdlib/lib/_http_common";
const {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_HANDLE_TYPE,
ERR_INVALID_SYNC_FORK_INPUT,
ERR_IPC_CHANNEL_CLOSED,
ERR_IPC_DISCONNECTED,
ERR_IPC_ONE_PIPE,
ERR_IPC_SYNC_FORK,
ERR_MISSING_ARGS,
} = __codes__;
const { owner_symbol } = __symbols__;
const childProcessChannel = dc.channel("child_process");
const { SocketListSend, SocketListReceive } = SocketList;
// Lazy loaded for startup performance and to allow monkey patching of
// internalBinding('http_parser').HTTPParser.
let freeParser;
let HTTPParser;
const MAX_HANDLE_RETRANSMISSIONS = 3;
const kChannelHandle = Symbol("kChannelHandle");
const kIsUsedAsStdio = Symbol("kIsUsedAsStdio");
const kPendingMessages = Symbol("kPendingMessages");
// This object contain function to convert TCP objects to native handle objects
// and back again.
const handleConversion = {
"net.Native": {
simultaneousAccepts: true,
send(message, handle, options) {
return handle;
},
got(message, handle, emit) {
emit(handle);
},
},
"net.Server": {
simultaneousAccepts: true,
send(message, server, options) {
return server._handle;
},
got(message, handle, emit) {
const server = new net.Server();
server.listen(handle, () => {
emit(server);
});
},
},
"net.Socket": {
send(message, socket, options) {
if (!socket._handle) return;
// If the socket was created by net.Server
if (socket.server) {
// The worker should keep track of the socket
message.key = socket.server._connectionKey;
const firstTime = !this[kChannelHandle].sockets.send[message.key];
const socketList = getSocketList("send", this, message.key);
// The server should no longer expose a .connection property
// and when asked to close it should query the socket status from
// the workers
if (firstTime) socket.server._setupWorker(socketList);
// Act like socket is detached
if (!options.keepOpen) socket.server._connections--;
}
const handle = socket._handle;
// Remove handle from socket object, it will be closed when the socket
// will be sent
if (!options.keepOpen) {
handle.onread = nop;
socket._handle = null;
socket.setTimeout(0);
if (freeParser === undefined)
freeParser = __hoisted__http_common__.freeParser;
if (HTTPParser === undefined)
HTTPParser = __hoisted__http_common__.HTTPParser;
// In case of an HTTP connection socket, release the associated
// resources
if (socket.parser && socket.parser instanceof HTTPParser) {
freeParser(socket.parser, null, socket);
if (socket._httpMessage) socket._httpMessage.detachSocket(socket);
}
}
return handle;
},
postSend(message, handle, options, callback, target) {
// Store the handle after successfully sending it, so it can be closed
// when the NODE_HANDLE_ACK is received. If the handle could not be sent,
// just close it.
if (handle && !options.keepOpen) {
if (target) {
// There can only be one _pendingMessage as passing handles are
// processed one at a time: handles are stored in _handleQueue while
// waiting for the NODE_HANDLE_ACK of the current passing handle.
assert(!target._pendingMessage);
target._pendingMessage = {
callback,
message,
handle,
options,
retransmissions: 0,
};
} else {
handle.close();
}
}
},
got(message, handle, emit) {
const socket = new net.Socket({
handle: handle,
readable: true,
writable: true,
});
// If the socket was created by net.Server we will track the socket
if (message.key) {
// Add socket to connections list
const socketList = getSocketList("got", this, message.key);
socketList.add({
socket: socket,
});
}
emit(socket);
},
},
"dgram.Native": {
simultaneousAccepts: false,
send(message, handle, options) {
return handle;
},
got(message, handle, emit) {
emit(handle);
},
},
"dgram.Socket": {
simultaneousAccepts: false,
send(message, socket, options) {
message.dgramType = socket.type;
return socket[kStateSymbol].handle;
},
got(message, handle, emit) {
const socket = new dgram.Socket(message.dgramType);
socket.bind(handle, () => {
emit(socket);
});
},
},
};
function stdioStringToArray(stdio, channel) {
const options = [];
switch (stdio) {
case "ignore":
case "overlapped":
case "pipe":
Array.prototype.push.call(options, stdio, stdio, stdio);
break;
case "inherit":
Array.prototype.push.call(options, 0, 1, 2);
break;
default:
throw new ERR_INVALID_ARG_VALUE("stdio", stdio);
}
if (channel) Array.prototype.push.call(options, channel);
return options;
}
function ChildProcess() {
Function.prototype.call.call(EventEmitter, this);
this._closesNeeded = 1;
this._closesGot = 0;
this.connected = false;
this.signalCode = null;
this.exitCode = null;
this.killed = false;
this.spawnfile = null;
this._handle = new Process();
this._handle[owner_symbol] = this;
this._handle.onexit = (exitCode, signalCode) => {
if (signalCode) {
this.signalCode = signalCode;
} else {
this.exitCode = exitCode;
}
if (this.stdin) {
this.stdin.destroy();
}
this._handle.close();
this._handle = null;
if (exitCode < 0) {
const syscall = this.spawnfile ? "spawn " + this.spawnfile : "spawn";
const err = new ErrnoException(exitCode, syscall);
if (this.spawnfile) err.path = this.spawnfile;
err.spawnargs = Array.prototype.slice.call(this.spawnargs, 1);
this.emit("error", err);
} else {
this.emit("exit", this.exitCode, this.signalCode);
}
// If any of the stdio streams have not been touched,
// then pull all the data through so that it can get the
// eof and emit a 'close' event.
// Do it on nextTick so that the user has one last chance
// to consume the output, if for example they only want to
// start reading the data once the process exits.
process.nextTick(flushStdio, this);
maybeClose(this);
};
if (childProcessChannel.hasSubscribers) {
childProcessChannel.publish({
process: this,
});
}
}
Object.setPrototypeOf(ChildProcess.prototype, EventEmitter.prototype);
Object.setPrototypeOf(ChildProcess, EventEmitter);
function flushStdio(subprocess) {
const stdio = subprocess.stdio;
if (stdio == null) return;
for (let i = 0; i < stdio.length; i++) {
const stream = stdio[i];
// TODO(addaleax): This doesn't necessarily account for all the ways in
// which data can be read from a stream, e.g. being consumed on the
// native layer directly as a StreamBase.
if (!stream || !stream.readable || stream[kIsUsedAsStdio]) {
continue;
}
stream.resume();
}
}
function createSocket(pipe, readable) {
return net.Socket({ handle: pipe, readable });
}
function getHandleWrapType(stream) {
if (stream instanceof Pipe) return "pipe";
if (stream instanceof TTY) return "tty";
if (stream instanceof TCP) return "tcp";
if (stream instanceof UDP) return "udp";
return false;
}
function closePendingHandle(target) {
target._pendingMessage.handle.close();
target._pendingMessage = null;
}
ChildProcess.prototype.spawn = function (options) {
let i = 0;
validateObject(options, "options");
// If no `stdio` option was given - use default
let stdio = options.stdio || "pipe";
stdio = getValidStdio(stdio, false);
const ipc = stdio.ipc;
const ipcFd = stdio.ipcFd;
stdio = options.stdio = stdio.stdio;
validateOneOf(options.serialization, "options.serialization", [
undefined,
"json",
"advanced",
]);
const serialization = options.serialization || "json";
if (ipc !== undefined) {
// Let child process know about opened IPC channel
if (options.envPairs === undefined) options.envPairs = [];
else validateArray(options.envPairs, "options.envPairs");
Array.prototype.push.call(options.envPairs, `NODE_CHANNEL_FD=${ipcFd}`);
Array.prototype.push.call(
options.envPairs,
`NODE_CHANNEL_SERIALIZATION_MODE=${serialization}`,
);
}
validateString(options.file, "options.file");
this.spawnfile = options.file;
if (options.args === undefined) {
this.spawnargs = [];
} else {
validateArray(options.args, "options.args");
this.spawnargs = options.args;
}
const err = this._handle.spawn(options);
// Run-time errors should emit an error, not throw an exception.
if (
err === UV_EACCES ||
err === UV_EAGAIN ||
err === UV_EMFILE ||
err === UV_ENFILE ||
err === UV_ENOENT
) {
process.nextTick(onErrorNT, this, err);
// There is no point in continuing when we've hit EMFILE or ENFILE
// because we won't be able to set up the stdio file descriptors.
if (err === UV_EMFILE || err === UV_ENFILE) return err;
} else if (err) {
// Close all opened fds on error
for (i = 0; i < stdio.length; i++) {
const stream = stdio[i];
if (stream.type === "pipe") {
stream.handle.close();
}
}
this._handle.close();
this._handle = null;
throw new ErrnoException(err, "spawn");
} else {
process.nextTick(onSpawnNT, this);
}
this.pid = this._handle.pid;
for (i = 0; i < stdio.length; i++) {
const stream = stdio[i];
if (stream.type === "ignore") continue;
if (stream.ipc) {
this._closesNeeded++;
continue;
}
// The stream is already cloned and piped, thus stop its readable side,
// otherwise we might attempt to read from the stream when at the same time
// the child process does.
if (stream.type === "wrap") {
stream.handle.reading = false;
stream.handle.readStop();
stream._stdio.pause();
stream._stdio.readableFlowing = false;
stream._stdio._readableState.reading = false;
stream._stdio[kIsUsedAsStdio] = true;
continue;
}
if (stream.handle) {
stream.socket = createSocket(
this.pid !== 0 ? stream.handle : null,
i > 0,
);
if (i > 0 && this.pid !== 0) {
this._closesNeeded++;
stream.socket.on("close", () => {
maybeClose(this);
});
}
}
}
this.stdin =
stdio.length >= 1 && stdio[0].socket !== undefined ? stdio[0].socket : null;
this.stdout =
stdio.length >= 2 && stdio[1].socket !== undefined ? stdio[1].socket : null;
this.stderr =
stdio.length >= 3 && stdio[2].socket !== undefined ? stdio[2].socket : null;
this.stdio = [];
for (i = 0; i < stdio.length; i++)
Array.prototype.push.call(
this.stdio,
stdio[i].socket === undefined ? null : stdio[i].socket,
);
// Add .send() method and start listening for IPC data
if (ipc !== undefined) setupChannel(this, ipc, serialization);
return err;
};
function onErrorNT(self, err) {
self._handle.onexit(err);
}
function onSpawnNT(self) {
self.emit("spawn");
}
ChildProcess.prototype.kill = function (sig) {
const signal =
sig === 0 ? sig : convertToValidSignal(sig === undefined ? "SIGTERM" : sig);
if (this._handle) {
const err = this._handle.kill(signal);
if (err === 0) {
/* Success. */
this.killed = true;
return true;
}
if (err === UV_ESRCH) {
/* Already dead. */
} else if (err === UV_EINVAL || err === UV_ENOSYS) {
/* The underlying platform doesn't support this signal. */
throw new ErrnoException(err, "kill");
} else {
/* Other error, almost certainly EPERM. */
this.emit("error", new ErrnoException(err, "kill"));
}
}
/* Kill didn't succeed. */
return false;
};
ChildProcess.prototype[Symbol.for("nodejs.dispose")] = function () {
if (!this.killed) {
this.kill();
}
};
ChildProcess.prototype.ref = function () {
if (this._handle) this._handle.ref();
};
ChildProcess.prototype.unref = function () {
if (this._handle) this._handle.unref();
};
class Control extends EventEmitter {
#channel = null;
#refs = 0;
#refExplicitlySet = false;
constructor(channel) {
super();
this.#channel = channel;
this[kPendingMessages] = [];
}
// The methods keeping track of the counter are being used to track the
// listener count on the child process object as well as when writes are
// in progress. Once the user has explicitly requested a certain state, these
// methods become no-ops in order to not interfere with the user's intentions.
refCounted() {
if (++this.#refs === 1 && !this.#refExplicitlySet) {
this.#channel.ref();
}
}
unrefCounted() {
if (--this.#refs === 0 && !this.#refExplicitlySet) {
this.#channel.unref();
this.emit("unref");
}
}
ref() {
this.#refExplicitlySet = true;
this.#channel.ref();
}
unref() {
this.#refExplicitlySet = true;
this.#channel.unref();
}
get fd() {
return this.#channel ? this.#channel.fd : undefined;
}
}
const channelDeprecationMsg =
"_channel is deprecated. " + "Use ChildProcess.channel instead.";
let serialization;
function setupChannel(target, channel, serializationMode) {
const control = new Control(channel);
target.channel = control;
target[kChannelHandle] = channel;
Object.defineProperty(target, "_channel", {
__proto__: null,
get: deprecate(
() => {
return target.channel;
},
channelDeprecationMsg,
"DEP0129",
),
set: deprecate(
(val) => {
target.channel = val;
},
channelDeprecationMsg,
"DEP0129",
),
configurable: true,
enumerable: false,
});
target._handleQueue = null;
target._pendingMessage = null;
if (serialization === undefined)
serialization = __hoisted_internal_child_process_serialization__;
const { initMessageChannel, parseChannelMessages, writeChannelMessage } =
serialization[serializationMode];
let pendingHandle = null;
initMessageChannel(channel);
channel.pendingHandle = null;
channel.onread = function (arrayBuffer) {
const recvHandle = channel.pendingHandle;
channel.pendingHandle = null;
if (arrayBuffer) {
const nread = streamBaseState[kReadBytesOrError];
const offset = streamBaseState[kArrayBufferOffset];
const pool = new Uint8Array(arrayBuffer, offset, nread);
if (recvHandle) pendingHandle = recvHandle;
for (const message of parseChannelMessages(channel, pool)) {
// There will be at most one NODE_HANDLE message in every chunk we
// read because SCM_RIGHTS messages don't get coalesced. Make sure
// that we deliver the handle with the right message however.
if (isInternal(message)) {
if (message.cmd === "NODE_HANDLE") {
handleMessage(message, pendingHandle, true);
pendingHandle = null;
} else {
handleMessage(message, undefined, true);
}
} else {
handleMessage(message, undefined, false);
}
}
} else {
this.buffering = false;
target.disconnect();
channel.onread = nop;
channel.close();
target.channel = null;
maybeClose(target);
}
};
// Object where socket lists will live
channel.sockets = { got: {}, send: {} };
// Handlers will go through this
target.on("internalMessage", function (message, handle) {
// Once acknowledged - continue sending handles.
if (
message.cmd === "NODE_HANDLE_ACK" ||
message.cmd === "NODE_HANDLE_NACK"
) {
if (target._pendingMessage) {
if (message.cmd === "NODE_HANDLE_ACK") {
closePendingHandle(target);
} else if (
target._pendingMessage.retransmissions++ ===
MAX_HANDLE_RETRANSMISSIONS
) {
closePendingHandle(target);
process.emitWarning(
"Handle did not reach the receiving process " + "correctly",
"SentHandleNotReceivedWarning",
);
}
}
assert(Array.isArray(target._handleQueue));
const queue = target._handleQueue;
target._handleQueue = null;
if (target._pendingMessage) {
target._send(
target._pendingMessage.message,
target._pendingMessage.handle,
target._pendingMessage.options,
target._pendingMessage.callback,
);
}
for (let i = 0; i < queue.length; i++) {
const args = queue[i];
target._send(args.message, args.handle, args.options, args.callback);
}
// Process a pending disconnect (if any).
if (!target.connected && target.channel && !target._handleQueue)
target._disconnect();
return;
}
if (message.cmd !== "NODE_HANDLE") return;
// It is possible that the handle is not received because of some error on
// ancillary data reception such as MSG_CTRUNC. In this case, report the
// sender about it by sending a NODE_HANDLE_NACK message.
if (!handle) return target._send({ cmd: "NODE_HANDLE_NACK" }, null, true);
// Acknowledge handle receival. Don't emit error events (for example if
// the other side has disconnected) because this call to send() is not
// initiated by the user and it shouldn't be fatal to be unable to ACK
// a message.
target._send({ cmd: "NODE_HANDLE_ACK" }, null, true);
const obj = handleConversion[message.type];
// Update simultaneous accepts on Windows
if (process.platform === "win32") {
handle.setSimultaneousAccepts(false);
}
// Convert handle object
obj.got.call(this, message, handle, (handle) => {
handleMessage(message.msg, handle, isInternal(message.msg));
});
});
target.on("newListener", function () {
process.nextTick(() => {
if (!target.channel || !target.listenerCount("message")) return;
const messages = target.channel[kPendingMessages];
const { length } = messages;
if (!length) return;
for (let i = 0; i < length; i++) {
ReflectApply(target.emit, target, messages[i]);
}
target.channel[kPendingMessages] = [];
});
});
target.send = function (message, handle, options, callback) {
if (typeof handle === "function") {
callback = handle;
handle = undefined;
options = undefined;
} else if (typeof options === "function") {
callback = options;
options = undefined;
} else if (options !== undefined) {
validateObject(options, "options");
}
options = { swallowErrors: false, ...options };
if (this.connected) {
return this._send(message, handle, options, callback);
}
const ex = new ERR_IPC_CHANNEL_CLOSED();
if (typeof callback === "function") {
process.nextTick(callback, ex);
} else {
process.nextTick(() => this.emit("error", ex));
}
return false;
};
target._send = function (message, handle, options, callback) {
assert(this.connected || this.channel);
if (message === undefined) throw new ERR_MISSING_ARGS("message");
// Non-serializable messages should not reach the remote
// end point; as any failure in the stringification there
// will result in error message that is weakly consumable.
// So perform a final check on message prior to sending.
if (
typeof message !== "string" &&
typeof message !== "object" &&
typeof message !== "number" &&
typeof message !== "boolean"
) {
throw new ERR_INVALID_ARG_TYPE(
"message",
["string", "object", "number", "boolean"],
message,
);
}
// Support legacy function signature
if (typeof options === "boolean") {
options = { swallowErrors: options };
}
let obj;
// Package messages with a handle object
if (handle) {
// This message will be handled by an internalMessage event handler
message = {
cmd: "NODE_HANDLE",
type: null,
msg: message,
};
if (handle instanceof net.Socket) {
message.type = "net.Socket";
} else if (handle instanceof net.Server) {
message.type = "net.Server";
} else if (handle instanceof TCP || handle instanceof Pipe) {
message.type = "net.Native";
} else if (handle instanceof dgram.Socket) {
message.type = "dgram.Socket";
} else if (handle instanceof UDP) {
message.type = "dgram.Native";
} else {
throw new ERR_INVALID_HANDLE_TYPE();
}
// Queue-up message and handle if we haven't received ACK yet.
if (this._handleQueue) {
Array.prototype.push.call(this._handleQueue, {
callback: callback,
handle: handle,
options: options,
message: message.msg,
});
return this._handleQueue.length === 1;
}
obj = handleConversion[message.type];
// convert TCP object to native handle object
handle = ReflectApply(handleConversion[message.type].send, target, [
message,
handle,
options,
]);
// If handle was sent twice, or it is impossible to get native handle
// out of it - just send a text without the handle.
if (!handle) message = message.msg;
// Update simultaneous accepts on Windows
if (obj.simultaneousAccepts && process.platform === "win32") {
handle.setSimultaneousAccepts(true);
}
} else if (
this._handleQueue &&
!(
message &&
(message.cmd === "NODE_HANDLE_ACK" ||
message.cmd === "NODE_HANDLE_NACK")
)
) {
// Queue request anyway to avoid out-of-order messages.
Array.prototype.push.call(this._handleQueue, {
callback: callback,
handle: null,
options: options,
message: message,
});
return this._handleQueue.length === 1;
}
const req = new WriteWrap();
const err = writeChannelMessage(channel, req, message, handle);
const wasAsyncWrite = streamBaseState[kLastWriteWasAsync];
if (err === 0) {
if (handle) {
if (!this._handleQueue) this._handleQueue = [];
if (obj && obj.postSend)
obj.postSend(message, handle, options, callback, target);
}
if (wasAsyncWrite) {
req.oncomplete = () => {
control.unrefCounted();
if (typeof callback === "function") callback(null);
};
control.refCounted();
} else if (typeof callback === "function") {
process.nextTick(callback, null);
}
} else {
// Cleanup handle on error
if (obj && obj.postSend) obj.postSend(message, handle, options, callback);
if (!options.swallowErrors) {
const ex = new ErrnoException(err, "write");
if (typeof callback === "function") {
process.nextTick(callback, ex);
} else {
process.nextTick(() => this.emit("error", ex));
}
}
}
/* If the primary is > 2 read() calls behind, please stop sending. */
return channel.writeQueueSize < 65536 * 2;
};
// Connected will be set to false immediately when a disconnect() is
// requested, even though the channel might still be alive internally to
// process queued messages. The three states are distinguished as follows:
// - disconnect() never requested: channel is not null and connected
// is true
// - disconnect() requested, messages in the queue: channel is not null
// and connected is false
// - disconnect() requested, channel actually disconnected: channel is
// null and connected is false
target.connected = true;
target.disconnect = function () {
if (!this.connected) {
this.emit("error", new ERR_IPC_DISCONNECTED());
return;
}
// Do not allow any new messages to be written.
this.connected = false;
// If there are no queued messages, disconnect immediately. Otherwise,
// postpone the disconnect so that it happens internally after the
// queue is flushed.
if (!this._handleQueue) this._disconnect();
};
target._disconnect = function () {
assert(this.channel);
// This marks the fact that the channel is actually disconnected.
this.channel = null;
this[kChannelHandle] = null;
if (this._pendingMessage) closePendingHandle(this);
let fired = false;
function finish() {
if (fired) return;
fired = true;
channel.close();
target.emit("disconnect");
}
// If a message is being read, then wait for it to complete.
if (channel.buffering) {
this.once("message", finish);
this.once("internalMessage", finish);
return;
}
process.nextTick(finish);
};
function emit(event, message, handle) {
if ("internalMessage" === event || target.listenerCount("message")) {
target.emit(event, message, handle);
return;
}
Array.prototype.push.call(target.channel[kPendingMessages], [
event,
message,
handle,
]);
}
function handleMessage(message, handle, internal) {
if (!target.channel) return;
const eventName = internal ? "internalMessage" : "message";
process.nextTick(emit, eventName, message, handle);
}
channel.readStart();
return control;
}
const INTERNAL_PREFIX = "NODE_";
function isInternal(message) {
return (
message !== null &&
typeof message === "object" &&
typeof message.cmd === "string" &&
message.cmd.length > INTERNAL_PREFIX.length &&
String.prototype.slice.call(message.cmd, 0, INTERNAL_PREFIX.length) ===
INTERNAL_PREFIX
);
}
const nop = Function.prototype;
function getValidStdio(stdio, sync) {
let ipc;
let ipcFd;
// Replace shortcut with an array
if (typeof stdio === "string") {
stdio = stdioStringToArray(stdio);
} else if (!Array.isArray(stdio)) {
throw new ERR_INVALID_ARG_VALUE("stdio", stdio);
}
// At least 3 stdio will be created
// Don't concat() a new Array() because it would be sparse, and
// stdio.reduce() would skip the sparse elements of stdio.
// See https://stackoverflow.com/a/5501711/3561
while (stdio.length < 3) Array.prototype.push.call(stdio, undefined);
// Translate stdio into C++-readable form
// (i.e. PipeWraps or fds)
stdio = Array.prototype.reduce.call(
stdio,
(acc, stdio, i) => {
function cleanup() {
for (let i = 0; i < acc.length; i++) {
if (
(acc[i].type === "pipe" || acc[i].type === "ipc") &&
acc[i].handle
)
acc[i].handle.close();
}
}
// Defaults
if (stdio == null) {
stdio = i < 3 ? "pipe" : "ignore";
}
if (stdio === "ignore") {
Array.prototype.push.call(acc, { type: "ignore" });
} else if (
stdio === "pipe" ||
stdio === "overlapped" ||
(typeof stdio === "number" && stdio < 0)
) {
const a = {
type: stdio === "overlapped" ? "overlapped" : "pipe",
readable: i === 0,
writable: i !== 0,
};
if (!sync) a.handle = new Pipe(PipeConstants.SOCKET);
Array.prototype.push.call(acc, a);
} else if (stdio === "ipc") {
if (sync || ipc !== undefined) {
// Cleanup previously created pipes
cleanup();
if (!sync) throw new ERR_IPC_ONE_PIPE();
else throw new ERR_IPC_SYNC_FORK();
}
ipc = new Pipe(PipeConstants.IPC);
ipcFd = i;
Array.prototype.push.call(acc, {
type: "pipe",
handle: ipc,
ipc: true,
});
} else if (stdio === "inherit") {
Array.prototype.push.call(acc, {
type: "inherit",
fd: i,
});
} else if (typeof stdio === "number" || typeof stdio.fd === "number") {
Array.prototype.push.call(acc, {
type: "fd",
fd: typeof stdio === "number" ? stdio : stdio.fd,
});
} else if (
getHandleWrapType(stdio) ||
getHandleWrapType(stdio.handle) ||
getHandleWrapType(stdio._handle)
) {
const handle = getHandleWrapType(stdio)
? stdio
: getHandleWrapType(stdio.handle)
? stdio.handle
: stdio._handle;
Array.prototype.push.call(acc, {
type: "wrap",
wrapType: getHandleWrapType(handle),
handle: handle,
_stdio: stdio,
});
} else if (isArrayBufferView(stdio) || typeof stdio === "string") {
if (!sync) {
cleanup();
throw new ERR_INVALID_SYNC_FORK_INPUT(inspect(stdio));
}
} else {
// Cleanup
cleanup();
throw new ERR_INVALID_ARG_VALUE("stdio", stdio);
}
return acc;
},
[],
);
return { stdio, ipc, ipcFd };
}
function getSocketList(type, worker, key) {
const sockets = worker[kChannelHandle].sockets[type];
let socketList = sockets[key];
if (!socketList) {
const Construct = type === "send" ? SocketListSend : SocketListReceive;
socketList = sockets[key] = new Construct(worker, key);
}
return socketList;
}
function maybeClose(subprocess) {
subprocess._closesGot++;
if (subprocess._closesGot === subprocess._closesNeeded) {
subprocess.emit("close", subprocess.exitCode, subprocess.signalCode);
}
}
function spawnSync(options) {
const result = spawn_sync.spawn(options);
if (result.output && options.encoding && options.encoding !== "buffer") {
for (let i = 0; i < result.output.length; i++) {
if (!result.output[i]) continue;
result.output[i] = result.output[i].toString(options.encoding);
}
}
result.stdout = result.output && result.output[1];
result.stderr = result.output && result.output[2];
if (result.error) {
result.error = new ErrnoException(
result.error,
"spawnSync " + options.file,
);
result.error.path = options.file;
result.error.spawnargs = Array.prototype.slice.call(options.args, 1);
}
return result;
}
export { ChildProcess };
export { kChannelHandle };
export { setupChannel };
export { getValidStdio };
export { stdioStringToArray };
export { spawnSync };