You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit c926842
Browse filesBrowse the repository at this point in the historyBrowse files
fix(webhooks): claim a repair attempt before requesting redelivery
`repair_delivery` read the repair row, checked the attempt cap and the
backoff, sent `POST /app/hook/deliveries/{id}/attempts`, and only then
wrote the attempt. Two watchdogs reading the same open row — an engine
overlapping its own restart, or two nodes on one store — could both pass
those checks and both ask GitHub to redeliver the same delivery, charge
it two attempts, and so reach the cap in half the real retries. The
retry pass added in 4d670b3 drives repairs from the table on every
poll, which is what made that window routine rather than incidental.
The attempt is now taken in one conditional store write before the
request goes out: `claim_webhook_redelivery` upserts with
`attempts = attempts + 1, last_attempt_at = .., resolved_at = NULL` but
only where the stored row still has the attempt count and the resolution
state the caller read. Exactly one claimant matches; the others get
`None` and send nothing. A close committed since the read also fails the
compare, so a pass can no longer resurrect a repair whose delivery has
landed. The per-GUID backoff ladder and the cap stay in the watchdog,
where the scan and the retry pass share them.
Both backends implement the claim as a single statement (`ON CONFLICT ..
DO UPDATE .. WHERE .. RETURNING`), which is the shape the webhook inbox
already uses to lease deliveries. New tests: the claim's compare on both
backends (attempt charged once, loser charges nothing, close stands,
resolved row reopens under the read resolution), and a two-node race on
one Postgres database proving one winner. Verified the SQL directly
against Postgres 18 and SQLite as well as through the crate's suites.
0 commit comments