web dashboard: state.db reconcile worker no longer segfaults the test interpreter (joined at lifespan shutdown) - #113552
Merged
Conversation
The dashboard lifespan started `_eager_reconcile_own_session_db` on a daemon thread and never joined it. Under pytest each TestClient context spawned one; the fresh tmp HERMES_HOME store makes every worker take the bootstrap path, so on a slow CI runner ten of them were still queued on `_session_db_bootstrap_lock` when the test's autouse leaked-DB sweep ran `SessionDB.close()` on the connection the live worker was stepping in `_open_probed` -> cross-thread `sqlite3_close` on an active statement -> `Fatal Python error: Segmentation fault` after every test had passed (PR #113430 run 35153362037, attempt 1; ~1/4 locally). The worker is now a regular (non-daemon) thread that the lifespan `finally` joins, so its connection is only ever closed by the thread that opened it and it cannot outlive the server or the interpreter. Startup is unchanged (the open still happens off the ready-probe path); the join is bounded by SessionDB's write patience, so shutdown cannot hang on it.
૮ >ﻌ< ა ci reviewran on 2838c97 — fix(web): join the state.db eager-reconcile worker at lifesp debug infoCI timingsCI timings · View report · View jobWall time 6m7s vs 6m (+1.9%). 8 job(s) slower, 3 faster, 1 unchanged.
|
SummaryFixes the What changed
Strengths
Findings
VerdictLooks good to merge |
This was referenced Sep 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The dashboard's startup state.db reconcile worker is now joined by the lifespan, so it can no longer have its sqlite connection closed from another thread — the
test_web_profiles_off_loop.pysegfault is gone.Symptom
tests/hermes_cli/test_web_profiles_off_loop.py ..............Fatal Python error: Segmentation faulton CI (PR #113430, run 35153362037 attempt 1) and ~1/4 locally, after all 18 tests had passed.Root cause
_lifespanstarted_eager_reconcile_own_session_dbon a daemon thread and never joined it; the CI faulthandler dump shows the worker insideconn.execute(...)inweb_server_sessions._open_probedwhile the main thread's autouse_close_leaked_session_dbsteardown calledSessionDB.close()on that same connection — a cross-threadsqlite3_closeon a statement being stepped. Ten earlier tests' workers were still queued on_session_db_bootstrap_lock(eachTestClientcontext bootstraps a fresh tmp store; slow on the runner), so the leak compounded across the file.Change
hermes_cli/web_server.py::_lifespan— thestatedb-eager-reconcileworker is a regular (non-daemon) thread held by the lifespan andjoin()ed in the shutdownfinally. Startup is unchanged (the open still runs off the ready-probe path, Desktop startup fails with GIL stall on Windows — _warm_gateway_module() import blocks event loop 15-22s #73083); the join is bounded by SessionDB's write patience, so shutdown cannot hang on it.tests/hermes_cli/test_web_server_boot_handshake.py::test_lifespan_shutdown_joins_statedb_reconcile_worker— invariant: the worker is off the startup path AND is finished (nostatedb-eager-reconcilethread alive) once theTestClientcontext exits. Red onorigin/main, green here.Twin sweep (
hermes_cli/web_server_*.py,tui_gateway/*.py): the hosted-room start thread is already stopped+joined; the Desktop cron ticker is a stop-event-driven long-lived loop whose join could block on a running job — different class, left alone.Validation
origin/mainscripts/run_tests.sh tests/hermes_cli/test_web_profiles_off_loop.py×10finished.is_set()False)tests/hermes_cli/test_web_server.py+ off-loop/boot-handshake/eventloop filesLive repro: before — probe
/tmp/batchbots/flakefix/probe_exit_segv.pyonorigin/main:Current thread … web_server_sessions.py line 143 in _open_probed+Fatal Python error: Segmentation fault(9/12), identical to the CI trace; after — same probe 0/12,registry live conns: 0.The probe inflates
_session_db_read_probe_statementswith a heavy recursive CTE so the worker is deterministically mid-step when the sweep runs; it is a diagnostic, not a committed test.Infographic