From 7cf7d2f7849936e3c84ee431433c2e8bcb6706ac Mon Sep 17 00:00:00 2001 From: historia Date: Wed, 26 Aug 2026 00:21:28 -0400 Subject: fix: harden server start/stop guards --- app/backends/servers.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'app/backends/servers.py') diff --git a/app/backends/servers.py b/app/backends/servers.py index 965d7df..986d82a 100644 --- a/app/backends/servers.py +++ b/app/backends/servers.py @@ -27,6 +27,7 @@ import signal import subprocess import sys import time +from datetime import datetime from pathlib import Path from typing import Callable, List, Optional @@ -253,6 +254,15 @@ def start(spec, progress: ProgressCallback = None, return True LOG_DIR.mkdir(parents=True, exist_ok=True) + # Refuse to double-start: a live pid file means a previous start is + # still booting (or its process is wedged). Spawning a second server + # on the same port would orphan the first with no pid record left. + if alive(spec.name): + report({"kind": "error", + "message": f"a {spec.name} server (pid " + f"{pid_for(spec.name)}) is already starting or " + "running; stop it first"}) + return False pid_file = _pid_path(spec.name) if pid_file.exists(): try: @@ -261,7 +271,10 @@ def start(spec, progress: ProgressCallback = None, pass cwd = getattr(spec, "cwd", None) - log_handle = _log_path(spec.name).open("w", encoding="utf-8") + # Append so an earlier boot's output survives (crash-loop debugging); + # the child inherits the handle and the parent's copy is closed right + # after the spawn, so nothing leaks here. + log_handle = _log_path(spec.name).open("a", encoding="utf-8") popen_kwargs = {"stdout": log_handle, "stderr": subprocess.STDOUT} if cwd is not None: popen_kwargs["cwd"] = str(cwd) @@ -277,6 +290,10 @@ def start(spec, progress: ProgressCallback = None, "message": f"could not start server: {exc}"}) log_handle.close() return False + log_handle.write(f"\n=== boot {datetime.now():%Y-%m-%d %H:%M:%S} " + f"(pid {proc.pid}) ===\n") + log_handle.flush() + log_handle.close() pid_file.write_text(str(proc.pid), encoding="utf-8") report({"kind": "starting", "name": spec.name, -- cgit v1.2.3