Git Product home page Git Product logo

foreground-child's Introduction

foreground-child

Run a child as if it's the foreground process. Give it stdio. Exit when it exits.

Mostly this module is here to support some use cases around wrapping child processes for test coverage and such. But it's also generally useful any time you want one program to execute another as if it's the "main" process, for example, if a program takes a --cmd argument to execute in some way.

USAGE

import { foregroundChild } from 'foreground-child'
// hybrid module, this also works:
// const { foregroundChild } = require('foreground-child')

// cats out this file
const child = foregroundChild('cat', [__filename])

// At this point, it's best to just do nothing else.
// return or whatever.
// If the child gets a signal, or just exits, then this
// parent process will exit in the same way.

You can provide custom spawn options by passing an object after the program and arguments:

const child = foregroundChild(`cat ${__filename}`, { shell: true })

A callback can optionally be provided, if you want to perform an action before your foreground-child exits:

const child = foregroundChild('cat', [__filename], spawnOptions, () => {
  doSomeActions()
})

The callback can return a Promise in order to perform asynchronous actions. If the callback does not return a promise, then it must complete its actions within a single JavaScript tick.

const child = foregroundChild('cat', [__filename], async () => {
  await doSomeAsyncActions()
})

If the callback throws or rejects, then it will be unhandled, and node will exit in error.

If the callback returns a string value, then that will be used as the signal to exit the parent process. If it returns a number, then that number will be used as the parent exit status code. If it returns boolean false, then the parent process will not be terminated. If it returns undefined, then it will exit with the same signal/code as the child process.

Caveats

The "normal" standard IO file descriptors (0, 1, and 2 for stdin, stdout, and stderr respectively) are shared with the child process. Additionally, if there is an IPC channel set up in the parent, then messages are proxied to the child on file descriptor 3.

In Node, it's possible to also map arbitrary file descriptors into a child process. In these cases, foreground-child will not map the file descriptors into the child. If file descriptors 0, 1, or 2 are used for the IPC channel, then strange behavior may happen (like printing IPC messages to stderr, for example).

Note that a SIGKILL will always kill the parent process, but will not proxy the signal to the child process, because SIGKILL cannot be caught. In order to address this, a special "watchdog" child process is spawned which will send a SIGKILL to the child process if it does not terminate within half a second after the watchdog receives a SIGHUP due to its parent terminating.

On Windows, issuing a process.kill(process.pid, signal) with a fatal termination signal may cause the process to exit with a 1 status code rather than reporting the signal properly. This module tries to do the right thing, but on Windows systems, you may see that incorrect result. There is as far as I'm aware no workaround for this.

util: foreground-child/proxy-signals

If you just want to proxy the signals to a child process that the main process receives, you can use the proxy-signals export from this package.

import { proxySignals } from 'foreground-child/proxy-signals'

const childProcess = spawn('command', ['some', 'args'])
proxySignals(childProcess)

Now, any fatal signal received by the current process will be proxied to the child process.

It doesn't go in the other direction; ie, signals sent to the child process will not affect the parent. For that, listen to the child exit or close events, and handle them appropriately.

util: foreground-child/watchdog

If you are spawning a child process, and want to ensure that it isn't left dangling if the parent process exits, you can use the watchdog utility exported by this module.

import { watchdog } from 'foreground-child/watchdog'

const childProcess = spawn('command', ['some', 'args'])
const watchdogProcess = watchdog(childProcess)

// watchdogProcess is a reference to the process monitoring the
// parent and child. There's usually no reason to do anything
// with it, as it's silent and will terminate
// automatically when it's no longer needed.

foreground-child's People

Contributors

addaleax avatar bcoe avatar coreyfarrell avatar demurgos avatar dependabot[bot] avatar frattaro avatar isaacs avatar rmg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

foreground-child's Issues

v2 throws with invalid arg type

Context

As part of an effort to augment mocha toolchains with standard posix exit codes, we found a key component of our toolchain (nyc) use [email protected] which manifests this issue.

the error

[email protected] can throw an invalid arg type error under certain conditions:

throw new ERR_INVALID_ARG_TYPE(name, 'number', value);
^

TypeError: The "code" argument must be of type number. Received type string ('128SIGABRT')
    at process.set [as exitCode] (node:internal/bootstrap/node:123:9)
    at ChildProcess.<anonymous> (/Users/foo/bar/node_modules/nyc/node_modules/foreground-child/index.js:63:22)
    at ChildProcess.emit (node:events:514:28)
    at maybeClose (node:internal/child_process:1105:16)
    at ChildProcess._handle.onexit (node:internal/child_process:305:5) {
  code: 'ERR_INVALID_ARG_TYPE'
}

root cause

This line assigns a value 128 + signal, resulting in a string eg. "128SIGABRT" instead of a numeric value.

process.exitCode = signal ? 128 + signal : code;

suggested fix

Use os.constants.signals for numerical values of signal strings; eg.

if (typeof signal === 'string') {
    process.exitCode = signal ? 128 + require('os').constants.signals[signal] : code;
} else {
    process.exitCode = signal ? 128 + signal : code;
}

reason to back-patch

Since foreground-child: ^2.0.0 is used by the latest version of nyc it would be helpful to patch v2.0.0 to fix the issue for projects that won't upgrade to [email protected] right away.

steps to reproduce

Given the test file oom.unit.js

describe('OOM error', () => {
  it('should cause an oom error', async () => {
    const x = [];
    while (true) {
      x.push({a: '123'.repeat(1000000) });
    }
  });
});

Running the following command reproduces the error

nyc mocha src/test/server/oom.unit.js

sometimes dead loop

Version

When

  • sometimes, not reproducible

Error

  • 100% CPU, guess somehow dead loop

  • After Ctrl-C

TypeError [ERR_INVALID_ARG_TYPE]: The "code" argument must be of type number. Received type string ('128SIGINT')
    at process.set [as exitCode] (node:internal/bootstrap/node:123:9)
    at ChildProcess.<anonymous> (/home/vscode/.yarn/berry/cache/foreground-child-npm-2.0.0-80c976b61e-8.zip/node_modules/foreground-child/index.js:63:22)
    at ChildProcess.emit (node:events:514:28)
    at maybeClose (node:internal/child_process:1105:16)
    at ChildProcess._handle.onexit (node:internal/child_process:305:5) {
  code: 'ERR_INVALID_ARG_TYPE'
}

Node.js v20.8.0

Related

Maybe caused by below line.

process.exitCode = signal ? 128 + signal : code

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/39359c8466846541b5b3298165ef79c509f0cd09/types/node/child_process.d.ts#L532

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/39359c8466846541b5b3298165ef79c509f0cd09/types/node/v16/process.d.ts#L63-L100

The signal should be null or some const string.

[discussion] Context behind watchdog process

๐Ÿ‘‹ I'm currently working on introducing foreground-child v3 into nyc to fix issues like #57 and istanbuljs/nyc#1535. That PR is here: istanbuljs/nyc#1546

Unfortunately, the watchdog process introduced in v3 is causing some problems. I'm a little fuzzy on the precise details, but nyc does some stuff to make test coverage work even across subprocesses, and the fact that this package now creates an extra process is causing some trouble with the automated tests that assert things about nyc's process tree behavior.

I reviewed the PR that introduced the watchdog process (#52), but I didn't see any discussion of why it was actually introduced or what problem it was trying to solve. Would you be able to provide that context?

Without knowing any of the context: would you be open to a flag to disable the watchdog?

SIGHUP is not supported on Windows

Windows 10
Node.js 18.0.0
foreground-child 2.0.0


When the parent process terminates, it sends a SIGHUP signal to kill the child:

child.kill('SIGHUP')

This is failing on Windows, apparently because SIGHUP is not supported:

     Error: kill ENOSYS
      at ChildProcess.kill (node:internal/child_process:501:13)

The error message is not helpful, but a comment line in the source code of child_process explains it:

https://github.com/nodejs/node/blob/v18.0.0/lib/internal/child_process.js#L500-L501

      /* The underlying platform doesn't support this signal. */
      throw errnoException(err, 'kill');

Maybe send SIGTERM on Windows and SIGHUP on other platforms?

No longer compatible with Node < 14.16 since v3.x, update engines.node

Since the use of the node: import prefix at https://github.com/tapjs/foreground-child/blob/v3.0.0/src/all-signals.ts#L1 this library has become incompatible with Node.js < 16. Found this through using c8 in a failed automated test run at https://github.com/metalsmith/metalsmith/actions/runs/7854693798/job/21435716699.

Error: Cannot find module 'node:constants'

Even though npm view c8@latest engines.node returns >=14.14.0
Please revert to importing from constants OR update engines.node to >=16

[email protected] breaks tests on Windows

I tried upgrading to the newest foreground-child in nyc tonight, but it broke yargs' and node_redis' test-suite on Windows:

screen shot 2016-01-20 at 8 25 08 pm

It seems as though something broke between 1.3.3 and 1.3.4 that does not allow nyc to directly invoke mocha, .e.g.,

nyc mocha

it would be useful to have a beforeExit event, for the parent process.

It would be useful to be able to have an asynchronous beforeExit event that the parent process can hook into.

@Raynos has requested that nyc combine the reporting and instrumenting step, which I agree is a nice simplification, to do this I would need to be able to execute the reporter when foreground-child exits.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.