Found while implementing #374 during the moscow:must sweep (branch bugfix/asb-small-musts-375-377-374). Same family as #374 and #487, outside that sweep's fixed membership, so it is filed here with full scope rather than absorbed.
Claim
SessionReceiverMultiplexer.ReplaceDisposedChildAsync swaps slot.Child after an ObjectDisposedException, but leaves _slotsByDeliveredMessage pointing at that slot and leaves slot.BusyMessage set.
A settlement for a delivery that was still outstanding at the moment of replacement therefore routes to the replacement child — which holds a different session, or none at all.
Consequences
Evidence
src/Chatter.MessageBrokers.AzureServiceBus/src/Chatter.MessageBrokers.AzureServiceBus/Receiving/SessionReceiverMultiplexer.cs — ReplaceDisposedChildAsync replaces the child without clearing _slotsByDeliveredMessage or slot.BusyMessage
src/Chatter.MessageBrokers.AzureServiceBus/CONTEXT.md — PeekLock Settlement doctrine
Bounded impact
Requires a child receiver to be disposed while a delivery from it is still outstanding — a teardown or reconnect race on a session receiver at MaxConcurrentCalls > 1. Not reachable on the single-session path. Consequence is a misrouted settlement surfacing as a retried fault, not silent message loss.
Proposed direction
On replacement, retire the outstanding delivery mapping along with the child: clear the _slotsByDeliveredMessage entries that point at the slot and reset slot.BusyMessage, so a settlement for a delivery the multiplexer can no longer honour reports an unreachable delivery instead of routing to an unrelated session. Pairs naturally with #487, which decides how an unroutable delivery is reported in the first place.
Parent epic: #307
Found while implementing #374 during the
moscow:mustsweep (branchbugfix/asb-small-musts-375-377-374). Same family as #374 and #487, outside that sweep's fixed membership, so it is filed here with full scope rather than absorbed.Claim
SessionReceiverMultiplexer.ReplaceDisposedChildAsyncswapsslot.Childafter anObjectDisposedException, but leaves_slotsByDeliveredMessagepointing at that slot and leavesslot.BusyMessageset.A settlement for a delivery that was still outstanding at the moment of replacement therefore routes to the replacement child — which holds a different session, or none at all.
Consequences
DeliveryUnreachable(after Session settlement silently no-ops but reports Settled when the held session is gone #374) and is reported as a failed settlement. Acceptable, though the diagnostic points at the wrong cause.CompleteMessageAsync/DeadLetterMessageAsyncon a session that never delivered the message, and the SDK throws. A deterministic absence then re-enters Recovery as a retryable fault — the same wrong-shape outcome described in SessionReceiverMultiplexer throws for an unroutable delivery where the module now reports a failed settlement #487.Evidence
src/Chatter.MessageBrokers.AzureServiceBus/src/Chatter.MessageBrokers.AzureServiceBus/Receiving/SessionReceiverMultiplexer.cs—ReplaceDisposedChildAsyncreplaces the child without clearing_slotsByDeliveredMessageorslot.BusyMessagesrc/Chatter.MessageBrokers.AzureServiceBus/CONTEXT.md— PeekLock Settlement doctrineBounded impact
Requires a child receiver to be disposed while a delivery from it is still outstanding — a teardown or reconnect race on a session receiver at
MaxConcurrentCalls > 1. Not reachable on the single-session path. Consequence is a misrouted settlement surfacing as a retried fault, not silent message loss.Proposed direction
On replacement, retire the outstanding delivery mapping along with the child: clear the
_slotsByDeliveredMessageentries that point at the slot and resetslot.BusyMessage, so a settlement for a delivery the multiplexer can no longer honour reports an unreachable delivery instead of routing to an unrelated session. Pairs naturally with #487, which decides how an unroutable delivery is reported in the first place.Parent epic: #307