Nobody has started this yet — be first.
Business impact
This is the exact "many replicas must not double-release the same due work" guarantee the README calls out as this project's reason for running two app replicas by default. Without it, two reaper replicas polling at the same moment can both claim and release the SAME expired reservation -- crediting its quantity back to available twice. In a real flash sale that means phantom stock appearing out of nowhere, which can then be sold to a second buyer for a unit that was never actually free.
Problem
SelectDueReservations's query is missing FOR UPDATE SKIP LOCKED. A second, concurrently-open transaction's identical due-work query can return the same row(s) a first transaction has already selected, even though the first hasn't committed or written anything yet.
Current behavior
A second, concurrently-open transaction's SelectDueReservations call returns the same reservation a first transaction already claimed and has not yet committed or rolled back.
Expected behavior
A row already selected (and about to be claimed) by one transaction should not be selectable by a second, concurrently-open transaction running the same query -- FOR UPDATE SKIP LOCKED is what provides that.
Steps to reproduce
go test ./tickets/tests/... -run TestTicket24 -vWhy this matters
A genuinely concurrent, goroutine-based version of this test turns out to be too timing-dependent to reliably trigger in practice. This ticket's test instead drives the exact production function, SelectDueReservations (exported specifically for this reason, and the same function ReapOnce itself calls), from two manually-sequenced transactions, deterministically -- SKIP LOCKED never blocks, so two sequential calls in one goroutine, with the first transaction deliberately left open, are enough to prove the claim is (or isn't) exclusive, with zero timing luck involved.
Suggested approach
Compare SelectDueReservations's SELECT against LockReservation's SELECT in pkg/inventory/service.go (right below it in the file listing) for the general shape of a claiming query, and check git log/the migration file's comment on reservations_due_idx for what the intended claiming pattern was.
Acceptance criteria
Verification
TEST_DATABASE_URL=postgresql://... go test ./tickets/tests/... -run TestTicket24 -v -count=5
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/reaper-missing-skip-lockedFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/reaper-missing-skip-lockedSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inSKIP LOCKEDhttp://localhost:8081localhost:5434flash-saleeveryday-widgetcurl localhost:8081/healthzgo run ./cmd/loadtest -url http://localhost:8081 -sku flash-sale -concurrency 10000Work the tickets in tickets/docs/ (ordered easy → hard); each names one dedicated Go test in tickets/tests/:
export TEST_DATABASE_URL=postgresql://inventory:inventory@localhost:5432/inventory_test?sslmode=disable
go test ./tickets/tests/... -run TestTicket24 -v # a single ticket
./tickets/run_tickets.sh # all 25, clean pass/fail summary
Tickets that don't touch Postgres (02, 07, 17, 19) run fine without TEST_DATABASE_URL; the rest t.Skip cleanly, not fail, if it's unset.
Level 1
Fix a bug
Read existing behaviour, correct it.