From 9f08d8b4896bc2b0a2f72ca8bb18dd393ad9ef93 Mon Sep 17 00:00:00 2001 From: Razvan Dimescu Date: Thu, 16 Apr 2026 19:21:56 +0300 Subject: [PATCH] fix(windows): stop service before port probe, wait for full exit Stop the running service before disabling Dnscache so the port 53 probe sees the real state (not Numa's own binding). Wait for SCM STOPPED state before copying the binary to avoid os error 32 (file in use). --- src/system_dns.rs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/system_dns.rs b/src/system_dns.rs index 35490ae..7e2d16a 100644 --- a/src/system_dns.rs +++ b/src/system_dns.rs @@ -679,15 +679,15 @@ fn install_windows() -> Result<(), String> { std::fs::write(&path, json).map_err(|e| format!("failed to write backup: {}", e))?; } - let needs_reboot = disable_dnscache()?; - // On re-install, stop the running service first so the binary can be - // overwritten (SCM holds a handle to the exe while it's running). - let reinstall = is_service_registered(); - if reinstall { + // overwritten and port 53 is released for the Dnscache probe. + if is_service_registered() { + eprintln!(" Stopping existing service..."); stop_service_scm(); } + let needs_reboot = disable_dnscache()?; + // Copy the binary to a stable path under ProgramData and register it // as a real Windows service (SCM-managed, boot-time, auto-restart). let service_exe = install_service_binary()?; @@ -880,14 +880,24 @@ fn start_service_scm() -> Result<(), String> { Ok(()) } -/// Stop the service. Idempotent — already-stopped or missing service logs -/// a warning but doesn't error, since both callers (install re-run, -/// uninstall) want best-effort cleanup rather than hard failure. +/// Stop the service and wait for it to fully exit. Idempotent — +/// already-stopped or missing service is not an error. #[cfg(windows)] fn stop_service_scm() { - if let Err(e) = run_sc(&["stop", crate::windows_service::SERVICE_NAME]) { - log::warn!("sc stop failed: {}", e); + let name = crate::windows_service::SERVICE_NAME; + let _ = run_sc(&["stop", name]); + // Wait up to 10s for the service to reach STOPPED state so the + // binary file handle is released before we try to overwrite it. + for _ in 0..20 { + if let Ok(out) = run_sc(&["query", name]) { + let text = String::from_utf8_lossy(&out.stdout); + if text.contains("STOPPED") || text.contains("1060") { + return; + } + } + std::thread::sleep(std::time::Duration::from_millis(500)); } + eprintln!(" warning: service did not stop within 10s"); } /// Remove the service from SCM. Idempotent — see `stop_service_scm`.