From 621e6b73c4fe4c13562c0906699003d0ca59f0f1 Mon Sep 17 00:00:00 2001 From: JJ Liebig Date: Tue, 22 Sep 2026 18:55:34 +0200 Subject: fix: recognize Windows daemons in non-killing jobs (#4489) refs #4309 --- src/platform/windows.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) (limited to 'src/platform') diff --git a/src/platform/windows.rs b/src/platform/windows.rs index cdf4acfc..d96570a6 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1379,7 +1379,8 @@ pub fn current_process_is_detached_server_daemon() -> bool { return false; } - matches!(current_process_is_in_job(), Ok(false)) + // Job membership alone does not tie the daemon lifetime to its launcher. + matches!(current_job_kills_processes_on_close(), Ok(false)) } pub fn foreground_job(child_pid: u32) -> Option { @@ -3257,6 +3258,67 @@ mod tests { const CONSOLE_TEST_PARENT_PID_ENV: &str = "HERDR_TEST_CONSOLE_PARENT_PID"; const WMI_DAEMON_TEST_CHILD_ENV: &str = "HERDR_TEST_WMI_DAEMON_CHILD"; + #[test] + fn windows_daemon_readiness_checks_job_limits_and_console() { + const CHILD_ENV: &str = "HERDR_TEST_DAEMON_READINESS_CHILD"; + if std::env::var_os(CHILD_ENV).is_some() { + use super::*; + + let job = unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) }; + assert!(!job.is_null(), "create test job"); + let job = unsafe { OwnedHandle::from_raw_handle(job) }; + assert_ne!( + unsafe { AssignProcessToJobObject(job.as_raw_handle(), GetCurrentProcess()) }, + 0, + "assign child to test job" + ); + assert!(current_process_is_in_job().unwrap()); + assert!( + current_process_is_detached_server_daemon(), + "a console-free process in a non-killing job must be ready" + ); + + for (flags, ready) in [(JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, false), (0, true)] { + let mut limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default(); + limits.BasicLimitInformation.LimitFlags = flags; + assert_ne!( + unsafe { + SetInformationJobObject( + job.as_raw_handle(), + JobObjectExtendedLimitInformation, + std::ptr::from_ref(&limits).cast(), + size_of::() as u32, + ) + }, + 0, + "set test job limits" + ); + assert_eq!(current_process_is_detached_server_daemon(), ready); + } + assert_ne!(unsafe { AllocConsole() }, 0, "allocate test console"); + assert!(!current_process_is_detached_server_daemon()); + assert_ne!(unsafe { FreeConsole() }, 0, "release test console"); + return; + } + + let mut child = Command::new(std::env::current_exe().unwrap()); + child + .args([ + "--exact", + "platform::windows::tests::windows_daemon_readiness_checks_job_limits_and_console", + "--nocapture", + ]) + .env(CHILD_ENV, "1"); + super::detach_server_daemon_command(&mut child); + let output = child.output().expect("run daemon readiness child"); + assert!( + output.status.success(), + "daemon readiness child failed: {}{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } + #[test] fn windows_environment_keys_use_unicode_case_insensitive_ordering() { assert_eq!( @@ -3275,7 +3337,7 @@ mod tests { "{}\n{}\n{}", cwd.display(), unsafe { GetConsoleWindow() }.is_null(), - !super::current_job_kills_processes_on_close().expect("inspect WMI daemon job") + super::current_process_is_detached_server_daemon() ), ) .expect("write WMI daemon test capture"); -- cgit v1.2.3