-
-
Notifications
You must be signed in to change notification settings - Fork 534
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
forward signals correctly (was: signals: ignore them) #419
Conversation
Can you share the situation that this solves? I've never come across this being an issue myself, nor have I seen it handled in other libraries that spawn child processes (https://github.com/babel/babel/blob/777a9ae6e42db1c9d57bbbdca8b233c2dc9b89ac/packages/babel-node/src/babel-node.js). |
Given the following code: process.on('SIGINT', () => {
console.log('received sigint')
setTimeout(() => {
console.log('exit')
process.exit(0)
}, 2000)
})
console.log('running')
require('http').createServer(() => true).listen(8080) Running directly in $ node index.ts
running
^Creceived sigint
exit
$ While in ts-node index.ts
running
^Creceived sigint
$ exit
As you can see, with
|
Awesome, thanks. Do you think we get add a test case so this doesn't occur in the future? |
79142b4
to
d3e5eb2
Compare
I wrote a quick sample test, but for some reason, the child process does not get killed at all. Will look into it a bit further. |
d3e5eb2
to
98ae8b8
Compare
98ae8b8
to
b91ccb8
Compare
src/index.spec.ts
Outdated
return done() | ||
}) | ||
|
||
setTimeout(() => proc.kill('SIGTERM'), 100) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some reasons, the kill signal is not properly sent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it work if you forwarded the signals to the child? It's possible that the current code change is just ignoring the signal.
The point of this test is to ensure that the signal is properly caught by the binary, not by the child. I tried to use 'SIGKILL', and that didn't work either, so maybe Mocha is getting in the way... |
b91ccb8
to
7a6b94a
Compare
Found the issue. In a nutshell, signals triggered by keyboard interrupts are sent to the process and all subprocesses at once, except if the suprocess was spawned in The solution is to detach the subprocess and manually forward relevant signals. I have found three so far:
Will update the PR shortly, but have already updated its title. I am trying to add a test to verify that keyboard interrupts have the same behavior as sending a signal to the top process first to make sure behavior always remain consistent. |
7a6b94a
to
d84900b
Compare
I gave up on testing specifically for keyboard interrupts. If you know of a way to test for that and would like me to do so, please let me know how I could set up the test. |
21bb4bc
to
77b44d8
Compare
This passes just fine on my desktop (Linux), so I am not sure as to why it would fail on CI. Apologies for the spam. |
77b44d8
to
88bac37
Compare
88bac37
to
be4e7e5
Compare
All tests are now passing. Please let me know if further changes are warranted before this can be merged. |
src/bin.ts
Outdated
proc.on('close', (code: number, signal: string) => { | ||
if (signal) { | ||
process.kill(process.pid, signal) | ||
} else if (code) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to check the else if
here? If it closes, shouldn't it have one or the other? In a hypothetical case it has neither, wouldn't we still want to exit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want the process to exit naturally (no process.exit) upon a signal, but to exit with a code when there is one.
src/bin.ts
Outdated
}) | ||
// Ignore signals, and instead forward them to the | ||
// child process | ||
const forward = (signal: string) => process.on(signal, () => proc.kill(signal)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably easier just to make this an array loop. The comments are super verbose and mostly unnecessary too. E.g.
['SIGINT', 'SIGTERM', 'SIGWINCH'].forEach(signal => process.on(signal, () => proc.kill(signal))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH I never remember what SIGWINCH
does, hence why I wrote it in a slightly more verbose way. If you consider this a blocker, I will change it, of course.
Are you absolutely sure we need to be running in detached mode? Even NPM doesn't run in detached mode and based on https://github.com/npm/lifecycle/blob/latest/index.js they just forward the two signals specifically. Edit: This was based on a reference to NPM I can no longer see in the issue, sorry if I'm mixing the issues up 😄 |
#419 (comment) Gives the explanation. We forward terminal size change as well since programs may be long-running and meant to be executed within a terminal (e.g a REPL interface, or something like |
Would like to see this PR go in, currently this kills my workflow because I have a zombie proc every time I Ctrl+C out of ts-node, leaving the port binded |
@narthur157 I take it you tested this PR against your use case? If not, could you give it a spin to confirm it would solve your problem properly? |
I've just sent a simpler alternative to the same problem here: #458 |
src/bin.ts
Outdated
}) | ||
// Ignore signals, and instead forward them to the | ||
// child process | ||
const forward = (signal: string) => process.on(signal, () => proc.kill(signal)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the latest version of NodeJS type definitions, signal
should be of type NodeJS.Signals
instead of string
. (it's not compiling)
src/bin.ts
Outdated
|
||
// If this process is exited with any other signals, | ||
// kill the subprocess with the same signal | ||
process.on('exit', (_code: number, signal: string) => proc.kill(signal)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the latest version of NodeJS type definitions, the "exit"
callback only accepts one argument. (it's not compiling)
signals are passed to the spawned child process; this means that the main process should not react to them, and instead wait for the spawned process to exit as a result of receiving the forwarded signal.
4987bab
to
31d5a34
Compare
@endel fixed the two issues you reported. What do we need to do to get this in? I'd like to do everything I can ASAP to get this merged (and hopefully released in a timely fashion), so if there is anything else I would need to do or any other concerns I would need to research or address, please let me know. |
Apologies for insisting, but is there anything that needs to be done to get this merged? |
Thank you! Let me know if I can ever be of any further help. |
@stelcheck @blakeembrey It seems the problem still persists when using child processes (cluster module). Here's an issue and example project demonstrating the problem: #519 |
signals are passed to the spawned child process; this means
that the main process should not react to them, and instead
wait for the spawned process to exit as a result of receiving
the forwarded signal.