Skip to content

Commit 0ddd607

Browse files
Robert Karpclaude
andcommitted
fix(#446605, #442484): broaden shutdown TaskCanceledException guard to cover CancellationToken.None
The previous guard checked oce.CancellationToken.IsCancellationRequested, but the Azure ServiceBus SDK raises ProcessErrorAsync with a TaskCanceledException whose CancellationToken is CancellationToken.None during processor shutdown — so IsCancellationRequested is always false and the guard never fired. Widening to any OperationCanceledException is safe because genuine transport errors are ServiceBusException, not OperationCanceledException. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent de4367d commit 0ddd607

2 files changed

Lines changed: 30 additions & 6 deletions

File tree

src/Ev.ServiceBus/Management/Wrappers/ReceiverWrapper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ private void TrySetReceptionRegistrationOnContext(MessageContext context, IServi
132132
/// <returns></returns>
133133
protected async Task OnExceptionOccured(ProcessErrorEventArgs exceptionEvent)
134134
{
135-
if (exceptionEvent.Exception is OperationCanceledException oce && oce.CancellationToken.IsCancellationRequested)
135+
if (exceptionEvent.Exception is OperationCanceledException)
136136
{
137137
_messageProcessingLogger.LogWarning(
138138
"[Ev.ServiceBus] Receive loop cancelled for {ClientType} '{ResourceId}' during shutdown.",

tests/Ev.ServiceBus.UnitTests/ReceiverWrapperTests.cs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,44 @@ private static TestableReceiverWrapper CreateWrapper(ILogger<LoggingExtensions.M
3131
}
3232

3333
[Fact]
34-
public async Task OnExceptionOccured_WithCancelledToken_DoesNotLogError()
34+
public async Task OnExceptionOccured_WithOperationCanceledException_DoesNotLogError()
3535
{
3636
var mockLogger = new Mock<ILogger<LoggingExtensions.MessageProcessing>>();
3737
var wrapper = CreateWrapper(mockLogger.Object);
3838

39-
using var cts = new CancellationTokenSource();
40-
cts.Cancel();
39+
// Azure SDK raises ProcessErrorAsync with CancellationToken.None during shutdown —
40+
// the token on the exception is not the shutdown token, so IsCancellationRequested is false.
41+
var args = new ProcessErrorEventArgs(
42+
new OperationCanceledException("shutdown", CancellationToken.None),
43+
ServiceBusErrorSource.Receive,
44+
"test-namespace",
45+
"test-queue",
46+
CancellationToken.None);
47+
48+
await wrapper.InvokeOnExceptionOccuredAsync(args);
49+
50+
mockLogger.Verify(
51+
x => x.Log(
52+
LogLevel.Error,
53+
It.IsAny<EventId>(),
54+
It.Is<It.IsAnyType>((v, t) => true),
55+
It.IsAny<Exception?>(),
56+
It.Is<Func<It.IsAnyType, Exception?, string>>((v, t) => true)),
57+
Times.Never());
58+
}
59+
60+
[Fact]
61+
public async Task OnExceptionOccured_WithTaskCanceledException_DoesNotLogError()
62+
{
63+
var mockLogger = new Mock<ILogger<LoggingExtensions.MessageProcessing>>();
64+
var wrapper = CreateWrapper(mockLogger.Object);
4165

4266
var args = new ProcessErrorEventArgs(
43-
new OperationCanceledException("shutdown", cts.Token),
67+
new TaskCanceledException(),
4468
ServiceBusErrorSource.Receive,
4569
"test-namespace",
4670
"test-queue",
47-
cts.Token);
71+
CancellationToken.None);
4872

4973
await wrapper.InvokeOnExceptionOccuredAsync(args);
5074

0 commit comments

Comments
 (0)