[Ops] Celery tasks using the pooled AsyncSessionLocal can bind asyncpg futures to a dead event loop #129
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Found while implementing #109 (recap email escaping), not fixed there — it is out of that issue's scope and deserves its own change.
app/database.pyprovidestask_session()specifically so Celery tasks get a NullPool, throw-away engine per call. Celery tasks spin up a fresh event loop each invocation (asyncio.run(...)/new_event_loop()), and a pooled connection created under one loop must never be reused under another.Most tasks in
app/tasks/reminder_tasks.pycorrectly usetask_session(). At least these do not, and instead use the pooled globalAsyncSessionLocal:send_recap_emailauto_close_voting/_auto_close_voting_asyncCurrent behavior
In a long-lived Celery prefork worker, a pooled connection opened under a previous task's (now-closed) event loop can be handed to a later task running under a new loop. asyncpg then raises:
This reproduces readily: driving
send_recap_emailunder test hit it immediately, and the test only passed once a NullPool sessionmaker was injected. In production this presents as intermittent, hard-to-reproduce recap-email and vote-auto-close failures that clear up after a worker restart — exactly the kind of flake that gets misattributed to SMTP or Discord.Fix / Spec
app/tasks/for every use ofAsyncSessionLocalinside a Celery task body and switch each totask_session().AsyncSessionLocal. (_cleanup_trashed_audio_asyncshould be checked too — it opensAsyncSessionLocal()directly.)AsyncSessionLocalis referenced anywhere underapp/tasks/.Acceptance criteria
AsyncSessionLocal.References
webapp/backend/app/database.py(task_session, NullPool rationale)webapp/backend/app/tasks/reminder_tasks.py—send_recap_email,auto_close_voting/_auto_close_voting_async,_cleanup_trashed_audio_asyncFound during the v3.3.0 hardening pass (#109).