Skip to content

Use inline senders on the tenanted Azure Service Bus path (GH-3826) - #3833

Merged
jeremydmiller merged 1 commit into
mainfrom
gh-3826/asb-tenanted-inline-senders
Aug 5, 2026
Merged

Use inline senders on the tenanted Azure Service Bus path (GH-3826)#3833
jeremydmiller merged 1 commit into
mainfrom
gh-3826/asb-tenanted-inline-senders

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #3826.

Not a routing bug

The issue reported a message published without a tenant id being Sent and never Received, and concluded it was "real multi-tenant conventional-routing behaviour". The route was correct and the listener was Accepting the whole time — the send failed.

TenantedSender deliberately does not implement ISenderRequiresCallback, so that SendingAgent picks sendWithExplicitHandlingAsync for the fire-and-forget senders beneath it (GH-2361). But EndpointCollection.CreateSendingAgent only calls RegisterCallback when the top-level sender implements that interface, and it does not recurse:

if (sender is ISenderRequiresCallback senderRequiringCallback && agent is ISenderCallback callbackAgent)
{
    senderRequiringCallback.RegisterCallback(callbackAgent);
}

Azure Service Bus put a BatchedSender under the TenantedSender, so _callback stayed null and every batch threw:

System.InvalidOperationException: This sender has not been registered.
   at Wolverine.Transports.Sending.BatchedSender.SendBatchAsync(...) BatchedSender.cs:line 124

So a tenanted ASB endpoint could not send anything — tenanted or untenanted.

The fix follows existing precedent

Redis, MQTT and Pub/Sub each already hit this and worked around it the same way, every one with a comment naming GH-2361 — e.g. RedisStreamEndpoint:

Both the tenant senders and the default fallback MUST be the fire-and-forget InlineRedisStreamSenderTenantedSender does not forward RegisterCallback, so a BatchedSender under it would silently drop every message (GH-2361).

ASB was simply never given the same treatment. This does that, rather than rework the callback plumbing across four transports. InlineAzureServiceBusSender awaits the real broker send, so the outbox marks success only after the broker has the message.

Also: a test bug worth 2 minutes

The test set the tenant's ManagementConnectionString to the AMQP endpoint (5673) instead of the management endpoint (5300), so every tenant provisioning call hung to its own timeout. That is where the issue's 4m28s came from. Class drops 5m33s → 2m29s.

Verification

  • Red baseline confirmed: both new tests fail without the product change, pass with it.
  • Bug_1933 2/2, untagged from Category=Flaky.
  • New tenanted_senders_are_inline_3826 asserts the sender shape and that an untenanted publish round-trips.
  • Full ASB suite 315/315 green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WHAuhdWS3XeAk16swV9G8m

TenantedSender deliberately does not implement ISenderRequiresCallback so that
SendingAgent picks sendWithExplicitHandlingAsync for the fire-and-forget senders
underneath it (GH-2361). But EndpointCollection.CreateSendingAgent only calls
RegisterCallback when the *top-level* sender implements that interface, and it
does not recurse. A BatchedSender underneath a TenantedSender therefore keeps
_callback == null and throws

    InvalidOperationException: This sender has not been registered.

on every batch, so a tenanted Azure Service Bus endpoint could not send anything
at all -- including on the untenanted default pathway, which is what GH-3826
reported as "sent and never received".

Redis, MQTT and Pub/Sub already hit this and each worked around it by forcing
the inline sender on the tenanted path, with a comment naming GH-2361. Azure
Service Bus was never given the same treatment. Do that here rather than rework
the callback plumbing: BuildInlineSenderForQueue / BuildInlineSenderForTopic
already build exactly the right shape, and InlineAzureServiceBusSender awaits the
real broker send, so the outbox marks success only after the broker has it.

Also fixes the test's tenant management connection string, which pointed at the
AMQP endpoint (5673) rather than the management endpoint (5300) and made every
tenant provisioning call hang to its own timeout -- the source of the 4m28s the
issue described. The class drops from 5m33s to 2m29s.

DefaultSender and TenantSenders() are exposed on TenantedSender so the new
regression test can assert the shape without reflection.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHAuhdWS3XeAk16swV9G8m
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.

Azure Service Bus multi-tenant conventional routing: message published without a tenant id is sent and never received

1 participant