child_process#Serializable TypeScript Examples
The following examples show how to use
child_process#Serializable.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: process.ts From DefinitelyTyped-tools with MIT License | 4 votes |
export function runWithListeningChildProcesses<In extends Serializable>({
inputs,
commandLineArgs,
workerFile,
nProcesses,
cwd,
handleOutput,
crashRecovery,
crashRecoveryMaxOldSpaceSize = DEFAULT_CRASH_RECOVERY_MAX_OLD_SPACE_SIZE,
handleStart,
handleCrash,
softTimeoutMs = Infinity,
}: RunWithListeningChildProcessesOptions<In>): Promise<void> {
return new Promise(async (resolve, reject) => {
let inputIndex = 0;
let processesLeft = nProcesses;
let rejected = false;
const runningChildren = new Set<ChildProcess>();
const maxOldSpaceSize = getMaxOldSpaceSize(process.execArgv) || 0;
const startTime = Date.now();
for (let i = 0; i < nProcesses; i++) {
if (inputIndex === inputs.length) {
processesLeft--;
continue;
}
const processIndex = nProcesses > 1 ? i + 1 : undefined;
let child: ChildProcess;
let crashRecoveryState = CrashRecoveryState.Normal;
let currentInput: In;
const onMessage = (outputMessage: unknown) => {
try {
const oldCrashRecoveryState = crashRecoveryState;
crashRecoveryState = CrashRecoveryState.Normal;
handleOutput(outputMessage as {}, processIndex);
if (inputIndex === inputs.length || Date.now() - startTime > softTimeoutMs) {
stopChild(/*done*/ true);
} else {
if (oldCrashRecoveryState !== CrashRecoveryState.Normal) {
// retry attempt succeeded, restart the child for further tests.
console.log(`${processIndex}> Restarting...`);
restartChild(nextTask, process.execArgv);
} else {
nextTask();
}
}
} catch (e) {
onError(e as Error);
}
};
const onClose = async () => {
if (rejected || !runningChildren.has(child)) {
return;
}
try {
// treat any unhandled closures of the child as a crash
if (crashRecovery) {
switch (crashRecoveryState) {
case CrashRecoveryState.Normal:
crashRecoveryState = CrashRecoveryState.Retry;
break;
case CrashRecoveryState.Retry:
// skip crash recovery if we're already passing a value for --max_old_space_size that
// is >= crashRecoveryMaxOldSpaceSize
crashRecoveryState =
maxOldSpaceSize < crashRecoveryMaxOldSpaceSize
? CrashRecoveryState.RetryWithMoreMemory
: (crashRecoveryState = CrashRecoveryState.Crashed);
break;
default:
crashRecoveryState = CrashRecoveryState.Crashed;
}
} else {
crashRecoveryState = CrashRecoveryState.Crashed;
}
if (handleCrash) {
handleCrash(currentInput, crashRecoveryState, processIndex);
}
switch (crashRecoveryState) {
case CrashRecoveryState.Retry:
await restartChild(resumeTask, process.execArgv);
break;
case CrashRecoveryState.RetryWithMoreMemory:
await restartChild(resumeTask, [
...getExecArgvWithoutMaxOldSpaceSize(),
`--max_old_space_size=${crashRecoveryMaxOldSpaceSize}`,
]);
break;
case CrashRecoveryState.Crashed:
crashRecoveryState = CrashRecoveryState.Normal;
if (inputIndex === inputs.length || Date.now() - startTime > softTimeoutMs) {
stopChild(/*done*/ true);
} else {
await restartChild(nextTask, process.execArgv);
}
break;
default:
assert.fail(`${processIndex}> Unexpected crashRecoveryState: ${crashRecoveryState}`);
}
} catch (e) {
onError(e as Error);
}
};
const onError = (err?: Error) => {
child.removeAllListeners();
runningChildren.delete(child);
fail(err);
};
const startChild = async (taskAction: () => void, execArgv: string[]) => {
try {
child = fork(workerFile, commandLineArgs, { cwd, execArgv: await getChildProcessExecArgv(i, execArgv) });
runningChildren.add(child);
} catch (e) {
fail(e as Error);
return;
}
try {
let closed = false;
const thisChild = child;
const onChildClosed = () => {
// Don't invoke `onClose` more than once for a single child.
if (!closed && child === thisChild) {
closed = true;
onClose();
}
};
const onChildDisconnectedOrExited = () => {
if (!closed && thisChild === child) {
// Invoke `onClose` after enough time has elapsed to allow `close` to be triggered.
// This is to ensure our `onClose` logic gets called in some conditions
const timeout = 1000;
setTimeout(onChildClosed, timeout);
}
};
child.on("message", onMessage);
child.on("close", onChildClosed);
child.on("disconnect", onChildDisconnectedOrExited);
child.on("exit", onChildDisconnectedOrExited);
child.on("error", onError);
taskAction();
} catch (e) {
onError(e as Error);
}
};
const stopChild = (done: boolean) => {
try {
assert(runningChildren.has(child), `${processIndex}> Child not running`);
if (done) {
processesLeft--;
if (processesLeft === 0) {
resolve();
}
}
runningChildren.delete(child);
child.removeAllListeners();
child.kill();
} catch (e) {
onError(e as Error);
}
};
const restartChild = async (taskAction: () => void, execArgv: string[]) => {
try {
assert(runningChildren.has(child), `${processIndex}> Child not running`);
console.log(`${processIndex}> Restarting...`);
stopChild(/*done*/ false);
await startChild(taskAction, execArgv);
} catch (e) {
onError(e as Error);
}
};
const resumeTask = () => {
try {
assert(runningChildren.has(child), `${processIndex}> Child not running`);
child.send(currentInput);
} catch (e) {
onError(e as Error);
}
};
const nextTask = () => {
try {
assert(runningChildren.has(child), `${processIndex}> Child not running`);
currentInput = inputs[inputIndex];
inputIndex++;
if (handleStart) {
handleStart(currentInput, processIndex);
}
child.send(currentInput);
} catch (e) {
onError(e as Error);
}
};
await startChild(nextTask, process.execArgv);
}
function fail(err?: Error): void {
if (!rejected) {
rejected = true;
for (const child of runningChildren) {
try {
child.removeAllListeners();
child.kill();
} catch {
// do nothing
}
}
const message = err ? `: ${err.message}` : "";
reject(new Error(`Something went wrong in ${runWithListeningChildProcesses.name}${message}`));
}
}
});
}