Skip to content

Fix delayed-delivery routing key prefix accumulation on retries (#2556)#2566

Open
apoorvdarshan wants to merge 1 commit into
celery:mainfrom
apoorvdarshan:fix-2556-routing-key-prefix-accumulation
Open

Fix delayed-delivery routing key prefix accumulation on retries (#2556)#2566
apoorvdarshan wants to merge 1 commit into
celery:mainfrom
apoorvdarshan:fix-2556-routing-key-prefix-accumulation

Conversation

@apoorvdarshan

Copy link
Copy Markdown

Summary

When RabbitMQ Native Delayed Delivery is used (Quorum Queues), a task that repeatedly calls self.retry(countdown=...) accumulates a routing-key prefix on every delayed retry. Once the accumulated routing key exceeds AMQP's 255-byte short-string limit, the retry fails with:

celery.exceptions.Reject: (error("'B' format requires 0 <= number <= 255"), False)

Root cause: kombu/transport/native_delayed_delivery.py::calculate_routing_key() unconditionally prepended a 28-bit binary prefix. On each delayed retry, Celery re-reads the already-prefixed routing key from request.delivery_info and passes it back into calculate_routing_key(), so a fresh 56-character prefix (28 bits + 28 dots) was prepended each time. After ~5 retries the key crossed 255 bytes.

Fixes #2556.

Fix

Before prepending a new prefix, strip any existing delayed-delivery prefix from the routing key. The prefix is a fixed 28-token dotted-binary pattern (MAX_NUMBER_OF_BITS_TO_USE digits, each 0/1, each terminated by a dot), matched by an anchored regex ^(?:[01]\.){28}. This keeps the routing key at a constant length across any number of retries while leaving ordinary (unprefixed) routing keys — including ones that contain dots — untouched.

Tests

Added two regression tests in t/unit/transport/test_native_delayed_delivery.py (both fail on main, pass with the fix):

  • test_calculate_routing_key_with_existing_prefix — a routing key that already carries a prefix gets the old prefix stripped, not doubled.
  • test_calculate_routing_key_does_not_accumulate_on_retries — repeated calls keep the key identical and <= 255 bytes.

All 22 tests in the file pass, and the surrounding transport unit suite is green (unrelated pre-existing failures only where optional broker deps are absent). flake8 and isort are clean on the changed files.

Disclosure: prepared with AI assistance; reviewed and verified locally.

The native delayed delivery calculate_routing_key() unconditionally
prepended a 28-bit binary prefix to the routing key. On sequential
self.retry(countdown=...) calls, Celery re-reads the already-prefixed
routing key from delivery_info and passes it back, so a fresh prefix was
prepended each time. After a few retries the accumulated routing key
exceeded AMQP's 255-byte short-string limit and the retry failed with
struct.error: 'B' format requires 0 <= number <= 255.

Strip any existing delayed-delivery prefix before prepending a new one
so the routing key stays a constant length across retries.

Fixes celery#2556.
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.81%. Comparing base (4281680) to head (8c5a75f).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2566   +/-   ##
=======================================
  Coverage   82.81%   82.81%           
=======================================
  Files          79       79           
  Lines       10333    10336    +3     
  Branches     1187     1187           
=======================================
+ Hits         8557     8560    +3     
  Misses       1575     1575           
  Partials      201      201           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@auvipy auvipy requested review from Nusnus and auvipy July 10, 2026 06:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Delay prefix in routing_key accumulates on task.retry() when using Quorum Queues

1 participant