BrighterCommand / BrighterCommand/Brighter
RMQ gateways declare transient non-exclusive queues, which RabbitMQ 4.3 blocks by default
- Dominant language
- C#
- Stars
- 2.5k
- Forks
- 296
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 21
Description
## Summary
Both RMQ gateways (`RMQ.Sync` and `RMQ.Async`) declare their queues as **transient, non-exclusive, non-auto-delete**. RabbitMQ **4.3** blocks that combination by default, so with the out-of-the-box subscription configuration a Brighter consumer **cannot connect at all** on 4.3.
CI pins 4.2, so this is invisible today.
## Measured
Observed against stock `rabbitmq:4-management` (RabbitMQ **4.3.5**). Every `RMQ.Sync` gateway test fails during `Receive`:
```
Paramore.Brighter.ChannelFailureException : Error connecting to RabbitMQ, see inner exception for details
---- RabbitMQ.Client.Exceptions.OperationInterruptedException : The AMQP operation was interrupted:
AMQP close-reason, initiated by Peer, code=541,
text='INTERNAL_ERROR - Feature `transient_nonexcl_queues` is deprecated.
By default, this feature is not permitted anymore.
The feature will be removed from a future major RabbitMQ version, regardless of the
configuration; actual version to be determined.'
classId=50, methodId=10
```
Stack reaches the broker through:
```
RmqMessageConsumer.CreateQueue() -> Channel.QueueDeclare(...)
RmqMessageConsumer.EnsureChannel()
RmqMessageConsumer.Receive(TimeSpan?)
```
## Cause
`durable: false`, `exclusive: false`, `autoDelete: false` is exactly the deprecated `transient_nonexcl_queues` combination.
- `src/Paramore.Brighter.MessagingGateway.RMQ.Sync/RmqMessageConsumer.cs:460`
```csharp
Channel.QueueDeclare(_queueName.Value, _isDurable, false, false, SetQueueArguments());
//NOTE: hasDlq cannot be true if _deadLetterQueuename is null
if (_hasDlq) Channel.QueueDeclare(_deadLetterQueueName!.Value, _isDurable, false, false, new Dictionary());
```
- `src/Paramore.Brighter.MessagingGateway.RMQ.Async/RmqMessageConsumer.cs:541` and `:546` do the same asynchronously.
`_isDurable` comes from the subscription, and **`false` is the default** on both gateways — `RmqSubscription.cs:106` and `:165` (Sync), `:113` and `:176` (Async). So the failure is the default path, not an unusual configuration. Setting `isDurable: true` on the subscription avoids it.
Note the dead-letter queue declaration inherits `_isDurable` too, so a durable subscription fixes both declarations together.
## Impact
- On RabbitMQ 4.3 and later, any consumer that does not explicitly opt into `isDurable: true` fails to start.
- The deprecation notice says the feature "will be removed from a future major RabbitMQ version, regardless of the configuration" — so permitting it via broker config is a stopgap, not a fix.
## Suggested direction
Decide what Brighter should declare when `isDurable: false`. The options that keep working on 4.3+ are a durable queue, or an auto-delete and/or exclusive queue where the transient lifetime is actually what the caller wanted. Worth settling deliberately, since it changes queue lifetime semantics for existing users.
## How it was found
Incidentally, while verifying the migration of the RMQ CI images off the `brightercommand/rabbitmq:*-management-delay` plugin builds onto stock images (#4240 / #4297). The machine happened to have a 4.3.5 broker on `localhost:5672`; the CI images are 4.2 and 3.13 and are unaffected.
Contributor guide
Assessment
This issue has not been assessed yet.