fix(gateway): evict scoped lock when PID+start_time match but process is not a gateway

On Linux, systemd spawns core services (cron, nginx, sshd) with
deterministic PIDs and jiffy start_times across reboots. A service can
land on the exact same PID and start_time as a previous gateway, causing
acquire_scoped_lock to mistake it for a live gateway and block startup.

The existing stale-detection paths only covered:
  - start_times both non-None and different (clear mismatch)
  - start_times both None (macOS/Windows fallback to cmdline check)

The boot-time collision falls through both: times are non-None and
equal, so neither branch fired.

Add a third check: when both start_times are known and match but the
live process fails _looks_like_gateway_process, read its cmdline. If
the cmdline is readable (non-None), we have positive evidence of an
impostor and mark the lock stale. Requiring a readable cmdline keeps the
check conservative — if cmdline is unreadable we do not evict.
This commit is contained in:
Tharushka Dinujaya
2026-06-15 05:25:07 -07:00
committed by Teknium
parent a376ca0081
commit ec05d2bc3e
2 changed files with 45 additions and 0 deletions
+15
View File
@@ -643,6 +643,21 @@ def acquire_scoped_lock(scope: str, identity: str, metadata: Optional[dict[str,
live_cmdline = _read_process_cmdline(existing_pid)
if live_cmdline is not None or not _record_looks_like_gateway(existing):
stale = True
# Secondary defence against boot-time PID+start_time collisions:
# systemd spawns core services deterministically, so an unrelated
# process (e.g. cron) can land on the exact same PID and jiffy
# count as a previous gateway. If both start_times are known and
# match but the live process is not a gateway, and we can confirm
# that by reading its cmdline, the lock is stale.
if (
not stale
and existing.get("start_time") is not None
and current_start is not None
and not _looks_like_gateway_process(existing_pid)
):
live_cmdline = _read_process_cmdline(existing_pid)
if live_cmdline is not None:
stale = True
# Check if process is stopped (Ctrl+Z / SIGTSTP) — stopped
# processes still appear alive to _pid_exists but are not
# actually running. Treat them as stale so --replace works.