aboutsummaryrefslogtreecommitdiff
path: root/src/js/node/child_process.js
diff options
context:
space:
mode:
authorGravatar Vlad Sirenko <sirenkovladd@gmail.com> 2023-07-29 02:00:43 +0300
committerGravatar GitHub <noreply@github.com> 2023-07-28 16:00:43 -0700
commit9078b1286d49d69da435256e80ab0b2e21230b18 (patch)
tree9d977611014734d6fd7d70fc2f4f1b3b848d5264 /src/js/node/child_process.js
parent242d8655d854c1b818c38d9b021a31d673638e1e (diff)
downloadbun-9078b1286d49d69da435256e80ab0b2e21230b18.tar.gz
bun-9078b1286d49d69da435256e80ab0b2e21230b18.tar.zst
bun-9078b1286d49d69da435256e80ab0b2e21230b18.zip
add fork to child_process (#3851)
* add fork to child_process * fix export * add test to child_process method `fork` * fmt fork * remove only from test
Diffstat (limited to 'src/js/node/child_process.js')
-rw-r--r--src/js/node/child_process.js116
1 files changed, 108 insertions, 8 deletions
diff --git a/src/js/node/child_process.js b/src/js/node/child_process.js
index c6b10bbec..edcb51cef 100644
--- a/src/js/node/child_process.js
+++ b/src/js/node/child_process.js
@@ -22,6 +22,8 @@ var ArrayPrototypeMap = Array.prototype.map;
var ArrayPrototypeIncludes = Array.prototype.includes;
var ArrayPrototypeSlice = Array.prototype.slice;
var ArrayPrototypeUnshift = Array.prototype.unshift;
+var ArrayPrototypeLastIndexOf = Array.prototype.lastIndexOf;
+var ArrayPrototypeSplice = Array.prototype.splice;
var ArrayIsArray = Array.isArray;
// var ArrayBuffer = ArrayBuffer;
@@ -194,7 +196,7 @@ export function spawn(file, args, options) {
}
function onAbortListener() {
- abortChildProcess(child, killSignal);
+ abortChildProcess(child, killSignal, options.signal.reason);
}
}
return child;
@@ -680,8 +682,97 @@ export function execSync(command, options) {
return ret.stdout;
}
-export function fork() {
- throw new Error("Not implemented");
+function stdioStringToArray(stdio, channel) {
+ const options = [];
+
+ switch (stdio) {
+ case "ignore":
+ case "overlapped":
+ case "pipe":
+ ArrayPrototypePush.call(options, stdio, stdio, stdio);
+ break;
+ case "inherit":
+ ArrayPrototypePush.call(options, 0, 1, 2);
+ break;
+ default:
+ throw new ERR_INVALID_ARG_VALUE("stdio", stdio);
+ }
+
+ if (channel) ArrayPrototypePush.call(options, channel);
+
+ return options;
+}
+
+/**
+ * Spawns a new Node.js process + fork.
+ * @param {string|URL} modulePath
+ * @param {string[]} [args]
+ * @param {{
+ * cwd?: string;
+ * detached?: boolean;
+ * env?: Record<string, string>;
+ * execPath?: string;
+ * execArgv?: string[];
+ * gid?: number;
+ * serialization?: string;
+ * signal?: AbortSignal;
+ * killSignal?: string | number;
+ * silent?: boolean;
+ * stdio?: Array | string;
+ * uid?: number;
+ * windowsVerbatimArguments?: boolean;
+ * timeout?: number;
+ * }} [options]
+ * @returns {ChildProcess}
+ */
+export function fork(modulePath, args = [], options) {
+ modulePath = getValidatedPath(modulePath, "modulePath");
+
+ // Get options and args arguments.
+ let execArgv;
+
+ if (args == null) {
+ args = [];
+ } else if (typeof args === "object" && !ArrayIsArray(args)) {
+ options = args;
+ args = [];
+ } else {
+ validateArray(args, "args");
+ }
+
+ if (options != null) {
+ validateObject(options, "options");
+ }
+ options = { __proto__: null, ...options, shell: false };
+ options.execPath = options.execPath || process.execPath;
+ validateArgumentNullCheck(options.execPath, "options.execPath");
+
+ // Prepare arguments for fork:
+ execArgv = options.execArgv || process.execArgv;
+ validateArgumentsNullCheck(execArgv, "options.execArgv");
+
+ if (execArgv === process.execArgv && process._eval != null) {
+ const index = ArrayPrototypeLastIndexOf.call(execArgv, process._eval);
+ if (index > 0) {
+ // Remove the -e switch to avoid fork bombing ourselves.
+ execArgv = ArrayPrototypeSlice.call(execArgv);
+ ArrayPrototypeSplice.call(execArgv, index - 1, 2);
+ }
+ }
+
+ args = [...execArgv, modulePath, ...args];
+
+ if (typeof options.stdio === "string") {
+ options.stdio = stdioStringToArray(options.stdio, "ipc");
+ } else if (!ArrayIsArray(options.stdio)) {
+ // Use a separate fd=3 for the IPC channel. Inherit stdin, stdout,
+ // and stderr from the parent if silent isn't set.
+ options.stdio = stdioStringToArray(options.silent ? "pipe" : "inherit", "ipc");
+ } else if (!ArrayPrototypeIncludes.call(options.stdio, "ipc")) {
+ throw new ERR_CHILD_PROCESS_IPC_REQUIRED("options.stdio");
+ }
+
+ return spawn(options.execPath, args, options);
}
//------------------------------------------------------------------------------
@@ -909,8 +1000,11 @@ export class ChildProcess extends EventEmitter {
#handleOnExit(exitCode, signalCode, err) {
if (this.#exited) return;
- this.exitCode = this.#handle.exitCode;
- this.signalCode = exitCode > 0 ? signalCode : null;
+ if (signalCode) {
+ this.signalCode = signalCode;
+ } else {
+ this.exitCode = exitCode;
+ }
if (this.#stdin) {
this.#stdin.destroy();
@@ -1286,11 +1380,11 @@ function onSpawnNT(self) {
self.emit("spawn");
}
-function abortChildProcess(child, killSignal) {
+function abortChildProcess(child, killSignal, reason) {
if (!child) return;
try {
if (child.kill(killSignal)) {
- child.emit("error", new AbortError());
+ child.emit("error", new AbortError(undefined, { cause: reason }));
}
} catch (err) {
child.emit("error", err);
@@ -1696,7 +1790,7 @@ function ERR_UNKNOWN_SIGNAL(name) {
}
function ERR_INVALID_ARG_TYPE(name, type, value) {
- const err = new TypeError(`The "${name}" argument must be of type ${type}. Received ${value}`);
+ const err = new TypeError(`The "${name}" argument must be of type ${type}. Received ${value?.toString()}`);
err.code = "ERR_INVALID_ARG_TYPE";
return err;
}
@@ -1709,6 +1803,12 @@ function ERR_INVALID_ARG_VALUE(name, value, reason) {
return new Error(`The value "${value}" is invalid for argument '${name}'. Reason: ${reason}`);
}
+function ERR_CHILD_PROCESS_IPC_REQUIRED(name) {
+ const err = new TypeError(`Forked processes must have an IPC channel, missing value 'ipc' in ${name}`);
+ err.code = "ERR_CHILD_PROCESS_IPC_REQUIRED";
+ return err;
+}
+
class SystemError extends Error {
path;
syscall;