-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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
child_process 'spawn' event is emitted too soon #37782
Comments
Interestingly, if I wrap the subprocess.send() (still inside on('spawn')) in a setTimeout with at least a 29 millisecond delay, it almost always works (~80–90% success rate). The setTimeout trick outside on('spawn') works but needs a much larger number, like 3 seconds. |
This looks like a race condition where children Look at the following change and console logs: parent.mjs: import { fork } from 'child_process';
const subprocess = fork('./tst/child.mjs');
subprocess.on('message', (...args) => { console.log('[PARENT] received', ...args) });
subprocess.on('spawn', () => {
console.log('spawn was called');
console.log({
'parent::subprocess.send': subprocess.send({ hello: 'child' })
});
}); child.mjs: console.log('pre-message-handler');
process.on('message', (...args) => { console.log('[CHILD] received', ...args) });
process.send({ hello: 'parent' }); It prints:
The parent sends the message before the child adds the message handler. |
Yep, that's what I surmised. The docs for
It would appear the doc is either incorrect or (more likely) it's a bug. |
As a workaround, I set up a handshake wherein the child immediately sends a "READY" message to its parent, resolving a promise in the parent: parent.mjs const settleReadyState = {};
const subprocess = fork('./child.mjs');
subprocess.ready = new Promise((resolve, reject) => Object.assign(settleReadyState, { resolve, reject }));
subprocess.once('message', () => {
settleReadyState.resolve();
subprocess.on('message', console.log);
});
subprocess.ready.then(() => subprocess.send({ hello: 'child' });
}); child.mjs process.on('message', console.log);
process.send('READY');
process.send({ hello: 'parent' }); |
What is interesting is that I found out that it only reproduces, if the child script's extension is child.js:
child.Mjs:
What is even weirder is that
Obviously, Otherwise, if |
I wonder if it's because ESM loader is always async? Update: The handshake workaround above seems to be working pretty well so far, and is backwards compatible (since the I'd be happy to add it to the docs. |
Yes and yes I would guess But the 'spawn' event is still pretty necessary (otherwise, without the handshake workaround, we'd have to poll I think |
I disagree, because the |
If the IMHO, the |
As far as I understand documentation, it only shows, whether comm channel was terminated or not. If channel is terminated, it becomes |
I think it's impossible to listen for events retroactively. However, AFAIK |
I made a mistake thinking that the So, I agree that the connected |
@Linkgoron I have been checking the source code in /lib/internal/child_process.js and I also made a mistake, apparently I've also been trying different versions of Node.js, the bugs seem to have been present since major 12. Regarding buffering the messages, I agree with your idea, but in a reverse. The messages are getting sent into nowhere, since IPC appears to be unavailable. However, the messages can be potentially buffered in the parent process ;) But I think it'd be too complicated and not really worth the effort |
I think I've identified the issue. Recompiling node. Let's hope I return with the good news :D |
@schamberg97 An I've added This is the parent file: import { fork } from 'child_process';
console.log('parent pid', process.pid)
const subprocess = fork('./child.mjs');
subprocess.on('spawn', () => {
console.log('spawn was called');
console.log({
'parent::subprocess.send': subprocess.send({ hello: 'child' })
});
}); This is the child: console.log('child pid:', process.pid); this is what was printed:
|
This problem was solved on #41221 |
I seem to experience this issue too - the 'error' event never fires and my child process seems to spawn succesfully (judging by the output on my console) but the 'spawn' event also never fires. Or maybe it fires before the event handler is registered, but either way - the above code does not work for me. I've managed to 'fix' this issue with a bit of a hack - With the absence of the 'error' event getting thrown, I'm running a while-loop until
|
Version:
15.1.0
thru15.11.0
(see screenshot below)Platform:
Darwin CALLMT20389 19.6.0 Darwin Kernel Version 19.6.0: Thu Oct 29 22:56:45 PDT 2020; root:xnu-6153.141.2.2~1/RELEASE_X86_64 x86_64
What steps will reproduce the bug?
Run the following simple repro example:
$> node ./parent.mjs
parent.mjs
child.mjs
How often does it reproduce? Is there a required condition?
100% of the time (dozens of executions)
What is the expected behavior?
child's process.on('message') should be triggered (parent's message should be received and logged to console).
What do you see instead?
Only parent's subprocess.on('message') is triggered:
Additional info
I installed node via nvm. I verified the version of node actually running is truly 15.11.0 by console logging
process.version
in both parent.mjs and child.mjs (both outputv15.11.0
)The text was updated successfully, but these errors were encountered: