You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The delivery limit validation introduced for RabbitMQ 4.0 fails endpoint startup when the input queue was declared with the x-delivery-limit: -1 queue argument — even though this is the method the RabbitMQ documentation describes as the only guaranteed way to get a truly unlimited delivery limit (see the RabbitMQ docs change referenced in #1688: "The value of -1, which disables the limit altogether, cannot be currently set through a policy - this can only be done with x-delivery-limit=-1 queue argument during queue declaration.").
On RabbitMQ 4.x, a quorum queue declared with x-delivery-limit: -1 is reported by the management API with "delivery_limit": "unlimited", which the transport parses as -1. BrokerVerifier.ShouldOverrideDeliveryLimit then:
does not match the BigValueInsteadOfActuallyUnlimited (100000) early exit,
does not match the limit == -1 && IsTransportPolicy(...) branch (the value comes from a queue argument, not a transport-created policy), and
falls into the "explicitly configured delivery limit" guard, which throws.
The result is that a queue configured to have exactly the semantics the validation is trying to guarantee (messages are never dropped by the broker due to repeated redeliveries) is rejected as misconfigured.
This particularly affects users who pre-provision topology outside the transport (deployment pipelines, RabbitMQ Kubernetes messaging topology operator manifests, infrastructure-as-code) with installers disabled and least-privilege runtime credentials (no policymaker). Anyone following the RabbitMQ documentation will naturally declare their queues with x-delivery-limit: -1 and then find that no endpoint can start. Because quorum queue arguments are immutable, recovering requires deleting and recreating every affected queue — the misconfiguration cannot be corrected in place, so this is expensive to discover after provisioning.
Expected behavior
Either:
On brokers >= 4.0, a quorum queue whose effective delivery limit is reported as unlimited (-1) passes validation regardless of whether the value came from a transport policy or a queue argument; or
The rejection is a deliberate design decision (see "Additional information" for a possible rationale around 3.x behavior), in which case it should be documented on the delivery-limit validation docs page, and the exception message should explain the actual remediation.
Actual behavior
Endpoint startup fails with:
System.InvalidOperationException: The delivery limit for '{queue}' is set to the non-default value of '-1'. Remove any delivery limit settings from queue arguments, user policies or operator policies to correct this.
The message is misleading in this case: -1 is not a limiting value, and "remove any delivery limit settings from queue arguments" is not actionable for quorum queues, whose arguments are immutable after declaration — the queue must be deleted and recreated.
Versions
NServiceBus.RabbitMQ 10.1.8 (behavior introduced with delivery-limit validation in 10.0.0; unchanged on current master, so also affects 11.x/12.x)
RabbitMQ broker 4.1.x
Reproduced against rabbitmq:4.1-management
Steps to reproduce
Run a RabbitMQ 4.x broker: docker run -d --name rmq -p 5672:5672 -p 15672:15672 rabbitmq:4.1-management
Declare a quorum queue with an unlimited delivery limit, per the RabbitMQ documentation:
The management API now reports "delivery_limit": "unlimited" for this queue.
Start an NServiceBus endpoint named my-endpoint using NServiceBus.RabbitMQ 10.1.8 with QueueType.Quorum, installers disabled, and delivery limit validation enabled (the default).
Startup throws the InvalidOperationException above.
Relevant log output
System.InvalidOperationException: The delivery limit for'my-endpoint' is set to the non-default value of '-1'. Remove any delivery limit settings from queue arguments, user policies or operator policies to correct this.
at NServiceBus.Transport.RabbitMQ.BrokerVerifier.ShouldOverrideDeliveryLimit(Queue queue)
at NServiceBus.Transport.RabbitMQ.BrokerVerifier.ValidateDeliveryLimit(String queueName, CancellationToken cancellationToken)
at NServiceBus.Transport.RabbitMQ.MessagePump.Initialize(...)
Additional Information
Workarounds
Do not set x-delivery-limit at queue declaration; instead pre-create a policy with delivery-limit: 100000 (exactly Queue.BigValueInsteadOfActuallyUnlimited) matching the queue, which passes validation read-only. For already-declared queues this workaround is unavailable without recreating the queue, since the argument cannot be removed.
DoNotValidateDeliveryLimits() — undesirable, as it disables the protection entirely.
Possible solutions
Accept an effective delivery limit of -1/unlimited on brokers >= 4.0 irrespective of its source (queue argument or policy). On 4.x the management API reports the effective limit directly, so the transport does not need to reason about where it came from. This would arguably be more correct than the policy the transport creates itself, since RabbitMQ documents the queue argument as the only guaranteed unlimited mechanism (RabbitMQ may not respect the unlimited retry policy created by the transport #1688).
If arg-based -1 should remain rejected because of RabbitMQ 3.x semantics (see below), gate acceptance on BrokerVersion >= 4.0.0 (already available in BrokerVerifier), and improve the exception message for the -1 case to state that quorum queue arguments are immutable and the queue must be recreated (or the limit overridden by policy).
Independent of the fix: document the constraint on the delivery-limit validation page, which currently doesn't mention that x-delivery-limit: -1 fails validation.
The limit == -1 && IsTransportPolicy(...) branch shows the validation already accepts -1 as a valid effective limit when the transport's own (pre-10.1.6) policy produced it, so the semantics of -1 are not in question — only the source. The queue-argument source appears not to have been considered when the guard was written; the PR #1689 discussion does not mention it, and there is no test covering x-delivery-limit: -1 (the existing tests only cover finite values, https://github.com/Particular/NServiceBus.RabbitMQ/blob/10.1.8/src/NServiceBus.Transport.RabbitMQ.Tests/BrokerVerifierTests.cs#L234-L276).
Describe the bug
Description
The delivery limit validation introduced for RabbitMQ 4.0 fails endpoint startup when the input queue was declared with the
x-delivery-limit: -1queue argument — even though this is the method the RabbitMQ documentation describes as the only guaranteed way to get a truly unlimited delivery limit (see the RabbitMQ docs change referenced in #1688: "The value of-1, which disables the limit altogether, cannot be currently set through a policy - this can only be done withx-delivery-limit=-1queue argument during queue declaration.").On RabbitMQ 4.x, a quorum queue declared with
x-delivery-limit: -1is reported by the management API with"delivery_limit": "unlimited", which the transport parses as-1.BrokerVerifier.ShouldOverrideDeliveryLimitthen:BigValueInsteadOfActuallyUnlimited(100000) early exit,limit == -1 && IsTransportPolicy(...)branch (the value comes from a queue argument, not a transport-created policy), andhttps://github.com/Particular/NServiceBus.RabbitMQ/blob/10.1.8/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs#L166-L191
The result is that a queue configured to have exactly the semantics the validation is trying to guarantee (messages are never dropped by the broker due to repeated redeliveries) is rejected as misconfigured.
This particularly affects users who pre-provision topology outside the transport (deployment pipelines, RabbitMQ Kubernetes messaging topology operator manifests, infrastructure-as-code) with installers disabled and least-privilege runtime credentials (no
policymaker). Anyone following the RabbitMQ documentation will naturally declare their queues withx-delivery-limit: -1and then find that no endpoint can start. Because quorum queue arguments are immutable, recovering requires deleting and recreating every affected queue — the misconfiguration cannot be corrected in place, so this is expensive to discover after provisioning.Expected behavior
Either:
unlimited(-1) passes validation regardless of whether the value came from a transport policy or a queue argument; orActual behavior
Endpoint startup fails with:
The message is misleading in this case:
-1is not a limiting value, and "remove any delivery limit settings from queue arguments" is not actionable for quorum queues, whose arguments are immutable after declaration — the queue must be deleted and recreated.Versions
master, so also affects 11.x/12.x)rabbitmq:4.1-managementSteps to reproduce
docker run -d --name rmq -p 5672:5672 -p 15672:15672 rabbitmq:4.1-management"delivery_limit": "unlimited"for this queue.my-endpointusing NServiceBus.RabbitMQ 10.1.8 withQueueType.Quorum, installers disabled, and delivery limit validation enabled (the default).InvalidOperationExceptionabove.Relevant log output
Additional Information
Workarounds
x-delivery-limitat queue declaration; instead pre-create a policy withdelivery-limit: 100000(exactlyQueue.BigValueInsteadOfActuallyUnlimited) matching the queue, which passes validation read-only. For already-declared queues this workaround is unavailable without recreating the queue, since the argument cannot be removed.DoNotValidateDeliveryLimits()— undesirable, as it disables the protection entirely.Possible solutions
-1/unlimitedon brokers >= 4.0 irrespective of its source (queue argument or policy). On 4.x the management API reports the effective limit directly, so the transport does not need to reason about where it came from. This would arguably be more correct than the policy the transport creates itself, since RabbitMQ documents the queue argument as the only guaranteed unlimited mechanism (RabbitMQ may not respect the unlimited retry policy created by the transport #1688).-1should remain rejected because of RabbitMQ 3.x semantics (see below), gate acceptance onBrokerVersion >= 4.0.0(already available inBrokerVerifier), and improve the exception message for the-1case to state that quorum queue arguments are immutable and the queue must be recreated (or the limit overridden by policy).x-delivery-limit: -1fails validation.Additional information
A possible rationale for distrusting arg-based
-1: on RabbitMQ 3.x a negativex-delivery-limitis treated as0by the broker (noted inQueue.GetDeliveryLimit, https://github.com/Particular/NServiceBus.RabbitMQ/blob/10.1.8/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementApi/Models/Queue.cs#L37-L73), so the same declaration is "unlimited" on 4.x but "discard after first delivery" on 3.x. That justifies rejecting it on 3.x brokers, but not on 4.x where the broker reports the effective limit asunlimited.The
limit == -1 && IsTransportPolicy(...)branch shows the validation already accepts-1as a valid effective limit when the transport's own (pre-10.1.6) policy produced it, so the semantics of-1are not in question — only the source. The queue-argument source appears not to have been considered when the guard was written; the PR #1689 discussion does not mention it, and there is no test coveringx-delivery-limit: -1(the existing tests only cover finite values, https://github.com/Particular/NServiceBus.RabbitMQ/blob/10.1.8/src/NServiceBus.Transport.RabbitMQ.Tests/BrokerVerifierTests.cs#L234-L276).Related: