From 624e132a6170c7e4e684896f43bdcab5810fb29b Mon Sep 17 00:00:00 2001 From: master <> Date: Thu, 2 Apr 2026 19:39:45 +0300 Subject: [PATCH] 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) --- .../MessagingTransportClient.cs | 5 ++++- .../MessagingTransportServer.cs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Router/__Libraries/StellaOps.Router.Transport.Messaging/MessagingTransportClient.cs b/src/Router/__Libraries/StellaOps.Router.Transport.Messaging/MessagingTransportClient.cs index b2e1012e2..1d72a7e29 100644 --- a/src/Router/__Libraries/StellaOps.Router.Transport.Messaging/MessagingTransportClient.cs +++ b/src/Router/__Libraries/StellaOps.Router.Transport.Messaging/MessagingTransportClient.cs @@ -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); diff --git a/src/Router/__Libraries/StellaOps.Router.Transport.Messaging/MessagingTransportServer.cs b/src/Router/__Libraries/StellaOps.Router.Transport.Messaging/MessagingTransportServer.cs index dbc24b23d..2f584b14a 100644 --- a/src/Router/__Libraries/StellaOps.Router.Transport.Messaging/MessagingTransportServer.cs +++ b/src/Router/__Libraries/StellaOps.Router.Transport.Messaging/MessagingTransportServer.cs @@ -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)