Description
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 totry_wait
. And iftry_wait
is successful, ignore the error ofkill
- call
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, callGetExitCodeProcess
- if
GetExitCodeProcess
is successful and exit code is notSTILL_ACTIVE
, considerkill
successful
This is how kill
is implemented in our project:
Meta
rustc --version --verbose
:
rustc 1.70.0-nightly