Nobody has started this yet — be first.
Business impact
Every event_stream() call creates a pubsub object that holds a connection from the underlying Redis connection pool. If subscribing raises -- a transient Redis blip, a connection reset, anything -- while a client happens to be connecting at that exact moment, the pubsub object and the connection it holds are never closed, and each such failure permanently removes one connection from the pool. This is invisible under normal conditions, but under sustained connection churn with intermittent Redis instability -- exactly the conditions a real production Redis experiences during a failover -- it accumulates until the pool is exhausted and every new stream request, even healthy ones, starts failing or hanging, with a full restart the only way to recover.
Problem
pubsub.subscribe(channel) is called before the try/finally block that would otherwise close the pubsub object, so if it raises, the finally block that calls pubsub.aclose() is never reached and the connection it was holding is never released back to the pool.
Current behavior
With a fake Redis client whose pubsub().subscribe() always raises, calling event_stream() and letting the exception propagate shows the fake pubsub's close method was never called.
Expected behavior
However connection setup fails -- whether subscribe() itself raises, or anything else between creating pubsub and successfully entering the main loop -- the pubsub object and its underlying connection must still be closed before the exception propagates.
Steps to reproduce
See practice_tickets/tests/test_ticket_13_pubsub_leak_on_subscribe_failure.py: use a fake Redis client whose pubsub().subscribe() always raises, call event_stream(), catch the resulting exception, and check whether the pubsub's close method was ever called -- it wasn't.
Why this matters
This is a resource-cleanup-ordering bug, not a logic bug -- every individual line is doing something reasonable, but the order (acquire resource, do a thing that can fail, guard cleanup only from that point onward) leaves a gap between resource acquired and cleanup guaranteed that a failure landing in exactly that gap slips through. It's the same shape of bug as a file handle opened before a try instead of inside it -- unremarkable individually, and only a real problem under the specific failure timing production eventually produces and a quick manual test essentially never does.
Suggested approach
Everything from pubsub = redis.pubsub() onward needs the guarantee that however far setup gets before failing, pubsub.aclose() still runs. Think about how much of the current setup sequence needs to move inside the try block -- and how much of the finally block needs to stop assuming setup fully succeeded, since it currently unconditionally calls unsubscribe(), which itself assumes subscribe() succeeded.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_13_pubsub_leak_on_subscribe_failure.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/pubsub-leak-on-subscribe-failureFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/pubsub-leak-on-subscribe-failureSubmit 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:8000http://localhost:8001Authorization: Bearer <user_id>X-User-Id: <user_id>curl localhost:8000/healthz
curl -N -H "Authorization: Bearer alice" localhost:8000/notifications/stream
# watch `: heartbeat` lines arrive roughly every 15s with no other traffic
Work the tickets in practice_tickets/tickets/ (ordered easy → hard); each names one dedicated pytest test in practice_tickets/tests/:
pytest practice_tickets/tests/test_ticket_01_heartbeat_default_margin.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 15, clean pass/fail summary
Tickets 14 and 15 need genuinely concurrent, long-lived streams, which the ASGI test transport used everywhere else in this project can't represent -- their tests spin up a real (short-lived) uvicorn server on a loopback socket instead (LiveServer in practice_tickets/tests/conftest.py).
Level 1
Fix a bug
Read existing behaviour, correct it.