fix(webapp): Celery publishes no longer freeze a backend worker when Redis stalls (#841) #850
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/841-blocking-loop"
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?
Refs #841. The issue stays open: this fixes the real blocking it suspected, but the evidence shows that blocking isn't what killed the workers (see the comment on #841).
What was wrong
Every
task.delay()/apply_async()/control.revoke()reachable from anasync defran on the event loop. That was 47 sites across 12 modules, includingnotify()once per recipient, through a sync helper.A
.delay()also subscribes the enqueuing process to the result backend, which had no socket timeout and a 20 × 1 s retry policy. Measured against a real Redis:.delay()held the loop forWhile it waited, that worker served nothing and couldn't shut down cleanly. Under uvicorn
--workers 2with Redis paused, the worker handling a notify served 0 of its keep-alive requests for 26 s and needed a SIGKILL.The fix
app/enqueue.py:await enqueue(task, …)andawait off_loop(fn, …)run publishes on one dedicated thread per process.exceptaround a refused enqueue are unchanged.asyncso those reads stay on the loop.celery_app: the result backend getsredis_socket_connect_timeout=2,redis_socket_timeout=2and a 2-retry policy, matching the broker.Guard:
tests/test_enqueue_off_loop.pyasync defoutsideapp/tasks/.notify()publishes)./healthon the same loop must still answer. There are no wall-clock bounds.origin/main, with the 47 sites listed.Tests
Heads-up: lane 7 (#846) and lane 6 will conflict in
notification_service.pyandsession_service.py. After they merge, the guard catches any new direct publish.🤖 Generated with Claude Code