Skip to content

fork: join HTTP server thread on llama_engine() shutdown (hydra_vortex #360 review) - #18

Merged
ddvnguyen merged 1 commit into
hydra-forkfrom
fix/llama-engine-p1-shutdown-thread
Jun 26, 2026
Merged

fork: join HTTP server thread on llama_engine() shutdown (hydra_vortex #360 review)#18
ddvnguyen merged 1 commit into
hydra-forkfrom
fix/llama-engine-p1-shutdown-thread

Conversation

@ddvnguyen

Copy link
Copy Markdown
Owner

Closes #16
Closes #17

Follows up on #15 (which added /v1/chat/completions to
llama-engine; that PR is already merged into hydra-fork at 8f3dc58 and
bumped in ddvnguyen/hydra_vortex#360). The review on ggml-org#360 surfaced two
findings, both in tools/llama-engine/llama-engine.cpp.

Finding 1 (P1) — std::terminate() on shutdown

After ctx_server.start_loop() returns (unblocked by the signal handler
on SIGTERM/SIGINT — e.g. a hydra-head restart), the HTTP server thread is
still blocked in pimpl->srv->listen_after_bind() (server-http.cpp:385).
server_http_context::thread is a std::thread with a defaulted
destructor (server-http.h:68), so destroying the still-joinable thread at
scope exit calls std::terminate() → SIGABRT. The hydra-head
auto-restart logic masks it, but the process never exits cleanly and the
crash-loop alerting may fire.

Live-confirmed on the #15 production build: podman
logs of hydra-system_head-rtx_1 showed
terminate called without an active exception after the deploy's auto-
restart, with a backtrace through ~server_http_context
llama_engine__libc_start_main.

Fix

Mirrors tools/server/server.cpp:283-285, 369-372 exactly:

ctx_server.start_loop();
if (params.port > 0) {
    ctx_http.stop();
    if (ctx_http.thread.joinable()) {
        ctx_http.thread.join();
    }
}
ctx_server.terminate();
llama_backend_free();

The if (params.port > 0) guard mirrors server.cpp's pattern of only
running HTTP teardown when the HTTP server was actually started.

Finding 2 (P2) — lifetime-ordering comment was factually inverted

The comment added in #15 at llama-engine.cpp:354-361 claimed the chosen
routes-before-ctx_http declaration order "Mirrors the lifetime
layout in tools/server/server.cpp" and that "reverse declaration order
would leave dangling captures". Both claims are wrong:

  • server.cpp does the opposite (ctx_http at L122, routes at L133).
  • ex_wrapper returns a std::function that captures routes.post_*
    by value; destroying it never dereferences routes, so both orders
    are safe. The real teardown hazard was the unjoined HTTP thread (Finding
    1), not lambda captures.

The declaration order is fine — the comment now accurately describes
what it does and cross-references #16.

Verification

  • clang -fsyntax-only clean
  • cmake --build build_sm120 --target llama-engine (RTX, sm_120)
  • Live bug reproduction: podman logs of the production engine
    showed the exact std::terminate() from this issue
  • cmake --build build_sm60 --target llama-engine (P100, sm_60)
  • Live fix verification: SIGTERM against the new binary
    → process exits 0, no SIGABRT, no zombie. Deferred to the
    hydra-vortex deploy step (production engine uses 14.4 GB GPU and
    the container's /llama/bin is read-only, so the new binary
    can only land via an image rebuild — the right place to verify
    end-to-end).

Cross-repo

After `ctx_server.start_loop()` returns (unblocked by the signal-triggered
`shutdown_handler` on SIGTERM/SIGINT), the HTTP server thread is still
blocked in `pimpl->srv->listen_after_bind()` (server-http.cpp:385).
`server_http_context::thread` is a `std::thread` with a defaulted
destructor (server-http.h:68), so destroying the still-joinable thread
at scope exit calls `std::terminate()` → SIGABRT.

`tools/server/server.cpp:283-285, 369-372` already handles this via
`clean_up()` → `ctx_http.stop()` + `ctx_http.thread.join()`. The engine
predates that pattern; this PR brings it into line.

Live-confirmed: podman logs of the production llama-engine on RTX showed
`terminate called without an active exception` after the #15
deploy restarted it (the bug was latent — #15 just exposed it). With this
fix, SIGTERM should drain the HTTP server, join the thread, and let the
process exit 0 — matching `server.cpp`'s graceful-shutdown contract.

While here, fix the misleading lifetime-ordering comment that #15 added at
llama-engine.cpp:354-361 (#17): the chosen declaration
order (`routes` before `ctx_http`) is fine because `ex_wrapper`
captures `routes.post_*` by value, but the previous comment's
"mirrors server.cpp" claim was the opposite of the actual layout
(server.cpp:122 declares `ctx_http` first, `routes` at server.cpp:133),
and the "dangling captures" rationale was wrong.

Closes #16
Closes #17

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ddvnguyen
ddvnguyen merged commit 5e2de41 into hydra-fork Jun 26, 2026
ddvnguyen pushed a commit to ddvnguyen/hydra_vortex that referenced this pull request Jun 26, 2026
Fork PR: ddvnguyen/llama.cpp#18 (review follow-up to #15)
Hydra issue: #356

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants