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 vote down vote up
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}`));
      }
    }
  });
}