GH-3533: Pin Azure Service Bus session listeners to specific session identifiers - #3534
Merged
Merged
Conversation
…identifiers Add an opt-in ServiceBusSessionProcessor-based session listener so competing consumers on ONE shared queue/subscription can each be pinned to their own session id(s). On a shared entity the session id becomes a broker-enforced routing key, so a listener pinned to "A" never sees the messages meant for "B". - ConfigureSessionProcessor(Action<ServiceBusSessionProcessorOptions>) on both queue and subscription listener configs (multicast, so it composes) - RequireSessionsWithOnlyTheseIdentifiers(params string[]) sugar that populates ServiceBusSessionProcessorOptions.SessionIds - BuildSessionProcessorOptions mirrors BuildProcessorOptions; reserves ReceiveMode=PeekLock and AutoCompleteMessages=false - New InlineAzureServiceBusSessionListener with full dead-letter / native scheduling / connection-state parity; AzureServiceBusEnvelope gains a ProcessSessionMessageEventArgs complete/defer/dead-letter path Gated: the legacy AcceptNextSession loop stays the default and only flips to the processor when ConfigureSessionProcessor is set, so existing session listeners are unaffected. Unit tests + an emulator end-to-end test reproducing the issue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKAxzuZ36VP6UPcTQf3MUs
This was referenced Jul 20, 2026
erdtsieck
pushed a commit
to erdtsieck/wolverine
that referenced
this pull request
Aug 3, 2026
…asperFxGH-3763) 19 [Trait("Category", "Flaky")] tags hid 65 of the 304 tests in this project -- 21% of the suite never ran. Not one of them recorded a reason, a number, or an issue link, so there was no way to tell from the code whether any had ever been real. git log -L on each tag line says most were never individually judged: 15 of the 19 went in on 2026-03-20/21 across four commits with messages like "Tag all ConventionalRouting tests as Flaky" and "tag flaky AWS/Azure SB tests". That is a bulk sweep, not 15 investigations. The other 4 were added later alongside real PRs (JasperFx#2588, JasperFx#3103, JasperFx#3534) and are left tagged here to be judged one at a time. There is now a specific reason to think the sweep was measuring infrastructure rather than tests. JasperFxGH-3781 follow-up found the emulator readiness gate was TCP-connecting to the AMQP port while provisioning goes to the management port, which answers 503 for ~26 seconds after the socket opens -- so any class doing provisioning near the start of a run could fail for reasons that had nothing to do with it. Nine of the fifteen are ConventionalRouting discovery tests, which are exactly that shape. This commit only removes the tags. Whether they stay off is decided by what CI says, per class, by name -- the RabbitMQ pass in JasperFx#3780 nearly untagged a class that fails 2 of 20 every run because it was verified as part of a set rather than individually. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0116vfBcKwcjWn8msM4ZjkuA
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 #3533
Thanks @tudor-tesss for the well-scoped request — the mechanism you described (session id as a broker-enforced routing key) is exactly what this implements.
Problem
There was no way to have several competing consumers share a single Azure Service Bus queue/subscription while each only ever processes messages for a fixed set of session ids. The hand-rolled session listener always calls
AcceptNextSessionAsync(), so a consumer locks whatever session is next available — the cross-delivery problem from the issue.Solution
Add an opt-in
ServiceBusSessionProcessor-based session listener and exposeServiceBusSessionProcessorOptionsto users. PopulatingSessionIdspins the processor so it only ever locks those sessions; on a shared entity that makes the session id a broker-enforced routing key.API
The general hook for the other knobs:
Both methods are available on queue and subscription listener configs.
What's in the box
ConfigureSessionProcessoronAzureServiceBusEndpoint— a multicast delegate (unlike inlineConfigureProcessor) so theSessionIdssugar and explicit customizations compose.BuildSessionProcessorOptionsmirrorsBuildProcessorOptions: mapsListenerCount→MaxConcurrentSessions, keeps in-session FIFO (MaxConcurrentCallsPerSession = 1), and re-asserts the acknowledgement-criticalReceiveMode = PeekLock/AutoCompleteMessages = false.InlineAzureServiceBusSessionListener(built onServiceBusSessionProcessor) with fullISupportDeadLetterQueue/ISupportNativeScheduling/IReportConnectionStateparity.AzureServiceBusEnvelopegains aProcessSessionMessageEventArgscomplete/defer/dead-letter path (it does not share a base type withProcessMessageEventArgs).Safety / gating
The existing hand-rolled
AcceptNextSessionloop remains the default. The processor engine is only used whenConfigureSessionProcessoris set (directly or viaRequireSessionsWithOnlyTheseIdentifiers). Existing session listeners are completely unaffected — this is purely additive. Wolverine reservesReceiveModeandAutoCompleteMessages.Tests
BuildSessionProcessorOptionsand both fluent methods (SessionIds population,ListenerCount→MaxConcurrentSessions, PeekLock/AutoComplete re-assertion, multicast composition, and that a plainRequireSessions()still uses the legacy loop)."A"receives exactly its messages, while a"B"message seeded onto the same shared queue is never delivered to it.Docs added to the Azure Service Bus session-identifiers page +
DocumentationSamples.🤖 Generated with Claude Code
https://claude.ai/code/session_01FKAxzuZ36VP6UPcTQf3MUs