Worker stops consuming after a remote control reply fails to encode, if pool processes get replaced
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 18/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- python, redis
- Domain
- backend, distributed-systems
Research direction
Start with the traceback: celery/worker/pidbox.py on_message catches the EncodeError and resets the pidbox, and kombu/pidbox.py reply/_publish_reply is where the publish fails. The reproduction script is in the issue body and needs a Valkey or Redis broker plus --max-tasks-per-child. Done means the worker keeps consuming at its normal rate after a failing reply, shown by the per-10-second task count. The root cause is not yet identified, so expect to debug the pool and pidbox interaction first.
Written by the indexing model from the issue text.
Description
Checklist
- I have verified that the issue exists against the
mainbranch of Celery. - This has already been asked to the discussions forum first.
- I have read the relevant section in the
contribution guide
on reporting bugs. - I have checked the issues list
for similar or identical bug reports. - I have checked the pull requests list
for existing proposed fixes. - I have checked the commit log
to find out if the bug was already fixed in the main branch. - I have included all related issues and possible duplicate issues
in this issue (If there are none, check this box anyway). - I have tried to reproduce the issue with pytest-celery and added the reproduction script below.
Mandatory Debugging Information
- I have included the output of
celery -A proj reportin the issue. - I have verified that the issue exists against the
mainbranch of Celery. - I have included the contents of
pip freezein the issue. - I have included all the versions of all the external dependencies required
to reproduce this bug.
Optional Debugging Information
- I have tried reproducing the issue on more than one Python version
and/or implementation. - I have tried reproducing the issue on more than one message broker and/or
result backend. - I have tried reproducing the issue on more than one version of the message
broker and/or result backend. - I have tried reproducing the issue on more than one operating system.
- I have tried reproducing the issue on more than one workers pool.
- I have tried reproducing the issue with autoscaling, retries,
ETA/Countdown & rate limits disabled. - I have tried reproducing the issue after downgrading
and/or upgrading Celery and its dependencies.
Related Issues and Possible Duplicates
Related Issues
- #3320 (
stats()from Flower failing with EncodeError, 2018 — same kind of trigger) - #11056 (
inspect statsfails when broker transport options hold a non-JSON value — the trigger in our case) - #10363 (changed
Pidbox._close_channel()for reset cycles; part of 5.7.0 — we haven't checked whether it's
involved, but 5.6.3 broke differently on the same trigger, see below)
Possible Duplicates
- None
Environment & Settings
Celery version: 5.7.0 (collider); also main at 1a48583d
celery report Output:
software -> celery:5.7.0 (collider) kombu:5.7.0 py:3.13.18
billiard:4.3.2 redis:8.1.0
platform -> system:Linux arch:64bit, ELF
kernel version:7.0.0-1014-aws imp:CPython
loader -> celery.loaders.app.AppLoader
settings -> transport:redis results:disabled
broker_url: 'redis://:********@127.0.0.1:6399/0'
deprecated_settings: None
worker_prefetch_multiplier: 1
task_default_queue: 'plain'
task_ignore_result: True
broker_connection_retry_on_startup: True
(this is the main environment: celery 1a48583d, kombu 38fbf784, billiard 895817a4 — their version strings are still
5.7.0 / 4.3.2; the release environment gives the same report with py:3.14.10)
Steps to Reproduce
Required Dependencies
- Minimal Python Version: Unknown (seen on 3.13.18 and 3.14.10)
- Minimal Celery Version: Unknown (seen on 5.7.0 and main 1a48583d)
- Minimal Kombu Version: Unknown (seen on 5.7.0 and main 38fbf784)
- Minimal Broker Version: Unknown (Valkey 8.1)
- Minimal Result Backend Version: N/A (results disabled)
- Minimal OS and/or Kernel Version: Unknown (Ubuntu 24.04, kernel 7.0.0-1014-aws)
- Minimal Broker Client Version: Unknown (redis-py 8.1.0)
- Minimal Result Backend Client Version: N/A
Python Packages
pip freeze Output:
amqp==5.4.2
billiard @ git+https://github.com/celery/billiard@895817a456af1a56c28675dfe4e22b87b991a72a
celery @ git+https://github.com/celery/celery@1a48583d40d31a7525474f83cf543b4194e113b5
click==8.5.1
click-didyoumean==0.3.1
click-plugins==1.1.1.2
click-repl==0.4.1
kombu @ git+https://github.com/celery/kombu@38fbf784791faa46c13c92a89c45a0c5c7bcd601
packaging==26.3
prompt-toolkit==3.0.53
python-dateutil==4.0.0
redis==8.1.0
typing-extensions==4.16.0
tzdata==2026.6
tzlocal==5.4.5
vine==5.1.0
wcwidth==0.9.4
Other Dependencies
Broker: Valkey 8.1 in Docker (`valkey/valkey:8.1`, `--save '' --appendonly no`), `redis://127.0.0.1:6399/0`.
Minimally Reproducible Test Case
# app.py
import os
import time
from celery import Celery
from celery.worker.control import inspect_command
app = Celery('repro', broker='redis://127.0.0.1:6399/0')
app.conf.update(worker_prefetch_multiplier=1, task_default_queue='repro', task_ignore_result=True)
@inspect_command()
def boom(state):
# The reply can't be encoded, so publishing it raises EncodeError outside the handler:
# celery/worker/pidbox.py logs "Control command error" and resets the pidbox.
# (An exception raised inside the handler would just be returned as {'error': ...}.)
return {'value': object()}
@app.task
def work(i):
time.sleep(0.25)
with open('/tmp/repro.log', 'a') as f:
f.write(f'{os.getpid()} {time.time():.3f} {i}\n')
celery -A app worker -c 8 --prefetch-multiplier 1 --max-tasks-per-child 10 -l infopython -c "from app import work; [work.delay(i) for i in range(5200)]"- After ~40 s:
python -c "from app import app; app.control.broadcast('boom', reply=True, timeout=2)"
(we sent it 12 times, every 5 s; the first one is already enough to see the drop) - Count the lines of
/tmp/repro.logper 10 seconds.
Expected Behavior
A control command reply that can't be encoded fails only that reply; the worker keeps consuming at the same rate.
Actual Behavior
The worker logs Control command error: EncodeError(...) (traceback below) and, if pool processes are being replaced
(--max-tasks-per-child), it almost stops taking messages from the broker: Task ... received lines drop to 0–2 per
10 s while the queue still holds thousands of messages. Nothing else is logged (no WorkerLostError).
Tasks finished per 10 s (* = after the first boom):
| window, s | main, --max-tasks-per-child 10 |
5.7.0, --max-tasks-per-child 10 |
5.7.0, --max-tasks-per-child 1000 |
|---|---|---|---|
| 0–40 | ~283 | ~288 | ~320 |
| 40–50 * | 150 | 146 | 312 |
| 50–60 * | 155 | 3 | 320 |
| 60–70 * | 0 | 6 | 320 |
| 70–80 * | 4 | 3 | 320 |
| 80–90 * | 5 | 2 | 320 |
| 90–150 * | 0–2 | 0–3 | ~316 |
Without process replacement the same failing replies have no visible effect, and process replacement alone (no
failing reply) is fine too.
Worker state ~110 s after the first boom (--max-tasks-per-child 10), two runs:
main:inspect stats→prefetch_count8, 8 pool processes,pool.writes.inqueues{'total': 8, 'active': 0};
inspect active0,inspect reserved1; RedisLLEN repro3749,HLEN unacked1.- 5.7.0:
prefetch_count8, 8 pool processes,pool.writes.inqueues{'total': 7, 'active': 0};
inspect active5,inspect reserved2; RedisLLEN repro3593,HLEN unacked2.
What we ruled out while looking for the cause: the Hub.remove() calls the redis transport makes on the pidbox
disconnect only hit sockets that are already closed, and the transport never registers on_readable on a pool pipe.
With celery 5.6.3 / kombu 5.6.1 / redis-py 5.2.1 (sentinel transport, a failing inspect stats reply as the trigger)
the worker stopped consuming completely after the first failing reply, even without process replacement.
How we found it: in production (Redis via Sentinel, prefork, --max-tasks-per-child 1000) Flower sent inspect stats
on startup, the reply failed on every worker (#11056), and right after that several prefork processes of each worker
stopped getting tasks; queue latency went from ~0 to 100+ s until the workers were restarted.
[2026-11-09 15:00:20,518: ERROR/MainProcess] Control command error: EncodeError(TypeError('Object of type object is not JSON serializable'))
Traceback (most recent call last):
File ".../kombu/serialization.py", line 41, in _reraise_errors
yield
File ".../kombu/serialization.py", line 220, in dumps
payload = encoder(data)
File ".../kombu/utils/json.py", line 63, in dumps
return _dumps(s, cls=cls, **dict(default_kwargs, **kwargs))
...
TypeError: Object of type object is not JSON serializable
when serializing dict item 'value'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File ".../celery/worker/pidbox.py", line 44, in on_message
self.node.handle_message(body, message)
File ".../kombu/pidbox.py", line 152, in handle_message
return self.dispatch(**body)
File ".../kombu/pidbox.py", line 119, in dispatch
self.reply({self.hostname: reply},
File ".../kombu/pidbox.py", line 156, in reply
self.mailbox._publish_reply(data, exchange, routing_key, ticket,
File ".../kombu/pidbox.py", line 296, in _publish_reply
producer.publish(
...
kombu.exceptions.EncodeError: Object of type object is not JSON serializable
- Dominant language
- Python
- Stars
- 29.1k
- Forks
- 5.3k
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 78
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from celery/celery
-
Docs: explain gc.freeze() in worker_init to keep prefork memory sharedPossibly taken @Duo-ee claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Remove the leftover references to the Riak result backend?Possibly taken @Duo-ee claimed this today. OpenIssue Type: Enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
`debug` options in the tutorial raise TypeErrorPossibly taken @freddyuniverse claimed this 22 days ago. OpenCategory: Documentation Issue Type: Bug Report
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
celery/celery#10897 · 1 comment ·
Maintainers usually reply within 1 day
-
beat unit tests test_pending_tick/test_ticks fail on Windows/CPython 3.11: exact float boundary in tick assertions leaves no room for clock quantizationPossibly taken @Duo-ee claimed this today. Open
Maintainers usually reply within 1 day
-
celery events -d resolves to --detach (click >= 8.5): '-d' is declared twice (--dump and --detach), dump mode unreachable from its documented short flagPossibly taken @rishabhx29 claimed this today. Open
celery/celery#11097 · 1 comment ·
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
AI bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
AUTO-MAS-Project/AUTO-MAS#1960 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
btclib-org/btclib-wallet#872 ·
Maintainers usually reply within 1 day
-
[Bug]: Scheduled returns scan all accepted minion keys causing extreme loadPossibly taken A pull request linked to this issue is open or already merged. Openbug needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
saltstack/salt#70789 · 1 comment ·
Maintainers usually reply within 3 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 87/100
btclib-org/btclib#3093 ·
Maintainers usually reply within 1 day