fix(update): an exited Windows owner with exit code 259 is dead, not running forever (review F10)
What: the Windows pid_is_alive opens the process with SYNCHRONIZE and returns WaitForSingleObject(h, 0) == WAIT_TIMEOUT; access denied still reads alive via liveness_from_open_error. Why: GetExitCodeProcess == STILL_ACTIVE (259) is also what a process that exited with code 259 reports while any handle keeps its object, and its creation time still matches the v2 marker, so Busy had no age bound. Python's _early_recovery already uses the wait probe. Red: Windows-only; marker_tests::pid_is_alive_false_for_an_exited_process_whose_exit_code_is_259 (cfg(windows)) needs a native runner. Green: cargo check --tests --target x86_64-pc-windows-msvc clean on the real marker.rs (std-only harness); Linux marker tests 49 passed.
This commit is contained in:
@@ -787,9 +787,9 @@ fn liveness_from_open_error(err: u32) -> bool {
|
||||
/// True when a process with `pid` currently exists.
|
||||
#[cfg(windows)]
|
||||
fn pid_is_alive(pid: u32) -> bool {
|
||||
use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, STILL_ACTIVE};
|
||||
use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, WAIT_TIMEOUT};
|
||||
use windows_sys::Win32::System::Threading::{
|
||||
GetExitCodeProcess, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
|
||||
OpenProcess, WaitForSingleObject, PROCESS_SYNCHRONIZE,
|
||||
};
|
||||
|
||||
// pid 0 is the System Idle Process, never an updater (Python: `pid <= 0` is dead).
|
||||
@@ -797,14 +797,17 @@ fn pid_is_alive(pid: u32) -> bool {
|
||||
return false;
|
||||
}
|
||||
unsafe {
|
||||
let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);
|
||||
// A process object is signalled once it exits. Never `GetExitCodeProcess ==
|
||||
// STILL_ACTIVE`: a process that exited with code 259 reads "running" for as long as
|
||||
// any handle keeps its object, and its creation time still matches the v2 marker.
|
||||
// Same probe as `hermes_cli/_early_recovery._pid_is_running`.
|
||||
let handle = OpenProcess(PROCESS_SYNCHRONIZE, 0, pid);
|
||||
if handle.is_null() {
|
||||
return liveness_from_open_error(GetLastError());
|
||||
}
|
||||
let mut code: u32 = 0;
|
||||
let ok = GetExitCodeProcess(handle, &mut code);
|
||||
let alive = WaitForSingleObject(handle, 0) == WAIT_TIMEOUT;
|
||||
CloseHandle(handle);
|
||||
ok != 0 && code == STILL_ACTIVE as u32
|
||||
alive
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -466,6 +466,26 @@ fn pid_is_alive_never_interprets_unsigned_pids_as_groups() {
|
||||
assert!(!pid_is_alive(u32::MAX - std::process::id() + 1));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn pid_is_alive_false_for_an_exited_process_whose_exit_code_is_259() {
|
||||
// 259 is STILL_ACTIVE: an exit-code probe reads this exited child as running for as long
|
||||
// as our `Child` keeps its process object open.
|
||||
let mut exited = std::process::Command::new("cmd")
|
||||
.args(["/C", "exit 259"])
|
||||
.spawn()
|
||||
.unwrap();
|
||||
assert_eq!(exited.wait().unwrap().code(), Some(259));
|
||||
assert!(!pid_is_alive(exited.id()), "an exited process is dead");
|
||||
let mut running = std::process::Command::new("cmd")
|
||||
.args(["/C", "ping -n 30 127.0.0.1 >NUL"])
|
||||
.spawn()
|
||||
.unwrap();
|
||||
assert!(pid_is_alive(running.id()), "a running process is alive");
|
||||
let _ = running.kill();
|
||||
let _ = running.wait();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pid_is_alive_false_for_pid_zero() {
|
||||
// pid 0 means the caller's process GROUP to kill(2), so kill(0, 0)
|
||||
|
||||
Reference in New Issue
Block a user