Skip to content

serve: give the engine its graceful exit — stdin drain, SIGBREAK, group isolation - #1734

Merged
JustVugg merged 2 commits into
JustVugg:devfrom
tarazum:fix/heat-save-stdin-drain
Sep 24, 2026
Merged

JustVugg merged 2 commits into
JustVugg:devfrom
tarazum:fix/heat-save-stdin-drain

Conversation

@tarazum

@tarazum tarazum commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #1733.

The engine's only teardown is atexit (qt_shutdown writes HEAT_FILE); every launcher stop path hard-killed it, so warm-start persistence never happened under coli serve, on any platform. On Windows there was additionally no way to reach a graceful exit from outside at all.

  • Engine.close: close the engine's stdin and wait a generous drain window (COLI_ENGINE_DRAIN_S, default 30 s — the serve loop reads requests from stdin, EOF runs atexit; EOF only lands between turns) before the existing terminate/kill ladder
  • serve(): on Windows also handle SIGBREAK — CTRL_BREAK is the one console signal a controller can target at the serve process group; without a handler it killed the loop before its finally could drain
  • Engine.init: spawn the engine in its own process group on Windows so the group-targeted CTRL_BREAK cannot kill it before the drain lands
  • coli stop: stop launchers first and give them the drain window before touching engine pids
  • tests/test_engine_close_drain.py: the shutdown handshake against real python subprocesses — EOF exit 0 within the window; hung engine falls back to the hard-stop ladder promptly

Validation on Windows (RTX 5070 Laptop 8 GB, Qwen3.6-35B-A3B int4-gs64, CUDA tier): before — no heat file after any shutdown; after — [qtier] HEAT_FILE saved on every stop, [qtier] HEAT_FILE loaded on restart, and the warm-persisted pass beats the same-process warm pass (decode 10.1-11.0 → 12.1-15.0 tok/s; long-context TTFT 43.9 → 26.6 s). Local make check equivalent: unit tests pass (python -m unittest discover -s tests -p test_engine_close_drain.py); no C changes.

Known remaining gap (needs a control channel, out of scope): coli stop from another console still cannot deliver a graceful stop on Windows.

…up isolation

The engine's only teardown is atexit (qt_shutdown writes HEAT_FILE); every
launcher stop path hard-killed it (terminate/TerminateProcess), so warm-start
persistence never happened under coli serve, on any platform.

- Engine.close: close the engine's stdin and wait a generous drain window
  first — the serve loop reads requests from stdin, EOF runs atexit —
  then fall through to the existing terminate/kill ladder
  (_ENGINE_DRAIN_S / COLI_ENGINE_DRAIN_S, default 30 s)
- serve(): on Windows also handle SIGBREAK — CTRL_BREAK is the one console
  signal a controller can target at this process group, and without a
  handler it killed the serve loop before its finally could drain
- Engine.__init__: spawn the engine in its own process group on Windows so
  the group-targeted CTRL_BREAK cannot kill it before the drain lands
- coli stop: stop launchers first and give them the drain window before
  touching engine pids
- tests/test_engine_close_drain.py: the shutdown handshake against real
  subprocesses (EOF exit 0 within the window; hung engine falls back)
The drain wait() may simply return None for a process (or a test double,
see FakeProcess in test_openai_server.py) that only terminates when asked
— the CI DispatcherTest caught the first version skipping terminate()
entirely. poll() after the drain reflects both a real EOF exit and a
stubbed one; the ladder runs only when the process is still alive.
@JustVugg
JustVugg merged commit 50c9e16 into JustVugg:dev Sep 24, 2026
29 checks passed
@tarazum
tarazum deleted the fix/heat-save-stdin-drain branch September 24, 2026 21:04
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.

2 participants