Nobody has started this yet — be first.
Business impact
Capacity planning has a blind spot: GET /metrics exposes a running total of how long-polls have ever resolved and how often they resolve early, but nothing shows how many GET /jobs/{id}?wait= calls are parked mid-wait right now, at scrape time. That's the one number that answers "how much concurrent long-poll load is this instance actually carrying" without reaching for a request-throughput rate that doesn't capture connection occupancy -- the thing that actually matters for "do I need another worker/instance."
Problem
A stub gauge, longpoll_active_waiters, already exists in app/metrics.py (see the PRACTICE-TICKET-03 comment above its definition) but nothing increments or decrements it anywhere.
Current behavior
longpoll_active_waiters (defined in app/metrics.py) is a stub gauge that nothing in get_job() ever calls .inc()/.dec() on, so it stays at 0 regardless of how many long-polls are actually in flight.
Expected behavior
longpoll_active_waiters should reflect the number of GET /jobs/{id} calls currently sitting inside the asyncio.wait_for(event.wait(), ...) call in get_job() -- incremented right before entering the wait, decremented right after it resolves (whether by a real change or by timing out).
Steps to reproduce
pytest practice_tickets/tests/test_ticket_03_active_waiters_gauge.py -vGET /jobs/{id}?wait=2 call is parked mid-wait, but longpoll_active_waiters rises by 0.0 instead of 1.0.Why this matters
This is the natural companion metric to this service's whole signature claim: the same request that makes long-polling efficient in request count is exactly the request that makes it show up here as sustained concurrent occupancy instead. get_job() already has a single, well-defined place where the wait begins and a single place where it ends -- a gauge incremented at the first point and decremented at the second, across both possible outcomes, is exactly what's needed.
Suggested approach
get_job() in app/routers/jobs.py already has a single, well-defined place where the wait begins (right before asyncio.wait_for(event.wait(), ...)) and a single place where it ends (right after). Make sure a request that resolves without entering the wait at all (an already-terminal job, or wait<=0) never touches this gauge.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_03_active_waiters_gauge.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 feat/active-waiters-gaugeFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/active-waiters-gaugeSubmit 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 inhttp://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 2
Implement a feature
Extend the system within its own patterns.