fix: reliably keep server alive after GUI close on Windows (#402)

The HTTP /watchdog/disable request races with process exit on Windows,
causing the watchdog to kill the server before the request arrives.

Added a .keep-running sentinel file as a reliable fallback:
- Tauri writes the file to data_dir before sending the HTTP request
- The watchdog checks for it during the grace period after detecting
  parent death
- The file is removed after being read to avoid stale state

This approach works regardless of HTTP timing because file writes
complete synchronously before the Tauri process exits.

Fixes #372

Co-authored-by: Matt Van Horn <[email protected]>
This commit is contained in:
Matt Van Horn
2026-04-16 01:56:30 -07:00
committed by GitHub
co-authored by Matt Van Horn
parent 3e7727d1d2
commit 479bc7fc5e
2 changed files with 33 additions and 0 deletions
+17
View File
@@ -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