BrighterCommand / BrighterCommand/Brighter

Azure Service Bus ignores the delay on SendWithDelay — the message is delivered immediately

Open
#4,318 0 comments 0 reactions 1 assignee Claimed by @iancooper View on GitHub
Bug
Dominant language
C#
Stars
2.5k
Forks
296
Avg merge
1d 11h
Merged PRs (30d)
21

Description

## Summary

A message published through the Azure Service Bus gateway with a delay is delivered **immediately**. The
delay is not honoured.

## Evidence

`azure-ci` job [`101853410391`](https://github.com/BrighterCommand/Brighter/actions/runs/34157403107/job/101853410391)
(probe PR #4317, run against a real namespace). Both pumps of
`When_sending_a_delayed_message_should_deliver_after_delay` fail:

```
Assert.Equal() Failure: Values differ
Expected: MT_NONE
Actual: MT_EVENT
```

That assertion is the test's **before-delay** arm: after `SendWithDelayAsync(message, TimeSpan.FromSeconds(5))`,
a single bounded 2-second receive must observe nothing. It observes the message. A 5-second delay is not
being applied.

## Why this has never been seen before

It was masked twice over, which is why it only surfaced now:

1. **#4309** — the generated ASB tests never received *any* message, so the delayed one never got far
enough to be judged. In that state the test actually appeared to "pass" its before-delay arm, for the
wrong reason: nothing could arrive because no subscription existed yet.
2. **#4310** — once #4309 was fixed the message arrived, but mapping threw `UriFormatException` before
any timing assertion could be reached.

With both cleared, the real behaviour is visible for the first time.

## ⚠️ The existing tests are structurally incapable of catching this

`tests/Paramore.Brighter.AzureServiceBus.Tests/Fakes/FakeServiceBusSenderWrapper.cs:27`

```csharp
public Task ScheduleMessageAsync(ServiceBusMessage message, DateTimeOffset scheduleEnqueueTime,
CancellationToken cancellationToken = default)
=> Send(message);
```

The fake **discards `scheduleEnqueueTime` entirely** and forwards to the ordinary send. So every
hand-written delayed-send test — `AzureServiceBusMessageProducerTestsAsync` lines 147, 172, 194, 226,
229, 274 and the Reactor equivalents — asserts only on body and application properties, and would pass
identically if the gateway never scheduled anything at all.

**Whatever the root cause turns out to be, this gap should be closed**: the fake should record the
requested enqueue time so a unit test can assert the gateway asks for the right one.

## Not yet diagnosed

The production path *looks* correct on inspection, which is why this needs a proper investigation rather
than a guess:

- `AzureServiceBusMessageProducer.SendWithDelayAsync` branches on `delay == TimeSpan.Zero` and otherwise
calls `ScheduleMessageAsync` with `new DateTimeOffset(DateTime.UtcNow.Add(delay.Value))`.
`DateTime.UtcNow` has `Kind == Utc`, so the `DateTimeOffset` gets a zero offset and the instant is right.
- `ServiceBusSenderWrapper.ScheduleMessageAsync` delegates straight to the SDK's
`ServiceBusSender.ScheduleMessageAsync`.
- `AzureServiceBusMessageProducerFactory` returns the raw producer — there is no decorator that could be
swallowing the delay.

Candidates worth testing, in no particular order: clock skew between the CI runner and the Service Bus
namespace (which would make `now + 5s` already past from Azure's point of view); something in the
sender's `CloseAsync` in the `finally` block interacting with scheduled delivery; or an assumption about
scheduled-message visibility that does not hold for a topic subscription.

## Impact on the conformance ledger

`AzureServiceBus / AzureServiceBusMessagingGateway` **FR-9** stays `Deferred -> #4240`. The other nine
behaviours moved to `Pass` on the strength of the same probe run; FR-5 is deferred separately as a
genuine platform difference (ASB has no invalid-message channel).

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.