fix(state): use PASSIVE WAL checkpoint in close() and pre-VACUUM paths (#45383) - #72549
TurgutKural wants to merge 2 commits into
Conversation
40df3c5 to
de422dc
Compare
|
Thanks for following up on the remaining close/vacuum checkpoint paths. The two Problems
Suggested changes
Automated hermes-sweeper review. |
784cb8f to
9816d66
Compare
|
Rebased onto current upstream/main (7965462). Confirmed the two hermes_state.py call sites are still present on main. The CI failure was a pre-existing vercel sandbox test issue now fixed upstream — not related to this PR's checkpoint changes. |
cc51d55 to
273d6f1
Compare
PR #64607 switched the periodic _try_wal_checkpoint() to PASSIVE to fix rationale that shutdown has 'no concurrent writers'. Field evidence shows that assumption is false: a SIGTERM-driven shutdown (systemd timer, systemctl restart, Ctrl-C during a busy session) calls SessionDB.close() while writes may still be in flight. The interrupted TRUNCATE checkpoint tears b-tree pages and truncates the WAL, leaving a corrupt main DB with no rollback path. Reproduced 2026-07-27: hermes-gateway-restart.timer fired at 07:40, SIGTERM'd the coder gateway mid-write -> coder state.db (648 MB / 165K pages) corrupted (btreeInitPage error 11, trees 5/8), WAL 0 bytes, target session's messages unrecoverable. Change all remaining wal_checkpoint(TRUNCATE) calls to PASSIVE so no code path can corrupt the DB under a SIGTERM race. PASSIVE replays committed WAL frames without an exclusive lock and cannot tear pages; the WAL is settled on next open. Updates the checkpoint-strategy tests accordingly. Fixes #45383
…ual checkpoint mode
273d6f1 to
0cb8f19
Compare
|
Upstream ile çeliştiği için kapatıyorum (upstream-absorbed / design conflict). Kanıt — CI kırmızısı gerçek bir çelişki, flaky değil:
Bu iki test upstream/main'de Upstream'in bilinçli tasarımı:
Çözüm zaten mevcut (#74645 ile): Kapatma kararı yorumdaki kanıtlara dayanıyor; sorunun kendisi (#45383) plugin yoluyla çözülebilir durumda. |
Summary
PR #64607 switched the periodic
_try_wal_checkpoint()toPASSIVEto fix #45383, but leftTRUNCATEinclose()and the pre-VACUUM paths, on the assumption that shutdown has "no concurrent writers." Field evidence shows that assumption is false.Root cause (field evidence, 2026-07-27)
hermes-gateway-restart.timerfired at 07:40,systemctl restart→ SIGTERM to the coder gateway.gateway.py:6750,sys.exit(128+signum)) → normal Python exit →SessionDB.close()→PRAGMA wal_checkpoint(TRUNCATE).gh pr checkssubprocess). The interruptedTRUNCATEcheckpoint tore b-tree pages and truncated the WAL to 0 bytes.state.db(648 MB / 165,969 pages) corrupted (btreeInitPage() error 11, trees 5/8), WAL empty, target session's messages unrecoverable.The periodic
PASSIVEcheckpoint was not involved — the corruption came directly fromclose()'sTRUNCATE.Changes
hermes_state.py:close()(:2554) →PASSIVE(wasTRUNCATE).optimize_ftsVACUUM (:3121) →PASSIVE.:10646) →PASSIVE.wal_checkpoint(TRUNCATE)calls remain in the module.tests/test_wal_checkpoint_strategy.py:TestCloseUsesTruncaterenamed toTestCloseUsesPassive; assertsclose()issues exactly onePASSIVEand zeroTRUNCATEcheckpoints.Why PASSIVE is safe at close/vacuum
PASSIVEreplays committed WAL frames into the main DB without an exclusive lock, so it cannot tear pages under a SIGTERM race. The WAL is settled on the next open via the normal recovery cadence; shrink-to-zero is not required at close because a fresh gateway reopens the same DB and continues from the high-water mark. VACUUM already manages the WAL independently.Validation
pytest tests/test_wal_checkpoint_strategy.py→ 6 passed.python -m py_compile hermes_state.py→ OK.grep wal_checkpoint(TRUNCATE) hermes_state.py→ none.This PR was prepared on a clean worktree branched directly from
upstream/main(single commit, no carry-stack contamination).Fixes #45383 (follow-up to PR #64607, which fixed the periodic path only).