Repository navigation
fix(OMN-13118): correlator resolves matched terminal in place for late awaiter - #1975
Conversation
…e awaiter (P_RESOLVE_BEFORE_AWAIT)
📝 WalkthroughWalkthrough
ChangesLongLivedTerminalCorrelator resolve-before-await race fix and regression tests
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
OMN-13118 — correlator resolves matched terminal in place for the late awaiter (P_RESOLVE_BEFORE_AWAIT)
Ticket: OMN-13118 (Tier B consume-leg redesign; epic OMN-12525)
The race (P_RESOLVE_BEFORE_AWAIT)
LongLivedTerminalCorrelatorruns ONE always-on poll loop over a singleruntime-lifetime consumer. A trial
register(cid)s its future BEFORE publishingits command (subscribe-before-publish at the correlation level), publishes, then
blocks on
wait(cid).Because the poll loop runs continuously and the publish -> generation -> wait
latency is large (~28s observed live), the loop typically consumes and matches
the correlated terminal BEFORE the runner's
wait()attaches. With the old_deliverthat popped the future on match, the resolved future was removedfrom the registry the instant it was matched. The late
_waitthen foundnothing in
_pending, created a fresh (unresolved) future, and timed out at the120s trial cap ->
None-> degenerate generation-failure row -> zero rows.This is the live "strike-seven" wedge: the COMPLETED terminal was published and
its HWM climbed every trial, but the delivered-and-discarded match meant the
runner saw no terminal.
The fix (this PR — clean, no instrumentation)
_deliverresolves the future IN PLACE:self._pending.get(cid)+future.set_result(body)— no pop on match. A future resolved before theawaiter attaches now survives in the registry until the awaiter reads it.
_waitowns removal: pops the cid in afinallyon BOTH success andtimeout, so a resolved-and-read future does not linger.
future.done()guard;register()(pre-publish) guarantees the future exists when_delivermatches.Two-line load-bearing change in
service_terminal_event_consumer.py:pop->getin_deliver, and move the registry removal into_wait'sfinally. No DIAG13118 instrumentation is included in this PR — this is theclean fix only.
Regression test
New
tests/unit/runtime/test_terminal_correlator_resolve_before_await.pydrivesthe real
LongLivedTerminalCorrelatorand pins the exact ordering:register(cid)-> append terminal -> block until the registry future is
done()(proving_deliverresolved it IN PLACE, i.e. delivery provably beat the awaiter) ->THEN
wait(cid)and assert it returns the body. Verified RED->GREEN: with_deliverreverted to pop-on-match the test FAILS (the resolved future is gonebefore the awaiter attaches); with the resolve-in-place fix it PASSES and the
awaiter also pops the cid on exit. A sibling test asserts the in-window
(arrive-during-await) path is not regressed.
Acceptance — LIVE K>=10 gate (PASSED on rebuild-10)
The acceptance gate for this defect class is NOT a unit test; it is the LIVE
K>=10 multi-cell battery on the stability-test lane. It PASSED on
reprobe-K10-rebuild10(2026-06-13T23:39Z, omnibase-infra-stability-test:18085/:18086, projection-api :13002), VERDICT PASS:
wait()logged RESOLVED (rebuild-9 had RESOLVED=0,3
no terminal event receivedwarnings; rebuild-10 had 0).context-roi-run-completed.v1HWM advanced 22 -> 24.total_rows448 -> 488 (+40);rows_for_fresh_run_id = 40viaprojection API (20 bool_flag + 20 address_norm, 40 distinct cids, sample
final_success=True first_pass_success=True attempt_count=1).TIMEOUT=0. (Residual TIMEOUT=39 / NO_PRE_REGISTER=40 are the benign
second-redundant-waiter from the runner opening two sessions per trial sharing
one cid — reported, not suppressed; candidate cleanup is register/await once
per trial.)
Evidence bundle:
docs/evidence/2026-06-12-weekend-pass/experiments/probe4-stability/reprobe-K10-rebuild10/(FINDINGS.md, build-provenance.json, projection-check.txt, watermarks-before/after.txt, diag13118-effects.log).
Honest pairing note: the live-proven deploy commit
0d958ea45carried theDIAG13118 instrumentation alongside this same logical fix (get-not-pop in
_deliver,_waitpops on exit). This PR ships the identical load-bearingchange WITHOUT the instrumentation. The runner-side
register(cid)-before-publishhalf of the pairing (omnimarket
cc1f658d, OMN-13118 runner-register-order) is aseparate PR.
Local verification
uv run ruff format/uv run ruff check— clean on both changed files.uv run mypy src/ --strict— Success, no issues in 2442 source files.uv run pytest tests/unit/runtime/auto_wiring/test_event_consumer_injection.py tests/integration/test_terminal_consumer_longlived_correlator.py tests/unit/runtime/test_terminal_correlator_resolve_before_await.py -q— 7 passed.pre-commit run --files ...— all hooks pass.Evidence-Source (OCC pairing)
The OCC evidence PR for the reprobe-K10-rebuild10 acceptance bundle is OCC#2616
(onex_change_control, base dev). It carries the OMN-13118 contract + the four
receipts (
dod-omnimarket-runner-pr,dod-omnibase-infra-correlator-pr,dod-live-gate,dod-deploy-assessment); the decisive evidence is the liveK>=10 reprobe-K10-rebuild10 PASS (
dod-live-gate). Receipt-Gate + deploy-gateresolve against OCC#2616.
Evidence-Source: OCC#2616
Evidence-Ticket: OMN-13118