GH-3712: validate or warn on silently-ignored listener configuration combos - #4017
Merged
Merged
Conversation
…nores Several listener configuration combinations were accepted in silence and then did nothing at runtime. The worst of them reads as a guarantee: `.ProcessInline().PartitionProcessingByGroupId(...)` promises that messages sharing a group id never run concurrently, and delivered no such thing -- `GroupShardingSlotNumber` is only read by BufferedReceiver and DurableReceiver, and InlineReceiver ignores it outright. * A bootstrap pass over every listening endpoint (after compilation, so endpoint policies and delayed configuration have settled the mode) now throws `InvalidListenerConfigurationException` for Inline + partitioned processing, and logs a warning for Inline + an explicit parallelism or BufferingLimits. * Killed the `ProcessInline()` ordering dependence. The eager `MaxDegreeOfParallelism = 1` clamp meant `.MaximumParallelMessages(20).ProcessInline()` and `.ProcessInline().MaximumParallelMessages(20)` left the endpoint in different states for the same two calls; `Endpoint.Compile()` now normalizes it once, after all configuration has been applied. * Diagnostics (`wolverine describe`, `wolverine diagnose`) print `n/a (Inline)` for parallelism rather than a number the endpoint never reads. * Documented the per-mode settings matrix in the listener docs, including the RabbitMQ sharded-topology case -- RabbitMQ queues are Inline by default, so a partitioned topology there needs `ConfigureListening(x => x.BufferedInMemory())`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3712.
The problem
Listener settings that a given
EndpointModesimply does not read were accepted in silence. The worst of them reads as a guarantee:GroupShardingSlotNumberis only read byBufferedReceiverandDurableReceiver;InlineReceiverignores it. So that configuration started cleanly, logged nothing, and gave the user no group-id ordering whatsoever.ProcessInline()also carried an order-dependent clamp:.MaximumParallelMessages(20).ProcessInline()ended atMaxDegreeOfParallelism == 1, while.ProcessInline().MaximumParallelMessages(20)ended at20— a value nothing reads, but which thewolverine describelistener table displayed as if it were live.What changed
A bootstrap validation pass (
ListenerConfigurationValidator, called fromstartMessagingTransportsAsyncbefore listeners start). It compiles every listening endpoint first — endpoint policies and delayed configuration are what settle the final mode — then checks settings against that mode:Inline+PartitionProcessingByGroupId()InvalidListenerConfigurationExceptionInline+ explicit parallelism (MaximumParallelMessages,Sequential,ExclusiveNodeWithParallelism, …)Inline+ customizedBufferingLimitsBackPressureAgent)Partitioning throws rather than warns because it is the only one of the three that is a guarantee — silently not honoring it surfaces as intermittent concurrency collisions under load, not as an obvious failure. The other two are inert but harmless, so existing apps carrying a stale value keep starting.
The order dependence is gone.
ProcessInline()no longer clamps eagerly;Endpoint.Compile()normalizesMaxDegreeOfParallelismto 1 for every Inline endpoint once all configuration has been applied. Both call orders now leave identical endpoint state, andEndpoint.DiscardedMaxDegreeOfParallelismrecords what the user actually asked for so the warning can name it.Diagnostics stop printing dead numbers.
wolverine describeandwolverine diagnoserendern/a (Inline)for parallelism instead of a value the endpoint never reads.Docs.
docs/guide/messaging/listeners.mdgains a "which settings apply in which mode" matrix, plus xml-doc fixes onExclusiveNodeWithParallelism,MaximumParallelMessages,Sequential,PartitionProcessingByGroupId, andProcessInline.RabbitMQ queues default to
Inline, andPublishToShardedRabbitQueues()sets the group-id slots on its listeners. A topology that does not also callConfigureListening(x => x.BufferedInMemory())(orUseDurableInbox()) will now fail to start instead of starting cleanly and not partitioning anything. Every sample and test in this repo already does; the docs now say so explicitly. Silently upgrading those listeners toBufferedwas considered and rejected — it would trade one silent surprise for a worse one, since Buffered changes the delivery guarantee.Tests
src/Testing/CoreTests/Configuration/listener_configuration_validation.cs— 11 tests covering each combination's severity and message, bothProcessInline()/MaximumParallelMessages()call orders converging, then/arendering, a real host refusing to start, and the false-positive guards (Buffered/Durable + partitioning still valid; a send-only endpoint is not validated as a listener).Verification
dotnet build wolverine.slnx -c Release -f net9.0— clean, 0 warningsFollow-up spotted, not fixed here
A local queue set to
Inlineis accepted at configuration time and then throws a bare, message-lessNotSupportedExceptionfromLocalQueue.BuildAgentthe first time anything sends to it (src/Wolverine/Transports/Local/LocalQueue.cs:70). Same family of problem, but outside this issue's scope.🤖 Generated with Claude Code