Skip to content

R-3: Model-server crash recovery — respawn, fast-fail gate, local fallback#145

Merged
TCVinNYC merged 2 commits into
mainfrom
fix/r3-model-server-recovery
Jul 3, 2026
Merged

R-3: Model-server crash recovery — respawn, fast-fail gate, local fallback#145
TCVinNYC merged 2 commits into
mainfrom
fix/r3-model-server-recovery

Conversation

@TCVinNYC

@TCVinNYC TCVinNYC commented Jul 3, 2026

Copy link
Copy Markdown
Member

Summary

The opt-in shared-detector process (AUTOPTZ_MODEL_SERVER=1 — the validated 4-12-cam scaling mode) was spawn-once: if it died, every InferenceClient.detect() blocked 5 s and returned [] forever. Now:

  • Supervisor respawn from the periodic health scan, mirroring the worker restart pattern exactly (exponential 1→30 s backoff, 5 attempts, reset-on-recovery). Respawn is non-blocking on the tick/GUI thread: start + deadline, then poll readiness on later ticks (adversarial review caught that a blocking ready.wait(30) would have frozen the Qt main thread up to 30 s per attempt; fixed with RED/GREEN evidence, independently reproduced by the re-reviewer).
  • Same queues + shm slots reused across respawn (attach is lazy per-request — pinned by test); sequence-number stale-reply protection holds across the restart boundary; healthy clients unaffected.
  • Client fast-fail: timeout_s 5→2 s default + a server_down gate that returns [] immediately during outages (no 2 s/frame crawl); gate stays set through spawning+backoff, cleared on ready.
  • Budget exhaustion → visible fallback: model_server_failed() accessor (mirrors is_camera_failed()) + workers rebuild LOCAL detectors via RemotePool.detector() fallback, so detection continues degraded instead of dying.
  • Model-server stays opt-in; no default flip; no knobs.

Test plan

  • 19 new RED-first tests across tests/test_inference_server_ipc.py + tests/test_supervisor_health.py: kill-child→respawn→detections resume; immediate fast-fail during outage (wall-clock bound); budget exhaustion→flag+fallback; same-queue identity; non-blocking tick path (fake Event.wait raises on blocking call); spawn-deadline→failed-attempt backoff; behavioral 2 s timeout bound.
  • Full suite 1936 passed; ruff + mypy + selftest green.
  • Task review: two rounds (Needs-fixes → fixed → Approved; re-reviewer independently reproduced RED and GREEN).

Validation

CI-safe (opt-in path). Live mid-run server-kill exercise folds into the 2.2.0 soak gate. Includes the #141 test-isolation cherry-pick (dedupes on merge).

🤖 Generated with Claude Code

TCVinNYC and others added 2 commits July 3, 2026 15:42
The shared model-server process (AUTOPTZ_MODEL_SERVER=1) was spawn-once with
no recovery: a dead server meant every camera's detect() blocked its full
5s timeout forever. Add supervisor-side respawn on the same health-scan
cadence, reusing the existing per-camera backoff pattern (exponential
1->30s, max 5 attempts) for the single shared process. Reuses the same
request/response queues across a respawn (shm re-attach was already lazy
per-request in serve(), pinned with a test rather than changed).

Client-side: InferenceClient's default timeout drops 5.0s -> 2.0s and gains
a server_down gate so detect() fails fast during an outage instead of
stalling each camera at the full timeout per frame.

On restart-budget exhaustion, Supervisor.model_server_failed() latches and
every model-server-mode worker's refresh_detector_from_pool() is invoked;
RemotePool.detector() now checks a failed flag and swaps in a lazily-built
local detector instead of the dead IPC client, so detection continues in
degraded per-worker mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
_respawn_model_server() previously did proc.start() then a synchronous
ready.wait(timeout=30.0) inside _scan_model_server_health(), which tick()
drives from a QTimer on the GUI thread — a slow respawn could freeze the
UI for up to 30s per attempt, up to 5 attempts.

Split the respawn into two phases across scan ticks: starting the child
now only records a spawn deadline and the ready Event, returning
immediately. Later ticks poll ready.is_set() (never .wait() with a
timeout) until either it signals ready (success — gate cleared, attempts
reset) or the deadline passes (treated as a failed attempt — stuck child
killed, existing backoff/budget accounting applies). The server_down gate
stays set for the whole spawning+backoff window so clients keep
fast-failing throughout.

Also add a real-behavior companion to test_default_timeout_is_two_seconds
and simplify an existing conditional-then-reassign in the health scan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@TCVinNYC TCVinNYC force-pushed the fix/r3-model-server-recovery branch from 1d39284 to 70fccba Compare July 3, 2026 19:44
@TCVinNYC TCVinNYC merged commit 859690c into main Jul 3, 2026
3 checks passed
@TCVinNYC TCVinNYC deleted the fix/r3-model-server-recovery branch July 3, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant