Repository navigation
SendInline() on a shared endpoint silently overwrites the listener's EndpointMode #4059
Description
Summary
On any transport where publishing and listening resolve to the same Endpoint object — a RabbitMQ queue, a Redis stream, a Pulsar topic — calling SendInline() on the publishing side silently overwrites the listening side's EndpointMode. No warning, no error; the listener just quietly runs in a different mode than it was configured for.
opts.ListenToRabbitQueue("orders")
.ProcessInParallelWithNativeAcks() // asks for native acks + parallel lanes
.PartitionProcessingByGroupId(PartitionSlots.Five);
opts.PublishMessage<Order>()
.ToRabbitQueue("orders")
.SendInline(); // silently resets Mode to InlineMechanism
Mode is a single property on Endpoint governing both directions, and the sending-side fluent methods assign it unconditionally (src/Wolverine/Configuration/SubscriberConfiguration.cs):
public T SendInline()
{
add(e => e.Mode = EndpointMode.Inline);
return this.As<T>();
}UseDurableOutbox() and BufferedInMemory() on the same class do the same thing (:95, :101). Because these are delayed configuration, the outcome depends on which fluent block Wolverine applies last — so the same two calls in the other order produce different endpoint state. That is the identical order-dependence class GH-3712 fixed for ProcessInline() + MaximumParallelMessages().
Why this is worth fixing now
It was hit independently, by two separate people, within an hour, while implementing #4046 (Redis Streams) and #4047 (Pulsar) — in both cases costing a debugging round on a test that failed for a reason nowhere near where it looked. It is pre-existing and not specific to EndpointMode.NativeAck.
But NativeAck raises the stakes. Previously the silent overwrite downgraded throughput. Now it can silently discard a processing guarantee: ProcessInParallelWithNativeAcks() + PartitionProcessingByGroupId() reset to Inline means partitioned processing is gone — and GH-3712's validator, which exists precisely to reject Inline + partitioning, sees the endpoint after the overwrite and cannot tell "the user asked for Inline" from "the user's listener mode was clobbered by a publish rule".
Suggested direction
The pieces to decide between:
- Warn or throw when a sending-side mode assignment changes the mode of an endpoint that is also a listener — the cheapest fix, and it makes the collision observable rather than silent.
- Separate the concerns. The mode genuinely means different things per direction (
buildSendingAgentvsbuildReceiverAsyncswitch on the same value). A distinctSendingModewould remove the collision at the root, at the cost of a larger change and a migration story. - Whatever is chosen, apply it to
UseDurableOutbox()andBufferedInMemory()too —SendInline()is just the loudest instance.
Note that option 1's check belongs in ListenerConfigurationValidator, not in the Mode setter: both sides are delayed configuration, so a setter-based guard catches one ordering and misses the reverse. Same reasoning as GH-3712, and the same place #4049 wants the ASB-sessions rejection to live.
Activity
jeremydmiller commentedon Aug 24, 2026
Deferred — not for this release. Confirmed with Jeremy: this is a long-standing, documented limitation of
Modebeing a single property across both directions, not a regression, and it is not being changed in the 6.30 line.Leaving the issue open as a record of the mechanism and of the two independent hits during #4046/#4047, since the analysis is worth keeping. Nobody should pick this up as part of the NativeAck wave.
JasperFxGH-4059: reject conflicting sending modes on shared listeners
JasperFxGH-4059: reject conflicting sending modes on shared listeners
JasperFxGH-4059: keep the two mode-sensitive tests Inline instead of …
GH-4059: say so when a publish rule overwrites a listener's EndpointM…
GH-4059: fix the conflicting sending/listening modes in the transport…