Watchdog respects keep-server-running setting via /watchdog/disable endpoint

This commit is contained in:
Jamie Pine
2026-03-15 06:05:17 -07:00
parent e239be5bbb
commit 410413dc57
3 changed files with 42 additions and 11 deletions
+8
View File
@@ -143,6 +143,14 @@ async def shutdown():
return {"message": "Shutting down..."} return {"message": "Shutting down..."}
@app.post("/watchdog/disable")
async def watchdog_disable():
"""Disable the parent process watchdog so the server keeps running."""
from backend.server import disable_watchdog
disable_watchdog()
return {"message": "Watchdog disabled"}
@app.get("/health", response_model=models.HealthResponse) @app.get("/health", response_model=models.HealthResponse)
async def health(): async def health():
"""Health check endpoint.""" """Health check endpoint."""
+12
View File
@@ -51,6 +51,15 @@ except Exception as e:
logger.error(f"Failed to import required modules: {e}", exc_info=True) logger.error(f"Failed to import required modules: {e}", exc_info=True)
sys.exit(1) sys.exit(1)
_watchdog_disabled = False
def disable_watchdog():
"""Disable the parent watchdog so the server keeps running after parent exits."""
global _watchdog_disabled
_watchdog_disabled = True
def _start_parent_watchdog(parent_pid, data_dir=None): def _start_parent_watchdog(parent_pid, data_dir=None):
"""Monitor parent process and exit if it dies. """Monitor parent process and exit if it dies.
@@ -117,6 +126,9 @@ def _start_parent_watchdog(parent_pid, data_dir=None):
watchdog_logger.warning(f"Parent PID {parent_pid} not found on first check — disabling watchdog") watchdog_logger.warning(f"Parent PID {parent_pid} not found on first check — disabling watchdog")
return return
while True: while True:
if _watchdog_disabled:
watchdog_logger.info("Watchdog disabled (keep server running), stopping monitor")
return
if not _is_pid_alive(parent_pid): if not _is_pid_alive(parent_pid):
watchdog_logger.info(f"Parent process {parent_pid} gone, shutting down server...") watchdog_logger.info(f"Parent process {parent_pid} gone, shutting down server...")
if sys.platform == "win32": if sys.platform == "win32":
+21 -10
View File
@@ -760,17 +760,28 @@ pub fn run() {
let _ = &app; // used on unix let _ = &app; // used on unix
match &event { match &event {
RunEvent::Exit => { RunEvent::Exit => {
println!("RunEvent::Exit received - server will self-terminate via parent watchdog"); let state = app.state::<ServerState>();
// The server monitors this process's PID via --parent-pid. let keep_running = *state.keep_running_on_close.lock().unwrap();
// When this process exits, the server detects it and shuts itself down.
// No need for taskkill/wmic/process tree enumeration.
// On Unix, send SIGTERM to the process group for immediate cleanup. if keep_running {
#[cfg(unix)] // Tell the server to disable its watchdog so it survives
{ // after this process exits.
let state = app.state::<ServerState>(); println!("Keep server running: disabling watchdog...");
let keep_running = *state.keep_running_on_close.lock().unwrap(); let client = reqwest::blocking::Client::builder()
if !keep_running { .timeout(std::time::Duration::from_secs(2))
.build()
.unwrap();
let _ = client
.post(&format!("http://127.0.0.1:{}/watchdog/disable", SERVER_PORT))
.send();
} else {
// Server will self-terminate via parent-pid watchdog when
// this process exits. On Unix, also send SIGTERM for
// immediate cleanup.
println!("RunEvent::Exit - server will self-terminate via watchdog");
#[cfg(unix)]
{
if let Some(pid) = state.server_pid.lock().unwrap().take() { if let Some(pid) = state.server_pid.lock().unwrap().take() {
use std::process::Command; use std::process::Command;
let _ = Command::new("kill") let _ = Command::new("kill")