Deferred from the moscow:must sweep that closed #374 (PR on branch bugfix/asb-small-musts-375-377-374). Same root cause, outside that sweep's fixed membership, so it is filed here with full scope rather than absorbed.
Claim
SessionReceiverMultiplexer.DeliveringChildFor throws InvalidOperationException when it cannot route a settlement to a child receiver. After #374, every other path in the module reports the same class of deterministic absence as a failed settlement, not as a thrown exception.
The module now holds two different answers for one class of condition.
Why it matters
The distinction is not cosmetic. A settlement that throws re-enters Recovery (src/Chatter.MessageBrokers/src/Chatter.MessageBrokers/Receiving/BrokeredMessageReceiver.cs, the TrySettleWithRecoveryAsync catch path), so the pipeline retries a condition that is deterministic and will never succeed on retry. A settlement that reports Failed is treated as terminal and logged, which is what the module's own PeekLock settlement doctrine calls for.
Evidence
src/Chatter.MessageBrokers.AzureServiceBus/src/Chatter.MessageBrokers.AzureServiceBus/Receiving/SessionReceiverMultiplexer.cs — DeliveringChildFor throws for an unroutable delivery
src/Chatter.MessageBrokers.AzureServiceBus/CONTEXT.md — PeekLock Settlement: a settlement that cannot reach its delivery reports Failed
src/Chatter.MessageBrokers/src/Chatter.MessageBrokers/Receiving/BrokeredMessageReceiver.cs — a thrown settlement re-enters Recovery
Bounded impact
Bounded to session receivers running at MaxConcurrentCalls > 1; single-session hosts never reach the multiplexer routing path. The consequence is wasted retry work and a misleading exception rather than message loss.
Proposed direction
Have the multiplexer report DeliveryUnreachable through the port it already forwards, so ServiceBusReceiver maps it to a failed settlement on the same path as every other unreachable delivery. Note that tests/Receiving/UsingSessionReceiverMultiplexer/WhenMultiplexingSessions.cs currently pins the throw and would need to move with the behaviour.
Parent epic: #307
Deferred from the
moscow:mustsweep that closed #374 (PR on branchbugfix/asb-small-musts-375-377-374). Same root cause, outside that sweep's fixed membership, so it is filed here with full scope rather than absorbed.Claim
SessionReceiverMultiplexer.DeliveringChildForthrowsInvalidOperationExceptionwhen it cannot route a settlement to a child receiver. After #374, every other path in the module reports the same class of deterministic absence as a failed settlement, not as a thrown exception.The module now holds two different answers for one class of condition.
Why it matters
The distinction is not cosmetic. A settlement that throws re-enters Recovery (
src/Chatter.MessageBrokers/src/Chatter.MessageBrokers/Receiving/BrokeredMessageReceiver.cs, theTrySettleWithRecoveryAsynccatch path), so the pipeline retries a condition that is deterministic and will never succeed on retry. A settlement that reportsFailedis treated as terminal and logged, which is what the module's own PeekLock settlement doctrine calls for.Evidence
src/Chatter.MessageBrokers.AzureServiceBus/src/Chatter.MessageBrokers.AzureServiceBus/Receiving/SessionReceiverMultiplexer.cs—DeliveringChildForthrows for an unroutable deliverysrc/Chatter.MessageBrokers.AzureServiceBus/CONTEXT.md— PeekLock Settlement: a settlement that cannot reach its delivery reportsFailedsrc/Chatter.MessageBrokers/src/Chatter.MessageBrokers/Receiving/BrokeredMessageReceiver.cs— a thrown settlement re-enters RecoveryBounded impact
Bounded to session receivers running at
MaxConcurrentCalls > 1; single-session hosts never reach the multiplexer routing path. The consequence is wasted retry work and a misleading exception rather than message loss.Proposed direction
Have the multiplexer report
DeliveryUnreachablethrough the port it already forwards, soServiceBusReceivermaps it to a failed settlement on the same path as every other unreachable delivery. Note thattests/Receiving/UsingSessionReceiverMultiplexer/WhenMultiplexingSessions.cscurrently pins the throw and would need to move with the behaviour.Parent epic: #307