Skip to content

test(events): hold the SUBSCRIBE ack at the wire until the timer arm is taken (BUG-2786) - #1214

Merged
xarmian merged 1 commit into
mainfrom
fix/BUG-2786-confirm-test-discrimination
Aug 27, 2026
Merged

test(events): hold the SUBSCRIBE ack at the wire until the timer arm is taken (BUG-2786)#1214
xarmian merged 1 commit into
mainfrom
fix/BUG-2786-confirm-test-discrimination

Conversation

@xarmian

@xarmian xarmian commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Fixes BUG-2786. Test-only change in internal/events; no production behaviour changes (one doc comment on the beforeUnconfirmedMark seam corrected).

The defect was in the instrument, not the timing

TestAConfirmedSubscriptionIsNeverLeftMarkedUnconfirmed sets confirmTimeout = 1ns and hooks beforeUnconfirmedMark, which only runs on the timer arm of establishSubscription's confirmation select. A 1ns bound guarantees the timer arm is ready — not that the acknowledgement is not. Under CI load the receive goroutine consumed the SUBSCRIBE reply before the establisher reached its select, both arms were ready, Go chose at random, and on the confirmed arm the hook never ran: the guard then reported "the acknowledgement never landed before the mark" when it had landed too early for the interleave to exist.

Measured before the fix with a throwaway hookRan flag: unloaded -race -count=300 → 300/300; 16 busy loops + GOMAXPROCS=2 -race -count=1000 → 10 failures, all hookRan=false, zero stalled-ack. The paired 200ms/50ms WARNs in the CI log come from sibling tests that expire their bound by design — they were never load evidence.

The fix

A signal-released variant of the file's existing SUBSCRIBE delay proxy (newSubscribeGateProxy): the reply is parked at the wire until the test releases it, so only the timer arm can be ready at the select and the interleave is a construction rather than a coin toss. The three other tests that relied on a fixed delay outrunning their bound (margins 350ms / 1.95s / 2.8s) are converted too, so the class is closed structurally, not by margin; each releases only after the mark is observed (Subscribe returned + observer count, or observer report), so the acknowledgement is late by construction. Premise guards run in an order where each guard's cause is the only one remaining (waitParkedforcedOpenhookRanraced).

Evidence (all on a185790 unless stated)

  • go build ./..., go vet ./..., make lint (0 issues), go test -race ./internal/events/ ok.
  • Positive leg, main test: 16 busy loops, GOMAXPROCS=2 -race -count=1000 → 1000/1000.
  • Positive leg, all four gated tests: same load, -count=250 → ok, 0 failures. The pre-conversion tree (31d7f58c, gate released from the pre-lock hook) fails that same leg 13 times — that is codex round 3's P1, reproduced by measurement before it was fixed.
  • Mutation (remove the confirmClosed re-check in markUnconfirmedAdmission): detected 10/10 unloaded and 10/10 loaded, via the real assertion, not a premise guard.
  • Codex: 8 rounds, final CLEAN on two fresh angles (lifecycle/teardown; repeated-run state). Rounds 1–5 and 7 each found real defects in the instrument (failsafe not closing the gate; release() double-close; gate released before the mark; one-shot held reads not being barriers; gate published after the accept goroutine; guard order). One R5 finding is pre-existing and recorded rather than widened: the proxy scans for the RESP marker within a single Read, so a split marker is not parked — same code as the delay proxy on main, and a miss is now named by waitParked rather than silent.

Sweep boundary: internal/events and internal/watchevents searched for other timer-arm-dependent tests; the four here are the population.

@xarmian
xarmian marked this pull request as ready for review August 27, 2026 02:25
@xarmian
xarmian merged commit 2c74ac5 into main Aug 27, 2026
7 checks passed
@xarmian
xarmian deleted the fix/BUG-2786-confirm-test-discrimination branch August 27, 2026 02:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant