Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically create child_process args. #47

Open
jaridmargolin opened this issue Feb 26, 2017 · 5 comments
Open

Dynamically create child_process args. #47

jaridmargolin opened this issue Feb 26, 2017 · 5 comments

Comments

@jaridmargolin
Copy link

jaridmargolin commented Feb 26, 2017

I would like one of my argument values to be dynamically created right before the child_process is spawned. Specifically my use case is to pass a a unique port number to each child_process --inspect=AVAILABLE_PORT.

I have been reviewing the src, and it looks as though this isn't possible. It looks like the args are hard coded in a cached settings.json file... Is this correct? Do you see any possible path forward to support the requested functionality?

@jaridmargolin
Copy link
Author

jaridmargolin commented Feb 27, 2017

Continuing to review the src / my requirements, and I believe I could accomplish what I need by forking spawn-wrap and making modifications directly in the shim. I really hate to take that path. Wondering if instead some sort of hooks interface could be implemented?

I would like to be able to execute code before the child process is spawned, and after the process has exited

I'd take a stab at PR if you are open to the idea of hooks? I think the hooks could be written to the settings.json file as a stringified function, or path to a file. Maybe this is all to hacky?

@isaacs
Copy link
Collaborator

isaacs commented Jun 3, 2017

Can you sketch out an example program that would use your proposed API surface, and explain what's going on? It's a bit challenging to follow the description, and I'd want to make sure we get it right if we're going to muck about in there.

It sounds like you want to provide a little node program/module that defines the added args in the shim, rather than putting them in a json config file. Why not just write a little node program that does this, and then calls runMain()? Then the config is static, but points at a program that does whatever.

// set-port.js
process.env.PORT = getAvailablePort()
// could just as easily do process.argv.push('--inspect=PORT')
require('spawn-wrap').runMain()
// wrapper.js
const sw = require('spawn-wrap')
const setPortModule = require.resolve('./set-port.js')
sw([setPortModule])

// now spawnning 'node foo.js --bar' will instead do `node set-port.js foo.js --bar`,
// and the runMain in set-port.js will re-load the node environment with the changed
// PORT environ as if `PORT=12345 node foo.js --bar` was run.

doStuffThatSpawnsChildProcs()

@isaacs
Copy link
Collaborator

isaacs commented Jun 3, 2017

Oh, is the issue that you need to pass the arg as a node execArgs, before the main file? Hm. That's trickier.

It wouldn't be too hard to specify a module that exports an array for the args to use, or something. It'd have to point at a file, not a stringified function, because otherwise any context would be lost.

@demurgos
Copy link
Contributor

demurgos commented Aug 9, 2018

We are encountering the same issue (for the same use case) in c8.

Thinking about it, I think that the most reliable solution would be to not use wrapper modules but handle the creation of Node subprocesses with an event emitter in the top process.

const ee = spawnWrap.spawn(file, args, opt);
ee.on("spawn", onSpawn);

// Triggered when a Node subprocess is about to be spawned, anywhere in the process subtree.
function onSpawn(spawnEvent) {
  // You can read the arguments and env of the subprocess
  console.log(spawnEvent.args);
  console.log(spawnEvent.env);
  // These fields are mutable:
  spawnEvent.args.unshift("--inspect=0");
  spawnEvent.env.DEBUG = "1";
  // When you are ready, you can start the real process (equivalent to `runMain`):
  const childProc = spawnEvent.spawn(); // or .spawnSync
  // It uses the parameters currently defined in `args` and `env`
}

Ultimately, the nyc or c8 wrappers using spawn-wrap currently do not need to be executed inside the spawned process: they can migrate to an event handler at the top process. This would naturally enable dynamic args/env configuration, and alleviate my concerns about patching the global module and messing with concurrent tasks (#73).
This kind of architecture would also solve bcoe/c8#20: when executing the wrapper and original main in the same process, the original main can prevent the completion of the wrapper. Having the handler in the top process would solve this thanks to process isolation. (the original main would have to detach itself and nuke its ancestors chain...)

@demurgos
Copy link
Contributor

I implemented a way to intercept spawn calls and dynamically replace the arguments.

Still WIP, but you can check this commit. Here is a usage example:

spawnWrap
  .observeSpawn(process.execPath, [NESTED_SYNC])
  .subscribe((ev) => {
    console.log('Intercepted a Node process spawn!')
    console.log(ev.args)
    ev.voidSpawn([...ev.args, 'extra'])
  })

This is similar to childProcess.spawn(process.execPath, [NESTED_SYNC]) but instead of returning a ChildProcess, it returns an observable for spawn events. The event contains the original arguments in ev.args. You can then read them and build a new array of arguments and pass it to ev.voidSpawn.
voidSpawn is named this way because it spawns the process with the new args, but does not return anything. This process may be deep in the tree so you don't have direct access to it and cannot read its stdout/stderr from the root process. I intend to add a proxySpawn method to proxy the stdout/stderr.

This should allow to inject --inspect=0 args and then read the stderr.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants