Startup recovery races the receiver and can deliver a fresh event twice #299
Reference in New Issue
Block a user
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?
Found by the review of #297. Pre-existing — a deterministic probe returns
posts=2identically onnextand on that PR's head, so it predates the branch and is not caused by it.Startup
recoverPendingruns in a goroutine concurrently with the receiver accepting traffic. A delivery row written just beforeNotifycan be selected by recovery, claimed, delivered, and RELEASED — after whichNotify'sretainIdlesucceeds on the now-free id and dispatches the same delivery again. The target receives two POSTs.The window is the startup interval during which recovery is still scanning and the receiver is already serving. It is narrow, and the reviewer found it only by building a deterministic probe after a sloppy harness surfaced it as an intermittent 901-for-900.
Why this is not tag-blocking
Delivery is at-least-once by design and now says so in the README (see the Rationale section, and #256 for why a bookkeeping-write failure deliberately leaves a delivery recoverable rather than lying about it). A receiver is already required to be idempotent. This race produces a duplicate that falls inside that documented guarantee rather than violating it.
It is still worth fixing: every other duplicate in the system is the consequence of a failure, and this one happens on a healthy database during ordinary startup. "At-least-once when something went wrong" and "at-least-once on every restart with concurrent traffic" are different operational characters even though the contract covers both.
Definition of done
internal/delivery/inflight.go) is the right place to reason about this; the bug is that release-then-reacquire is legal for a delivery that was never actually finished by the second claimant's standard.Not milestoned. Top of the next milestone.