Nobody has started this yet — be first.
Business impact
The entire signature claim of this project -- long-polling costs far fewer requests than naive 1-second polling -- depends on one guarantee: a status change that happens while a GET /jobs/{id}?wait= call is parked wakes it up promptly, not after the full wait window. This bug reintroduces exactly the race the registry's own docstring describes and claims to avoid: a change that lands in the narrow gap between a caller reading "what should I wait on" and that caller actually starting to wait can be silently missed, forcing that request to sit out its full timeout instead of returning promptly. A caller experiencing this wouldn't see an error -- just occasional, unexplained slowness on an otherwise-fast long-poll, the kind of intermittent latency that's brutal to track down.
Problem
This reuses the same asyncio.Event object across every call to notify(), immediately clearing it right after setting it -- the class's own docstring explains exactly why replacing the Event object instead avoids a missed wakeup.
Current behavior
JobRegistry.notify() calls handle.event.set() immediately followed by handle.event.clear() on the same Event object, instead of swapping in a fresh Event -- reintroducing the exact missed-wakeup race the class's own docstring says replacing the object avoids.
Expected behavior
A caller that captures a reference to a job's current Event and starts waiting on it -- even if a notify() for that exact change already ran moments before the wait actually began -- must still observe that change immediately, not wait for some later one.
Steps to reproduce
pytest practice_tickets/tests/test_ticket_14_registry_missed_wakeup_race.py -vhandle = registry.create("job-x"), capture event = handle.event, call registry.notify("job-x"), then await asyncio.wait_for(event.wait(), timeout=0.5).Why this matters
This is a real concurrency bug, not a logic error you can spot by tracing through the code once -- it only manifests for a specific interleaving (a notify landing between "read the current Event" and "start waiting on it"), which real asyncio scheduling in a fast, low-contention test environment essentially never reproduces on its own. That's exactly why none of the existing black-box HTTP tests in this project catch it. A race that "isn't covered by any test" isn't the same claim as "doesn't exist."
Suggested approach
Compare what happens to an asyncio.Event object across two calls to notify() under the current code (same object, .set() then .clear() on it) versus what the docstring says should happen (a new Event object each time, with .set() called on the old one). Think through why a waiter holding a reference to the old object still needs to see it resolve correctly even after has already moved on to a new one.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_14_registry_missed_wakeup_race.py -v
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/registry-missed-wakeup-raceFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/registry-missed-wakeup-raceSubmit 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 innotify()http://localhost:8000localhost:6379curl localhost:8000/healthz{"status": "ok", "redis": true}Work the tickets in practice_tickets/tickets/ (ordered easy → hard); each names one dedicated pytest file in practice_tickets/tests/:
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
pytest practice_tickets/tests/test_ticket_01_completed_jobs_counted_as_failed.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 15, clean pass/fail summary
Each ticket test runs in its own pytest process with a 60s timeout, so one hung/crashed ticket can't take down the whole run or hide another ticket's result. Run pytest tests/ (the project's own suite, against fakeredis, hermetic and fast) after each fix to check for regressions -- the README documents exactly which fixes are expected to change that suite's results.
Level 1
Fix a bug
Read existing behaviour, correct it.