Boot crashes with `bad_generator` in `rabbit_queue_decorator:select/1` when a quorum queue has `decorators = undefined`
bugkhepri
### Describe the bug
A node fails to boot with `error:{bad_generator,undefined}` when the queue federation plugin (or any other queue decorator) is enabled, if the metadata store contains a queue whose `decorators` field is the atom `undefined`:
```
BOOT FAILED
===========
Exception during startup:
error:{bad_generator,undefined}
rabbit_queue_decorator:-select/1-lc$^0/1-0-/1, line 40
rabbit_queue_decorator:maybe_recover/1, line 66
rabbit_queue_decorator:-register/2-lc$^0/1-0-/1, line 53
rabbit_queue_decorator:register/2, line 53
rabbit_boot_steps:run_step/2, line 60
rabbit_boot_steps:run_boot_steps/1, line 25
```
Originally reported in #16836.
### Root cause
`rabbit_queue_decorator:select/1` is a list comprehension that does not tolerate the field being `undefined` (or `none`), although the `decorators` field type explicitly permits both (`amqqueue.erl:116`: `decorators :: [atom()] | none | undefined | ...`):
```erlang
select(Modules) ->
[M || M <- Modules, code:which(M) =/= non_existing].
```
How a queue comes to hold `decorators = undefined`:
1. Under Mnesia, durable queue records were stored with `decorators = undefined` (`rabbit_db_queue:set_in_mnesia_tx/2` via `amqqueue:reset_decorators/1`, `amqqueue.erl:628`). The live RAM `rabbit_queue` table held the real list; the persisted `rabbit_durable_queue` table held `undefined`.
2. When `khepri_db` became `required` (4.3.0), the Mnesia->Khepri migration runs very early in boot, before the RAM `rabbit_queue` table is repopulated, so the converter sources from `rabbit_durable_queue` and copies those records into Khepri verbatim, preserving `decorators = undefined` (commit ecfccc65bb, "rabbit_db: Consider `rabbit_durable_*` tables during Mnesa->Khepri migration").
3. During a normal boot, **classic** queues are re-stored through `rabbit_amqqueue:internal_declare/2` -> `rabbit_queue_decorator:set/1`, which normalizes `undefined` to `[]`. **Quorum** queues recover via Ra and are *not* re-stored through that path, so they retain `decorators = undefined` in Khepri.
4. Enabling a queue decorator (e.g. the queue federation plugin) triggers `rabbit_queue_decorator:register/2`, which sweeps every queue via `maybe_recover/1` -> `select/1`. The quorum queue's `undefined` then raises `bad_generator` and the node fails to boot.
This is consistent with the original report: a single node upgraded from an older 4.x release (Mnesia) across 4.2 -> 4.3 (migrating to Khepri), whose queues are predominantly quorum queues, crashing only once queue federation was enabled. It is unrelated to the Erlang version.
### Reproduction
Full Docker reproduction: https://github.com/lukebakken/rabbitmq-server-16836
In short, `reproduce.sh` walks the upgrade chain that produces the bad data:
1. Boot **4.1.8** (Mnesia default), declare a durable classic queue and a durable quorum queue; both store `decorators = undefined` in `rabbit_durable_queue`.
2. Upgrade to **4.2.8** (still Mnesia).
3. Upgrade to **4.3.2**; the Mnesia->Khepri migration runs. After boot, the classic queue reads `decorators = []` but the quorum queue still reads `undefined`.
4. Enable `rabbitmq_queue_federation` and restart, and boot fails with the crash above.
### Affected versions
4.3.x, on any node carrying a quorum queue that was migrated from Mnesia. (Any queue decorator triggers it; the queue federation plugin is the common path.)
### Suggested fix
Make `rabbit_queue_decorator:select/1` tolerate the non-list values its own field type permits, returning `[]` for them:
```erlang
select(undefined) -> [];
select(none) -> [];
select(Modules) ->
[M || M <- Modules, code:which(M) =/= non_existing].
```
This is the single iteration point for queue decorators: every consumer (`maybe_recover/1`, `rabbit_amqqueue:policy_changed/2`, `rabbit_amqqueue_process:decorator_callback/3`, `rabbit_quorum_queue:notify_decorators/3`) routes through `select/1`, so this one change covers them all. It also mirrors the sibling exchange decorator, which already guards the same pattern (`rabbit_exchange_decorator.erl:80`: `select(_, undefined) -> [].`).
There are currently no unit tests for `rabbit_queue_decorator`; a small test covering `select(undefined)`, `select(none)`, and a normal list would be worth adding alongside the fix.
A complementary data-hygiene follow-up (optional) would be to normalize migrated quorum-queue decorators to `[]` so the stored value is corrected, not just tolerated.
关闭于 2026-07-02 0 条评论