Skip to content

fix(daemon): harden /bench/run child lifecycle, bound request models - #221

Open
Artemowka22 wants to merge 2 commits into
FlashML-org:mainfrom
Artemowka22:fix/daemon-bench-lifecycle
Open

fix(daemon): harden /bench/run child lifecycle, bound request models#221
Artemowka22 wants to merge 2 commits into
FlashML-org:mainfrom
Artemowka22:fix/daemon-bench-lifecycle

Conversation

@Artemowka22

Copy link
Copy Markdown

Summary

Two defects in the daemon control plane, found by source-level audit in a downstream port (agisota/freetoken-mlx, docs/AUDIT.md) and confirmed present here:

  1. POST /bench/run: the bench child survives SSE cancellation. The streaming generator has no try/finally; when the client disconnects mid-stream, Starlette closes the generator and the ft bench bw child keeps running — holding GPU memory (the route exists precisely because the bench needs GPU exclusivity) and writing the shared profile, and it is never reaped. Fix: spawn the child in its own process group (start_new_session=True), wrap read/wait/emit in try/finally, and on teardown SIGTERM the group → 5 s grace → SIGKILL → guaranteed reap.

  2. Request models accept invalid input. StartBody.port (and via inheritance SwitchBody.port) is an unbounded int | None that flows into serve lifecycle mutation; list defaults use mutable literals. Fix: port constrained to 1..65535, list fields via Field(default_factory=list).

Wire behavior of /bench/run (progress/result/error events) is unchanged.

Test plan

  • pytest tests/daemon/test_daemon_app.py (new, torch-free like the rest of tests/daemon/): port-bound validation matrix, child-killed-and-reaped on client disconnect mid-stream, success path still emits the result SSE event
  • full tests/daemon/ suite passes

The SSE generator had no try/finally: a client disconnect mid-stream closed the
generator and left the bench child running — holding the GPU this route exists to
hand over, and writing the shared profile — and never reaped. Spawn the child in
its own process group and guarantee teardown on any exit from the stream: SIGTERM
the group, 5 s grace, SIGKILL escalation, unconditional reap (shielded so a second
cancellation mid-teardown cannot skip it). Wire format is unchanged.

Found via the freetoken-mlx downstream audit (docs/AUDIT.md, defect 1).
StartBody.port (and SwitchBody's, via inheritance) was an unbounded int flowing
into serve lifecycle mutation; constrain it to a real socket port (1..65535,
None = default). List args defaults move to Field(default_factory=list).

Found via the freetoken-mlx downstream audit (docs/AUDIT.md, defect 5).
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