diff --git a/backend/server.py b/backend/server.py index bc6a81b2..2262d4c8 100644 --- a/backend/server.py +++ b/backend/server.py @@ -105,6 +105,11 @@ def _start_parent_watchdog(parent_pid, data_dir=None): This is the clean shutdown mechanism: instead of the Tauri app trying to forcefully kill the server (which spawns console windows on Windows), the server monitors its parent and shuts itself down gracefully. + + The Tauri app writes a .keep-running sentinel file to data_dir before + exiting when "remain running after close" is enabled. This is a reliable + fallback for the HTTP /watchdog/disable request, which can race with + process exit on Windows. """ import os import signal @@ -178,6 +183,18 @@ def _start_parent_watchdog(parent_pid, data_dir=None): if _watchdog_disabled: watchdog_logger.info("Watchdog was disabled during grace period, keeping server alive") return + # Check for sentinel file written by Tauri before exit. + # This catches the case where the HTTP disable request + # didn't arrive before the parent process died (common + # on Windows where process teardown is fast). + sentinel = os.path.join(data_dir, ".keep-running") if data_dir else None + if sentinel and os.path.exists(sentinel): + watchdog_logger.info("Found .keep-running sentinel file, keeping server alive") + try: + os.remove(sentinel) + except OSError: + pass + return watchdog_logger.info("Watchdog still enabled after grace period, shutting down server...") if sys.platform == "win32": # sys.exit triggers SystemExit, allowing uvicorn to run diff --git a/tauri/src-tauri/src/main.rs b/tauri/src-tauri/src/main.rs index c65dda0c..ca0cdf07 100644 --- a/tauri/src-tauri/src/main.rs +++ b/tauri/src-tauri/src/main.rs @@ -860,6 +860,22 @@ pub fn run() { // Tell the server to disable its watchdog so it survives // after this process exits. println!("Keep server running: disabling watchdog..."); + + // Write a sentinel file as a reliable fallback. On Windows + // the HTTP request below can race with process exit, leaving + // the watchdog unaware it should stay alive. The sentinel + // file is checked during the watchdog grace period. + let data_dir = app + .path() + .app_data_dir() + .unwrap_or_default(); + let sentinel = data_dir.join(".keep-running"); + if let Err(e) = std::fs::write(&sentinel, b"1") { + eprintln!("Failed to write keep-running sentinel: {}", e); + } else { + println!("Wrote keep-running sentinel to {:?}", sentinel); + } + let client = reqwest::blocking::Client::builder() .timeout(std::time::Duration::from_secs(2)) .build()