BrighterCommand / BrighterCommand/Brighter

ASB: queue creation ignores SubscriptionConfiguration, so OnMissingChannel.Create cannot make a session-enabled queue

Open
#4,269 0 comments 0 reactions 1 assignee Claimed by @DevJonny View on GitHub
.NET 0 - Backlog Bug V10.X
Dominant language
C#
Stars
2.5k
Forks
296
Avg merge
1d 11h
Merged PRs (30d)
21

Description

## Summary

On the Azure Service Bus transport, the **queue** creation path ignores `AzureServiceBusSubscriptionConfiguration` almost entirely, while the **topic subscription** path honours it. The practical effect is that `OnMissingChannel.Create` cannot produce a session-enabled queue, or one with a non-default lock duration, TTL, or dead-lettering-on-expiration — even when the subscription explicitly asks for them.

This matters more than a normal missing-option gap because `RequiresSession` is **immutable after queue creation** in Azure Service Bus. If Brighter auto-creates the queue without it, the only remedy is deleting and recreating the entity.

## Current behaviour

`AdministrationClientWrapper.CreateQueueAsync` takes three parameters and sets two properties:

```csharp
// AzureServiceBusWrappers/AdministrationClientWrapper.cs:69
public async Task CreateQueueAsync(string queueName, TimeSpan? autoDeleteOnIdle = null, long? maxMessageSizeInKilobytes = default)
{
...
await _administrationClient.CreateQueueAsync(new CreateQueueOptions(queueName)
{
AutoDeleteOnIdle = autoDeleteOnIdle ?? TimeSpan.MaxValue,
MaxMessageSizeInKilobytes = maxMessageSizeInKilobytes
});
```

Compare the subscription path in the same class, which threads the configuration through in full:

```csharp
// AzureServiceBusWrappers/AdministrationClientWrapper.cs:106-113
var subscriptionOptions = new CreateSubscriptionOptions(topicName, subscriptionName)
{
MaxDeliveryCount = subscriptionConfiguration.MaxDeliveryCount,
DeadLetteringOnMessageExpiration = subscriptionConfiguration.DeadLetteringOnMessageExpiration,
LockDuration = subscriptionConfiguration.LockDuration,
DefaultMessageTimeToLive = subscriptionConfiguration.DefaultMessageTimeToLive,
AutoDeleteOnIdle = subscriptionConfiguration.QueueIdleBeforeDelete,
RequiresSession = subscriptionConfiguration.RequireSession
};
```

The consumer already holds the configuration it would need, and passes only the idle timeout:

```csharp
// AzureServiceBusQueueConsumer.cs:107
await AdministrationClientWrapper.CreateQueueAsync(Topic, SubscriptionConfiguration.QueueIdleBeforeDelete);
```

So a subscription declaring `RequireSession = true` with `UseServiceBusQueue = true` gets a consumer that attempts session receive against a queue Brighter just created **without** sessions. A `SessionId` stamped on a message bound for a non-session queue is silently ignored by the broker — no error, no ordering, and nothing observable until you notice messages aren't being processed in the order you asked for.

## Impact

- `RequireSession` is unreachable via `OnMissingChannel.Create` for queues, and unfixable afterwards without deleting the entity.
- `LockDuration`, `DefaultMessageTimeToLive` and `DeadLetteringOnMessageExpiration` are all settable on `AzureServiceBusSubscriptionConfiguration` and all silently dropped for queues, so auto-created queues get broker defaults regardless of what the subscription declares.
- Consumers wanting an ordered, session-keyed queue have to provision the entity out-of-band (IaC, or a bespoke startup hosted service holding a `ServiceBusAdministrationClient`) purely to set one immutable boolean.

## Proposed change

Thread `AzureServiceBusSubscriptionConfiguration` through to `CreateQueueOptions` on the queue path, mirroring the subscription path. No new configuration surface is needed — `RequireSession`, `LockDuration`, `DefaultMessageTimeToLive` and `DeadLetteringOnMessageExpiration` already exist on that type.

## One wrinkle worth deciding on

There are two callers of `CreateQueueAsync`, and only one of them has a `SubscriptionConfiguration`:

```csharp
// AzureServiceBusQueueMessageProducer.cs:80
await _administrationClientWrapper.CreateQueueAsync(channelName);
```

A producer has no subscription configuration, so for a queue used by both a producer and a consumer in the same application, whichever starts first creates the entity — and if that's the producer, the queue is created with defaults and `RequiresSession` is then permanently wrong. Options that seem plausible from the outside:

- Have the producer's create path be `Validate`-like for session-requiring queues, so the consumer's configured create wins.
- Carry the relevant queue settings on the publication as well, so both callers can create an equivalent entity.
- Leave the producer path as-is and document that a session-enabled queue must be created by the consumer or out-of-band.

Happy to take direction on which of those you'd prefer before opening a PR — I'm glad to do the work either way.

## Version

Reproduced against `Paramore.Brighter.MessagingGateway.AzureServiceBus` **10.7.0**; the signature is unchanged on `master` at time of writing:

```
M:Paramore.Brighter.MessagingGateway.AzureServiceBus.AzureServiceBusWrappers.IAdministrationClientWrapper.CreateQueueAsync(System.String,System.Nullable{System.TimeSpan},System.Nullable{System.Int64})
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.