905fdda100
Salvage round on the Phase 1 skeleton (three findings from review): 1. The DELETE leg was silently upgraded to WAL on healthy SQLite: pre-seeding the file via PRAGMA was undone by SessionDB.__init__'s apply_wal_with_fallback(), which upgrades any non-WAL file whenever the configured mode (default wal) says so — only WAL-reset-vulnerable interpreters preserved DELETE, i.e. the leg tested the advertised mode only where CI wasn't running. Each matrix leg now pins database.journal_mode in an isolated HERMES_HOME for the child and audits the ON-DISK mode after the run (effective_mode_or_skip): a leg that ran in a different mode skips instead of double-counting. 2. Cell 2 exit-code conflation: a claimant crashing with an unhandled exception exits 1 — indistinguishable from the clean "lost the claim" exit(1), so one winner + seven crashes passed as consume-once proof. Codes are now disjoint (0=won, 10=lost, anything else=crash). 3. Crashed-writer diagnostics: wait_for now fails immediately with the child's stderr when the writer dies before reaching the kill window (was: 60s opaque deadline, stderr discarded). spawn_child prepends to an inherited PYTHONPATH instead of clobbering it. Plus: dead `if False` scaffolding removed from cell 1's writer; README matrix section corrected (cell 2 is default-mode-only by design — the consume-once property rests on a single predicated UPDATE).
93 lines
2.8 KiB
Python
93 lines
2.8 KiB
Python
"""Cell 2 — consume-once under cross-process concurrent delivery.
|
|
|
|
Contract clause (arXiv:2608.03836): a parked handoff is claimed by exactly
|
|
one consumer, no matter how many independent processes race for it. This is
|
|
the cell the paper found failing at saturation 1.0 in 36/40 cells across
|
|
deployed frameworks; Hermes' ``claim_handoff`` ships the paper's own repair
|
|
shape (single UPDATE with a state predicate, rowcount-checked), which the
|
|
tracking-issue probe confirmed. This cell pins it permanently.
|
|
|
|
8 independent OS processes are released from a file barrier simultaneously;
|
|
each calls ``SessionDB.claim_handoff()`` on the same pending session.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from tests.conformance.persistence._harness import (
|
|
on_disk_journal_mode,
|
|
reap,
|
|
spawn_child,
|
|
wait_for,
|
|
)
|
|
|
|
CLAIMANT = r"""
|
|
import sys, time
|
|
from pathlib import Path
|
|
from hermes_state import SessionDB
|
|
|
|
db_path = Path({db_path!r})
|
|
barrier = Path({barrier!r})
|
|
ready = Path({ready!r})
|
|
|
|
ready.touch()
|
|
deadline = time.monotonic() + 55
|
|
while not barrier.exists():
|
|
if time.monotonic() > deadline:
|
|
sys.exit(3)
|
|
time.sleep(0.005)
|
|
|
|
db = SessionDB(db_path=db_path)
|
|
won = db.claim_handoff("cell2")
|
|
# Disjoint codes: 0=won, 10=lost. An unhandled exception exits 1, which must
|
|
# NEVER be confusable with a clean "lost the claim" — a run where one
|
|
# claimant wins and seven CRASH is not a consume-once proof.
|
|
sys.exit(0 if won else 10)
|
|
"""
|
|
|
|
N_CLAIMANTS = 8
|
|
|
|
|
|
def test_exactly_one_claimant_wins(tmp_path):
|
|
from hermes_state import SessionDB
|
|
|
|
db_path = tmp_path / "state.db"
|
|
barrier = tmp_path / "go"
|
|
|
|
db = SessionDB(db_path=db_path)
|
|
db.create_session("cell2", source="conformance")
|
|
assert db.request_handoff("cell2", "telegram") is not False
|
|
|
|
children = []
|
|
ready_files = []
|
|
for i in range(N_CLAIMANTS):
|
|
ready = tmp_path / f"ready-{i}"
|
|
ready_files.append(ready)
|
|
children.append(
|
|
spawn_child(
|
|
CLAIMANT.format(
|
|
db_path=str(db_path), barrier=str(barrier), ready=str(ready)
|
|
)
|
|
)
|
|
)
|
|
|
|
# Barrier: release only when every process is up and polling.
|
|
wait_for(
|
|
lambda: all(r.exists() for r in ready_files),
|
|
what="all claimants at the barrier",
|
|
)
|
|
barrier.touch()
|
|
|
|
results = [reap(c) for c in children]
|
|
mode = on_disk_journal_mode(db_path)
|
|
|
|
codes = [rc for rc, _, _ in results]
|
|
assert all(rc in (0, 10) for rc in codes), (
|
|
f"[journal_mode={mode}] claimant crashed/timed out: {codes}; "
|
|
f"stderr: {[e[-300:] for _, _, e in results if e]}"
|
|
)
|
|
winners = codes.count(0)
|
|
assert winners == 1, (
|
|
f"[journal_mode={mode}] consume-once violated: {winners} of "
|
|
f"{N_CLAIMANTS} concurrent claimants won"
|
|
)
|