[codex major + omp major-1, corroborated] Notify fan-out drain drops sequences #72
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub#72
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
[codex major + omp major-1, corroborated] Notify fan-out drain drops sequences
Two independent reviewers found the same dropped-work race in
internal/notify/tasks.go: the drainer sees emptyseqs(drain()) and breaks while a concurrentenqueueFanoutattaches to the still-running entry and appends (tasks.go:228-237); the drainer removes the task (:257), the joiner already returned (:232) and starts no worker.end()(:80-102) never re-checkse.seqs. The sequence is never processed;Runsweeps webhooks/retention only, not unprocessed fan-out. Recipients permanently, silently miss notifications.Fix
Close the race: re-check
e.seqsin/afterend()(drain-then-end atomically under the entry discipline), or make the joiner own leftover seqs, or sweep unprocessed fan-out from the activity log. Deterministic regression test (late-attach vs terminal-drain interleaving) failing pre-fix. Coverage gate holds; doc Decisions entry (law 12).Acceptance criteria
-race -count=20.go test -race+ coverage ≥95% oninternal/notify.Fixed by PR #81 (#81): two-sided close in internal/notify/tasks.go — leader ends only via endIfQuiescent (drain-then-end atomic, refuses while seqs pending), joiner re-checks current() and re-enqueues on a miss. Deterministic regression test (late-attach-vs-terminal-drain) fails pre-fix, green -race -count=20; package coverage 97.2%.
PR #81 review (fix/issue-72,
e458976) — DROPPED-WORK scrutiny, verified in scratch worktrees (main worktree untouched, read-only).VERDICT: ready to merge. No blocking issues; no fixes needed.
Lock discipline (law 3) — PASS
Progress/livelock — PASS
No duplicates — PASS
finishLocked refactor — PASS: byte-identical to old end() body (state/stamp/note, wg.Done, delete, recent/order/128-bound). Fanout tasks never touched cursors/metrics; nothing lost. Bare end() retained only for the seqless webhooks task — safe.
Bad-repo path — PASS: drains+discards, still ends quiescent; a concurrent attach costs one more drain-discard round, then ends.
Negative control — GENUINE: adapted interleaving (begin→drain-empty→join+attach(7)→bare end) on pre-fix
d8c0e33finishes the task with seq 7 pending on the detached entry — the exact silent loss; new test asserts the refusal at that step. (Direct backport can't compile pre-fix — new symbols — so the control ran adapted; passed, temp file removed.)Gate: full notify suite -race PASS (count=1), new tests -race -count=20 PASS, coverage 97.2% (>=95%), gofmt/vet clean, zero import changes. Doc entries (06 Decisions, notify.go header, tasks.go Concurrency subsection) match the implementation.
Footnote (pre-existing, non-blocking): StartWebhooks returns the live e.rec pointer readable without the table lock — predates this PR, untouched by it.
Fixed by PR #81 (review: lock order + no-dup + negative control verified; 97.2% coverage), merged. Closing.