Subprocess is considered as failed exit when using `process.kill` on windows only
Reproduction
https://github.com/huang-julien/execa-repro
Steps
- clone it on windows
- run index.js with WSL
- it exit after 5 sec without issue
- run again but with powershell or cmd
- it crash after 5 sec (terminated with SIGTERM)
Description
Hello :wave:
This is an issue we detected on https://github.com/nuxt/test-utils/pull/848
On windows, with using process.exit() without exitCode, the exitCode received by execa is null while it is 0 on linux.
Hi @huang-julien,
Thanks for reporting this!
I cannot easily setup WSL on my machine at the very moment, so we might need a few back-and-forth going through your reproduction example. Thanks for putting this up!
My first question would be: does the example reproduce without setting up the HTTP server?
I reduced the repro with a simple timeout :) .
WSL isn't mandatory, you can use linux. It's just that I don't have dual boot linux/windows 😅
Weirdly, execa receive 0 and null for exitCode and signal in WSL/linux but gets null and 'SIGTERM' in windows
Thanks!
What's happening is the following: Windows does not support Unix-style signals. Node.js emulates a handful of signals, but it has some limitations. You can find more details here and there. This behavior is inherited by Execa since we rely on Node.js child_process core module.
Links to the code for subprocess.kill():
- Node.js: here, which forwards to libuv there
- libuv:
- on Unix, it calls the
killsyscall, which sends a Unix signal - on Windows, it calls the
TerminateProcessWin32 API method here , which just terminates the subprocess
- on Unix, it calls the
And for process.on(signal):
- Node.js: here, here then there
- libuv:
- on Unix, it uses
sigfillsetandsigaction - on Windows, it uses
SetConsoleCtrlHandlerWin32 API method to translateCTRL-CtoSIGINT,CTRL-BREAKtoSIGBREAK, closing the terminal window toSIGHUP. Some other logic is used to emulateSIGWINCHtoo. Other signals cannot be handled.
- on Unix, it uses
So, on Windows (without WSL), process.on('SIGTERM') is a noop. You should be able to experience this yourself by looking at the stdout property on the Execa result, which should contain 'SIGTERM signal received' when using WSL, but not when using cmd.exe or Powershell.
On Unix (or WSL), process.on('SIGTERM') is run, and since you call process.exit(), the subprocess has a successful exit, which means exitCode is 0 and signal is undefined. On Windows (non-WSL), process.on('SIGTERM') is not run, and the subprocess is terminated right away, which means exitCode is undefined and signal is 'SIGTERM'.
This is the expected behavior. The difference is due to the fact that Unix and Windows implement IPC differently. Node.js/libuv does some work to try to bridge those differences, but it is unfortunately limited.
The reason this behavior is now showing up with Execa 9 and not 8: I am not completely sure, but this is probably due to a bug fix landed by Execa 9. We fixed a bunch of related bugs in that release, and it's possible the new behavior, which is the correct one, might have been previously erroneous.
Thanks for posting this issue, as it gave me the idea to:
- Add a way to gracefully terminate in a cross-platform way, which will be available in the upcoming release. See docs
- Better document signal handling. Docs
@huang-julien Based on the above, do you think this issue should be closed, or is there something else that you think might be a pending bug?
This would be wonderful, thank you !
We still have a process pending with subprocess.kill(0) but i think it may be worth checking that after the upcoming release. This issue can probably closed.
The new features mentioned in my previous comment have just been released in 9.2.0.