Skip to content

GH-3712: validate or warn on silently-ignored listener configuration combos - #4017

Merged
jeremydmiller merged 1 commit into
mainfrom
gh-3712/listener-config-validation
Aug 23, 2026
Merged

GH-3712: validate or warn on silently-ignored listener configuration combos#4017
jeremydmiller merged 1 commit into
mainfrom
gh-3712/listener-config-validation

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #3712.

The problem

Listener settings that a given EndpointMode simply does not read were accepted in silence. The worst of them reads as a guarantee:

opts.ListenToRabbitQueue("orders")
    .ProcessInline()
    .PartitionProcessingByGroupId(PartitionSlots.Five);   // did nothing at all

GroupShardingSlotNumber is only read by BufferedReceiver and DurableReceiver; InlineReceiver ignores 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 at MaxDegreeOfParallelism == 1, while .ProcessInline().MaximumParallelMessages(20) ended at 20 — a value nothing reads, but which the wolverine describe listener table displayed as if it were live.

What changed

A bootstrap validation pass (ListenerConfigurationValidator, called from startMessagingTransportsAsync before 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:

Combination Result
Inline + PartitionProcessingByGroupId() throws InvalidListenerConfigurationException
Inline + explicit parallelism (MaximumParallelMessages, Sequential, ExclusiveNodeWithParallelism, …) warning, value normalized to 1
Inline + customized BufferingLimits warning (an Inline endpoint never starts a BackPressureAgent)

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() normalizes MaxDegreeOfParallelism to 1 for every Inline endpoint once all configuration has been applied. Both call orders now leave identical endpoint state, and Endpoint.DiscardedMaxDegreeOfParallelism records what the user actually asked for so the warning can name it.

Diagnostics stop printing dead numbers. wolverine describe and wolverine diagnose render n/a (Inline) for parallelism instead of a value the endpoint never reads.

Docs. docs/guide/messaging/listeners.md gains a "which settings apply in which mode" matrix, plus xml-doc fixes on ExclusiveNodeWithParallelism, MaximumParallelMessages, Sequential, PartitionProcessingByGroupId, and ProcessInline.

⚠️ Note for RabbitMQ users of partitioned topologies

RabbitMQ queues default to Inline, and PublishToShardedRabbitQueues() sets the group-id slots on its listeners. A topology that does not also call ConfigureListening(x => x.BufferedInMemory()) (or UseDurableInbox()) 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 to Buffered was 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, both ProcessInline()/MaximumParallelMessages() call orders converging, the n/a rendering, 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 warnings
  • CoreTests: 2498 total, 0 failed
  • PolicyTests, MessageRoutingTests, BackPressureTests: green

Follow-up spotted, not fixed here

A local queue set to Inline is accepted at configuration time and then throws a bare, message-less NotSupportedException from LocalQueue.BuildAgent the 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

…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validate or warn on silently-ignored listener configuration combos (Inline + partitioning/parallelism)

1 participant