BrighterCommand / BrighterCommand/Brighter

RMQ gateways declare transient non-exclusive queues, which RabbitMQ 4.3 blocks by default

Open
#4,355 0 comments 0 reactions 1 assignee Claimed by @iancooper View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.