fix(hosted): compare token and approval expiries after the advisory lock wait - #917
Merged
Merged
Conversation
… transactions `now()` is the transaction's start, read at BEGIN before pg_advisory_xact_lock waits, so a setup token that expired while its restore waited was reinserted and could evict a live one. Compare against statement_timestamp() (LOCKED_NOW) instead. Closes #911
The trimmed CTE ran whether or not the insert's guard admitted the row, so a restore refused as expired still evicted the Burrow's oldest live token at the cap.
dormouse-bot
requested a deployment
to
hosted-preview
October 2, 2026 14:14 — with
GitHub Actions
Waiting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
lockedopens its transaction withBEGINand then waits onpg_advisory_xact_lock, but every liveness check inside it compared againstnow(), which Postgres fixes at the transaction's start, before the wait. A setup token whose expiry passed whilerestoreSetupTokenwaited on the Burrow's lock was therefore reinserted, and atMAX_TOKENS_PER_BURROWthetrimmedCTE evicted a live token to make room for it. The enrollment poll's redeem had the same flaw.Those comparisons now read
LOCKED_NOW(statement_timestamp(), exported besidelockedinhosted/server/relay-auth.ts). Each statement inside the action starts after the lock is held, and unlikeclock_timestamp()it stays the same across a statement's CTEs, sopruned,trimmed, and the insert guard agree on one instant. This coversadmit's three comparisons and the poll's redeemUPDATEplus its follow-upSELECT.admit'strimmedCTE is also gated on the row's own liveness: it ran whether or not the insert guard admitted the row, so even a correctly refused restore evicted a live token.after()keepsclock_timestamp()for TTL stamps.hosted.mdgains the rule, and its word budget is ratcheted by 50.The new test in
hosted/server/tests/relay.test.tsfills a Burrow to its token cap, holds that Burrow's advisory lock from a second connection across a restored token's expiry, and asserts the restore inserts nothing and evicts nothing. The test failed in CI on the first commit with 7 of 8 tokens left, which exposed the trim; the second commit gates it. Against a local Postgres 16, the same lock-wait pattern inserted the expired row withWHERE expiry > now(), and inserted nothing withclock_timestamp()orstatement_timestamp(), and the gated statement left a full Burrow's 8 tokens intact for an expired restore.Closes #911 — automated triage