Skip to content

Commit a61e11f

Browse files
authored
Consistently don't log message errors on cancellation (#2221)
1 parent d49fbe4 commit a61e11f

File tree

4 files changed

+101
-7
lines changed

4 files changed

+101
-7
lines changed

src/Grpc.AspNetCore.Server/Internal/PipeExtensions.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ public static async Task WriteSingleMessageAsync<TResponse>(this PipeWriter pipe
7171
GrpcEventSource.Log.MessageSent();
7272
}
7373
}
74-
catch (Exception ex)
74+
catch (Exception ex) when (ex is not OperationCanceledException)
7575
{
76+
// Don't write error when user cancels write
7677
GrpcServerLog.ErrorSendingMessage(logger, ex);
7778
throw;
7879
}
@@ -121,8 +122,9 @@ public static async Task WriteStreamedMessageAsync<TResponse>(this PipeWriter pi
121122
GrpcEventSource.Log.MessageSent();
122123
}
123124
}
124-
catch (Exception ex)
125+
catch (Exception ex) when (ex is not OperationCanceledException)
125126
{
127+
// Don't write error when user cancels write
126128
GrpcServerLog.ErrorSendingMessage(logger, ex);
127129
throw;
128130
}
@@ -275,8 +277,9 @@ public static async ValueTask<T> ReadSingleMessageAsync<T>(this PipeReader input
275277
}
276278
}
277279
}
278-
catch (Exception ex)
280+
catch (Exception ex) when (ex is not OperationCanceledException)
279281
{
282+
// Don't write error when user cancels read
280283
GrpcServerLog.ErrorReadingMessage(logger, ex);
281284
throw;
282285
}

src/Grpc.Net.Client/Internal/StreamExtensions.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,11 @@ public static async Task WriteMessageAsync<TMessage>(
313313
}
314314
catch (Exception ex)
315315
{
316-
GrpcCallLog.ErrorSendingMessage(call.Logger, ex);
316+
if (!IsCancellationException(ex))
317+
{
318+
// Don't write error when user cancels write
319+
GrpcCallLog.ErrorSendingMessage(call.Logger, ex);
320+
}
317321

318322
if (TryCreateCallCompleteException(ex, call, out var statusException))
319323
{
@@ -346,7 +350,11 @@ public static async Task WriteMessageAsync(
346350
}
347351
catch (Exception ex)
348352
{
349-
GrpcCallLog.ErrorSendingMessage(call.Logger, ex);
353+
if (!IsCancellationException(ex))
354+
{
355+
// Don't write error when user cancels write
356+
GrpcCallLog.ErrorSendingMessage(call.Logger, ex);
357+
}
350358

351359
if (TryCreateCallCompleteException(ex, call, out var statusException))
352360
{
@@ -357,6 +365,8 @@ public static async Task WriteMessageAsync(
357365
}
358366
}
359367

368+
private static bool IsCancellationException(Exception ex) => ex is OperationCanceledException or ObjectDisposedException;
369+
360370
private static bool TryCreateCallCompleteException(Exception originalException, GrpcCall call, [NotNullWhen(true)] out Exception? exception)
361371
{
362372
// The call may have been completed while WriteAsync was running and caused WriteAsync to throw.
@@ -365,7 +375,7 @@ private static bool TryCreateCallCompleteException(Exception originalException,
365375
// Replace exception with the status error if:
366376
// 1. The original exception is one Stream.WriteAsync throws if the call was completed during a write, and
367377
// 2. The call has already been successfully completed.
368-
if (originalException is OperationCanceledException or ObjectDisposedException &&
378+
if (IsCancellationException(originalException) &&
369379
call.CallTask.IsCompletedSuccessfully())
370380
{
371381
exception = call.CreateFailureStatusException(call.CallTask.Result);

test/Grpc.Net.Client.Tests/AsyncClientStreamingCallTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
using Grpc.Net.Client.Internal.Http;
2525
using Grpc.Net.Client.Tests.Infrastructure;
2626
using Grpc.Tests.Shared;
27+
using Microsoft.Extensions.Logging.Testing;
2728
using NUnit.Framework;
2829

2930
namespace Grpc.Net.Client.Tests;
@@ -160,6 +161,49 @@ public async Task ClientStreamWriter_WriteWhilePendingWrite_ErrorThrown()
160161
Assert.AreEqual("Can't write the message because the previous write is in progress.", ex.Message);
161162
}
162163

164+
[Test]
165+
public async Task ClientStreamWriter_DisposeWhilePendingWrite_NoReadMessageError()
166+
{
167+
// Arrange
168+
var testSink = new TestSink();
169+
var loggerFactory = new TestLoggerFactory(testSink, true);
170+
PushStreamContent<HelloRequest, HelloReply>? content = null;
171+
172+
var responseTcs = new TaskCompletionSource<HttpResponseMessage>(TaskCreationOptions.RunContinuationsAsynchronously);
173+
var httpClient = ClientTestHelpers.CreateTestClient(request =>
174+
{
175+
content = (PushStreamContent<HelloRequest, HelloReply>)request.Content!;
176+
return responseTcs.Task;
177+
});
178+
var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: loggerFactory);
179+
180+
// Act
181+
var call = invoker.AsyncClientStreamingCall<HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions());
182+
183+
// Assert
184+
var writeTask1 = call.RequestStream.WriteAsync(new HelloRequest { Name = "1" });
185+
186+
var writeSyncPoint = new SyncPoint(runContinuationsAsynchronously: true);
187+
var testStream = new TestStream(writeSyncPoint);
188+
var serializeToStreamTask = content!.SerializeToStreamAsync(testStream);
189+
190+
Assert.IsFalse(writeTask1.IsCompleted);
191+
await writeSyncPoint.WaitForSyncPoint().DefaultTimeout();
192+
193+
call.Dispose();
194+
writeSyncPoint.Continue();
195+
196+
var ex1 = await ExceptionAssert.ThrowsAsync<RpcException>(() => call.ResponseAsync).DefaultTimeout();
197+
Assert.AreEqual(StatusCode.Cancelled, ex1.StatusCode);
198+
Assert.AreEqual(StatusCode.Cancelled, call.GetStatus().StatusCode);
199+
Assert.AreEqual("gRPC call disposed.", call.GetStatus().Detail);
200+
201+
var ex2 = await ExceptionAssert.ThrowsAsync<RpcException>(() => writeTask1).DefaultTimeout();
202+
Assert.AreEqual(StatusCode.Cancelled, ex2.StatusCode);
203+
204+
Assert.IsFalse(testSink.Writes.Any(w => w.EventId.Name == "ErrorSendingMessage"), "ErrorSendingMessage shouldn't be logged on dispose.");
205+
}
206+
163207
[Test]
164208
public async Task ClientStreamWriter_CompleteWhilePendingWrite_ErrorThrown()
165209
{

test/Grpc.Net.Client.Tests/AsyncServerStreamingCallTests.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -23,6 +23,7 @@
2323
using Grpc.Net.Client.Tests.Infrastructure;
2424
using Grpc.Shared;
2525
using Grpc.Tests.Shared;
26+
using Microsoft.Extensions.Logging.Testing;
2627
using NUnit.Framework;
2728

2829
namespace Grpc.Net.Client.Tests;
@@ -277,6 +278,42 @@ await streamContent.AddDataAndWait(await ClientTestHelpers.GetResponseDataAsync(
277278
Assert.AreEqual("gRPC call disposed.", call.GetStatus().Detail);
278279
}
279280

281+
[Test]
282+
public async Task AsyncServerStreamingCall_DisposeDuringPendingRead_NoReadMessageError()
283+
{
284+
// Arrange
285+
var testSink = new TestSink();
286+
var loggerFactory = new TestLoggerFactory(testSink, true);
287+
288+
var streamContent = new SyncPointMemoryStream();
289+
290+
var httpClient = ClientTestHelpers.CreateTestClient(request =>
291+
{
292+
return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.OK, new StreamContent(streamContent)));
293+
});
294+
var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: loggerFactory);
295+
296+
// Act
297+
var call = invoker.AsyncServerStreamingCall<HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());
298+
299+
var responseStream = call.ResponseStream;
300+
301+
// Assert
302+
Assert.IsNull(responseStream.Current);
303+
304+
var moveNextTask1 = responseStream.MoveNext(CancellationToken.None);
305+
Assert.IsFalse(moveNextTask1.IsCompleted);
306+
307+
call.Dispose();
308+
309+
var ex = await ExceptionAssert.ThrowsAsync<RpcException>(() => moveNextTask1).DefaultTimeout();
310+
Assert.AreEqual(StatusCode.Cancelled, ex.StatusCode);
311+
Assert.AreEqual(StatusCode.Cancelled, call.GetStatus().StatusCode);
312+
Assert.AreEqual("gRPC call disposed.", call.GetStatus().Detail);
313+
314+
Assert.IsFalse(testSink.Writes.Any(w => w.EventId.Name == "ErrorReadingMessage"), "ErrorReadingMessage shouldn't be logged on dispose.");
315+
}
316+
280317
[Test]
281318
public async Task ClientStreamReader_WriteWithInvalidHttpStatus_ErrorThrown()
282319
{

0 commit comments

Comments
 (0)