Child::kill calls kill on Unix, and TerminateProcess on Windows.
If process is terminated, but handle is alive yet, kill is successful on Unix, but fails on Windows.
kill is successful on Unix because the process is zombie until waited (it fails after process waited, that's probably OK)
According to documentation of TerminateProcess
After a process has terminated, call to TerminateProcess with open handles to the process fails with ERROR_ACCESS_DENIED (5) error code
So if someone else (not us) terminated the process, kill would fail.
There are problems with this behavior:
- API is different between Unix and Windows
- API to reliably terminate the process is complicated
- call
kill
- if
kill failed, need to try_wait. And if try_wait is successful, ignore the error of kill
I could reproduce it. I don't have easy access to Windows machine to create completely isolated test case, but repro looks like this:
let mut command = std::process::Command::new("powershell");
command.args(["-c", "Start-Sleep -Seconds 10000"]);
let mut child = command.spawn().unwrap();
let pid = child.id().try_into().unwrap();
// Terminate by process id, NOT by our process handle. Like if some other process killed it.
// This function calls `OpenProcess(pid)` followed by `TerminateProcess`.
kill(pid).unwrap();
thread::sleep(Duration::from_secs(5));
child.kill().expect("Failed to kill child process");
This fails with:
Failed to kill child process: Os { code: 5, kind: PermissionDenied, message: "Access is denied." }
exactly as described in WinAPI docs.
I think proper kill implementation should look like this:
- if process has been waited already, fail explicitly, to be consistent with Unix
- call
TerminateProcess
- if
TerminateProcess failed, call GetExitCodeProcess
- if
GetExitCodeProcess is successful and exit code is not STILL_ACTIVE, consider kill successful
This is how kill is implemented in our project:
https://github.com/facebook/buck2/blob/b6fb1364e1caf6dac821767b27bda65726e38895/app/buck2_wrapper_common/src/winapi_process.rs#L58-L72
Meta
rustc --version --verbose:
Child::killcallskillon Unix, andTerminateProcesson Windows.If process is terminated, but handle is alive yet,
killis successful on Unix, but fails on Windows.killis successful on Unix because the process is zombie until waited (it fails after processwaited, that's probably OK)According to documentation of
TerminateProcessSo if someone else (not us) terminated the process,
killwould fail.There are problems with this behavior:
killkillfailed, need totry_wait. And iftry_waitis successful, ignore the error ofkillI could reproduce it. I don't have easy access to Windows machine to create completely isolated test case, but repro looks like this:
This fails with:
exactly as described in WinAPI docs.
I think proper
killimplementation should look like this:TerminateProcessTerminateProcessfailed, callGetExitCodeProcessGetExitCodeProcessis successful and exit code is notSTILL_ACTIVE, considerkillsuccessfulThis is how
killis implemented in our project:https://github.com/facebook/buck2/blob/b6fb1364e1caf6dac821767b27bda65726e38895/app/buck2_wrapper_common/src/winapi_process.rs#L58-L72
Meta
rustc --version --verbose: