Use WaitAsync to abandon handlers that ignore CancellationToken

Root cause found via diagnostics: the handler call at 16:27:19 never
returned. Guard: processing message X logged, but Guard: processed
never appeared. The 55s CancellationToken fired but the handler
ignored it (blocked on a non-cancelable StackExchange.Redis operation
or DB query that uses its own timeout).

Fix: Replace await handler(token) with handler(token).WaitAsync(token).
WaitAsync returns when EITHER the handler completes OR the token fires,
regardless of whether the handler cooperatively checks the token.
The abandoned handler continues in background but the consumer loop
resumes immediately.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-02 19:39:45 +03:00
parent da628531f8
commit 624e132a61
2 changed files with 6 additions and 2 deletions

View File

@@ -714,7 +714,10 @@ public sealed class MessagingTransportClient : ITransportClient, IMicroserviceTr
timeoutCts.CancelAfter(TimeSpan.FromSeconds(55));
_logger.LogWarning("[DIAG] Guard: processing message {MessageId}", lease.MessageId);
await handler(lease, timeoutCts.Token);
// WaitAsync abandons handlers that ignore CancellationToken (e.g.,
// StackExchange.Redis commands with their own internal timeout).
// The handler continues in background but the consumer loop is unblocked.
await handler(lease, timeoutCts.Token).WaitAsync(timeoutCts.Token);
sw.Stop();
_logger.LogWarning("[DIAG] Guard: message {MessageId} processed in {ElapsedMs}ms", lease.MessageId, sw.ElapsedMilliseconds);
await lease.AcknowledgeAsync(cancellationToken);

View File

@@ -395,7 +395,8 @@ public sealed class MessagingTransportServer : ITransportServer, IDisposable
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(55)); // 55s < 60s gateway timeout
await handler(lease, timeoutCts.Token);
// WaitAsync abandons handlers that ignore CancellationToken.
await handler(lease, timeoutCts.Token).WaitAsync(timeoutCts.Token);
await lease.AcknowledgeAsync(cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)