You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Apply dispatch pub/sub no-op to thread-local backends (v2.6.57)
Celery stores the result backend in a threading.local unless
result_backend_thread_safe is set, so each thread gets its own backend
instance. The 2.6.55/2.6.56 no-op only patched the import thread's
instance; the cleanup/integrity producer threads created fresh
unpatched backends and kept subscribing to result channels (verified
live and reproduced locally with a stack trace through send_task).
disable_dispatch_result_subscription now wraps Celery._get_backend so
every backend instance any thread creates carries the no-op. Validated
end-to-end: the API-triggered cleanup repro went from 2,633 standing
result-channel subscriptions to zero.
Copy file name to clipboardExpand all lines: CHANGELOG.MD
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
5
5
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0).
7
7
8
+
## [2.6.57] - 2026-06-11
9
+
10
+
### Fixed
11
+
12
+
- **The dispatch pub/sub no-op now actually applies to the threads that dispatch - the 2.6.55/2.6.56 fix never took effect in production.** Celery's `Celery._backend` is stored in a `threading.local` unless `result_backend_thread_safe` is set, so every thread gets its OWN backend instance. The no-op was patched onto the backend created on the import thread, while the cleanup/integrity producer threads (spawned per run by the maintenance routes) lazily created fresh, unpatched backends and kept subscribing - verified live: the first 2.6.56 cleanup run still showed 3,000-5,000 standing subscriptions and ~70-second Redis forced disconnects. Root-caused with a local reproduction (the run is clean when driven from the main thread, subscribes when driven from a `threading.Thread`, confirmed via a stack trace through `send_task -> backend.on_task_call`). `disable_dispatch_result_subscription()` now wraps `Celery._get_backend` so every backend instance created by any thread carries the no-op; validated end-to-end in a local stack where the API-triggered cleanup previously accumulated 2,633 result-channel subscriptions and now accumulates zero. The regression test now asserts the no-op holds in a freshly spawned thread. This retroactively explains all three production runs today: 2.6.53 stalled (get() subscriptions plus dispatch subscriptions), 2.6.54 was clean (its per-dispatch `cancel_for` ran on the same thread-local objects that subscribed), and 2.6.56 regressed (dispatch subscription alive in the producer thread with the unsubscribe removed).
0 commit comments