Quorum queues with combined names > 255 bytes crash in maybe_grow, member management CLI, and observer
Context
While tracing the callers of rabbit_queue_type_util:qname_to_internal_name/1 as part of investigating cross-vhost naming collisions in #11038, I identified an existing, standalone bug where queues whose combined names exceed 255 bytes crash across member management operations and CLI diagnostics.
Description
When a quorum queue has a combined name (vhost + _ + name) exceeding 255 bytes, operations in rabbit_quorum_queue:maybe_grow/5, rabbit_queue_type_ra:modify_members_matching/6, and rabbit_observer_cli_quorum_queues:sheet_body/1 crash with {badmatch, {error, {too_long, ...}}}.
Background & Root Cause
Under the AMQP 0-9-1 specification, queue names can be up to 255 octets and virtual host names can also be up to 255 octets. When joined internally as <<VHost/binary, "_", Name/binary>>, a completely valid queue definition can easily exceed 255 bytes (e.g. a 10-byte vhost with a 250-byte queue name).
Because Erlang atoms cannot exceed 255 bytes, rabbit_queue_type_util:qname_to_internal_name/1 returns {error, {too_long, Name}} for these names.
During declaration in rabbit_quorum_queue:declare/2:
RaName = case qname_to_internal_name(QName) of
{ok, A} ->
A;
{error, {too_long, N}} ->
rabbit_data_coercion:to_atom(ra:new_uid(N))
end,
LeaderId = {RaName, LeaderNode},
NewQ0 = amqqueue:set_pid(Q, LeaderId),For names > 255 bytes, RabbitMQ generates a unique identifier via ra:new_uid/1 and stores {RaName, LeaderNode} in amqqueue:set_pid/2.
All standard queue operations (publishing, consuming, deleting, status queries, and recovery on node restart) correctly read RaName directly from amqqueue:get_pid(Q).
However, three specific call sites recompute the internal name from scratch using qname_to_internal_name/1 instead of reading amqqueue:get_pid(Q):
rabbit_quorum_queue:maybe_grow/5(line 1922):{ok, RaName} = qname_to_internal_name(QName)When cluster size changes or policies require growing quorum queue replicas, this crashes with
{badmatch, {error, {too_long, ...}}}. In contrast, line 1641 indo_add_member/4already reads{RaName, _} = amqqueue:get_pid(Q0).rabbit_queue_type_ra:modify_members_matching/6(line 167):{ok, RaName} = rabbit_queue_type_util:qname_to_internal_name(QName)Administrative CLI commands (
rabbitmq-queues add_member,delete_member,grow,shrink, andrebalance) iterate over queues in a vhost. If any matching queue in the vhost has a name > 255 bytes, the entire command crashes with{badmatch}.rabbit_observer_cli_quorum_queues:sheet_body/1(line 141):{ok, InternalName} = rabbit_queue_type_util:qname_to_internal_name(#resource{virtual_host = Vhost, name= Name})In line 130,
rabbit_amqqueue:pid_of(Q)was already matched as{QName, _QNode}whereQNameis the Ra name. Callingqname_to_internal_namehere is redundant and crashesrabbitmq-diagnostics observerwhen viewing quorum queues.
Edge Cases Analyzed
- AMQP Protocol Compliance: Valid queue names allowed by AMQP 0-9-1 specifications trigger this naturally when combined with vhost names.
- Normal Queues (<= 255 bytes): For all standard queues,
amqqueue:get_pid(Q)contains the exact sameRaNamethatqname_to_internal_name/1would produce. Zero behavior change. - Process Liveness & Offline Nodes:
amqqueue:get_pid(Q)is a local record field access on#amqqueue{}; it does not perform network RPCs or require the leader process to be responding. - Queue Types: This is isolated to Quorum Queues. While Streams also use Raft internally, their queue object stores a standard Erlang process
pid()inamqqueue:get_pid(Q), not a{RaName, Node}tuple. Furthermore, Streams do not implement therabbit_queue_type_rabehaviour, so they are safely ignored bymodify_members_matching/6. - Zero Data Migration: This fix does not change how queue names are formatted or persisted on disk. Existing Ra state machines and member directories remain completely untouched.
Proposed Fix
Update the three call sites to read amqqueue:get_pid(Q):
- In
rabbit_quorum_queue:maybe_grow/5: replaceqname_to_internal_name(QName)with{RaName, _} = amqqueue:get_pid(Q). - In
rabbit_queue_type_ra:modify_members_matching/6: readRaNamefromamqqueue:get_pid(Q). - In
rabbit_observer_cli_quorum_queues:sheet_body/1: useQNamedirectly and remove the redundantqname_to_internal_namecall.
Source: rabbitmq/rabbitmq-server