diff --git a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs index a1671c97d5d9aa2f95b60cfb575d2ccc54c1dd01..169de5a7800ff58bb4af46d4b172996bf9e00948 100644 --- a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs +++ b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs @@ -75,7 +75,6 @@ namespace Grpc.Core.Internal.Tests { var finishedTask = asyncCallServer.ServerSideCallAsync(); var requestStream = new ServerRequestStream<string, string>(asyncCallServer); - var responseStream = new ServerResponseStream<string, string>(asyncCallServer); // Finishing requestStream is needed for dispose to happen. var moveNextTask = requestStream.MoveNext(); @@ -91,7 +90,6 @@ namespace Grpc.Core.Internal.Tests { var finishedTask = asyncCallServer.ServerSideCallAsync(); var requestStream = new ServerRequestStream<string, string>(asyncCallServer); - var responseStream = new ServerResponseStream<string, string>(asyncCallServer); fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true); @@ -103,24 +101,89 @@ namespace Grpc.Core.Internal.Tests AssertFinished(asyncCallServer, fakeCall, finishedTask); } + [Test] + public void ReadCompletionFailureClosesRequestStream() + { + var finishedTask = asyncCallServer.ServerSideCallAsync(); + var requestStream = new ServerRequestStream<string, string>(asyncCallServer); + + // if a read completion's success==false, the request stream will silently finish + // and we rely on C core cancelling the call. + var moveNextTask = requestStream.MoveNext(); + fakeCall.ReceivedMessageHandler(false, null); + Assert.IsFalse(moveNextTask.Result); + + fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true); + AssertFinished(asyncCallServer, fakeCall, finishedTask); + } + + [Test] + public void WriteAfterCancelNotificationFails() + { + var finishedTask = asyncCallServer.ServerSideCallAsync(); + var requestStream = new ServerRequestStream<string, string>(asyncCallServer); + var responseStream = new ServerResponseStream<string, string>(asyncCallServer); + + fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true); + + // TODO(jtattermusch): should we throw a different exception type instead? + Assert.Throws(typeof(InvalidOperationException), () => responseStream.WriteAsync("request1")); + + // Finishing requestStream is needed for dispose to happen. + var moveNextTask = requestStream.MoveNext(); + fakeCall.ReceivedMessageHandler(true, null); + Assert.IsFalse(moveNextTask.Result); + + AssertFinished(asyncCallServer, fakeCall, finishedTask); + } + + [Test] + public void WriteCompletionFailureThrows() + { + var finishedTask = asyncCallServer.ServerSideCallAsync(); + var requestStream = new ServerRequestStream<string, string>(asyncCallServer); + var responseStream = new ServerResponseStream<string, string>(asyncCallServer); + + var writeTask = responseStream.WriteAsync("request1"); + fakeCall.SendCompletionHandler(false); + // TODO(jtattermusch): should we throw a different exception type instead? + Assert.ThrowsAsync(typeof(InvalidOperationException), async () => await writeTask); - // TODO: read completion failure ... + // Finishing requestStream is needed for dispose to happen. + var moveNextTask = requestStream.MoveNext(); + fakeCall.ReceivedMessageHandler(true, null); + Assert.IsFalse(moveNextTask.Result); + fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true); + AssertFinished(asyncCallServer, fakeCall, finishedTask); + } - // TODO: write fails... + [Test] + public void WriteAndWriteStatusCanRunConcurrently() + { + var finishedTask = asyncCallServer.ServerSideCallAsync(); + var requestStream = new ServerRequestStream<string, string>(asyncCallServer); + var responseStream = new ServerResponseStream<string, string>(asyncCallServer); - // TODO: write completion fails... + var writeTask = responseStream.WriteAsync("request1"); + var writeStatusTask = asyncCallServer.SendStatusFromServerAsync(Status.DefaultSuccess, new Metadata()); - // TODO: cancellation delivered... + fakeCall.SendCompletionHandler(true); + fakeCall.SendStatusFromServerHandler(true); - // TODO: cancel notification in the middle of a read... + Assert.DoesNotThrowAsync(async () => await writeTask); + Assert.DoesNotThrowAsync(async () => await writeStatusTask); - // TODO: cancel notification in the middle of a write... + // Finishing requestStream is needed for dispose to happen. + var moveNextTask = requestStream.MoveNext(); + fakeCall.ReceivedMessageHandler(true, null); + Assert.IsFalse(moveNextTask.Result); - // TODO: cancellation delivered... + fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true); - // TODO: what does writing status do to reads? + AssertFinished(asyncCallServer, fakeCall, finishedTask); + } static void AssertFinished(AsyncCallServer<string, string> asyncCallServer, FakeNativeCall fakeCall, Task finishedTask) { diff --git a/src/csharp/Grpc.Core.Tests/Internal/FakeNativeCall.cs b/src/csharp/Grpc.Core.Tests/Internal/FakeNativeCall.cs index 441bf9660beda8ddc08282e07358a66da0892a21..1bec258ca291b5b605cc18dc97a1c81ada25efc4 100644 --- a/src/csharp/Grpc.Core.Tests/Internal/FakeNativeCall.cs +++ b/src/csharp/Grpc.Core.Tests/Internal/FakeNativeCall.cs @@ -76,6 +76,12 @@ namespace Grpc.Core.Internal.Tests set; } + public SendCompletionHandler SendStatusFromServerHandler + { + get; + set; + } + public ReceivedCloseOnServerHandler ReceivedCloseOnServerHandler { get; @@ -161,7 +167,7 @@ namespace Grpc.Core.Internal.Tests public void StartSendStatusFromServer(SendCompletionHandler callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata) { - SendCompletionHandler = callback; + SendStatusFromServerHandler = callback; } public void StartServerSide(ReceivedCloseOnServerHandler callback)