aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/csharp/Grpc.Core/Internal/AsyncCall.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/csharp/Grpc.Core/Internal/AsyncCall.cs')
-rw-r--r--src/csharp/Grpc.Core/Internal/AsyncCall.cs149
1 files changed, 84 insertions, 65 deletions
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
index 2c3e3d75ea..be5d611a53 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
@@ -51,22 +51,35 @@ namespace Grpc.Core.Internal
static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<AsyncCall<TRequest, TResponse>>();
readonly CallInvocationDetails<TRequest, TResponse> details;
+ readonly INativeCall injectedNativeCall; // for testing
// Completion of a pending unary response if not null.
TaskCompletionSource<TResponse> unaryResponseTcs;
+ // Indicates that steaming call has finished.
+ TaskCompletionSource<object> streamingCallFinishedTcs = new TaskCompletionSource<object>();
+
+ // Response headers set here once received.
+ TaskCompletionSource<Metadata> responseHeadersTcs = new TaskCompletionSource<Metadata>();
+
// Set after status is received. Used for both unary and streaming response calls.
ClientSideStatus? finishedStatus;
- bool readObserverCompleted; // True if readObserver has already been completed.
-
public AsyncCall(CallInvocationDetails<TRequest, TResponse> callDetails)
- : base(callDetails.RequestMarshaller.Serializer, callDetails.ResponseMarshaller.Deserializer)
+ : base(callDetails.RequestMarshaller.Serializer, callDetails.ResponseMarshaller.Deserializer, callDetails.Channel.Environment)
{
this.details = callDetails.WithOptions(callDetails.Options.Normalize());
this.initialMetadataSent = true; // we always send metadata at the very beginning of the call.
}
+ /// <summary>
+ /// This constructor should only be used for testing.
+ /// </summary>
+ public AsyncCall(CallInvocationDetails<TRequest, TResponse> callDetails, INativeCall injectedNativeCall) : this(callDetails)
+ {
+ this.injectedNativeCall = injectedNativeCall;
+ }
+
// TODO: this method is not Async, so it shouldn't be in AsyncCall class, but
// it is reusing fair amount of code in this class, so we are leaving it here.
/// <summary>
@@ -100,7 +113,7 @@ namespace Grpc.Core.Internal
bool success = (ev.success != 0);
try
{
- HandleUnaryResponse(success, ctx);
+ HandleUnaryResponse(success, ctx.GetReceivedStatusOnClient(), ctx.GetReceivedMessage(), ctx.GetReceivedInitialMetadata());
}
catch (Exception e)
{
@@ -125,7 +138,7 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(!started);
started = true;
- Initialize(details.Channel.Environment.CompletionQueue);
+ Initialize(environment.CompletionQueue);
halfcloseRequested = true;
readingDone = true;
@@ -152,7 +165,7 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(!started);
started = true;
- Initialize(details.Channel.Environment.CompletionQueue);
+ Initialize(environment.CompletionQueue);
readingDone = true;
@@ -176,10 +189,9 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(!started);
started = true;
- Initialize(details.Channel.Environment.CompletionQueue);
+ Initialize(environment.CompletionQueue);
halfcloseRequested = true;
- halfclosed = true; // halfclose not confirmed yet, but it will be once finishedHandler is called.
byte[] payload = UnsafeSerialize(msg);
@@ -187,6 +199,7 @@ namespace Grpc.Core.Internal
{
call.StartServerStreaming(HandleFinished, payload, metadataArray, GetWriteFlagsForCall());
}
+ call.StartReceiveInitialMetadata(HandleReceivedResponseHeaders);
}
}
@@ -201,12 +214,13 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(!started);
started = true;
- Initialize(details.Channel.Environment.CompletionQueue);
+ Initialize(environment.CompletionQueue);
using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
{
call.StartDuplexStreaming(HandleFinished, metadataArray);
}
+ call.StartReceiveInitialMetadata(HandleReceivedResponseHeaders);
}
}
@@ -248,6 +262,28 @@ namespace Grpc.Core.Internal
}
/// <summary>
+ /// Get the task that completes once if streaming call finishes with ok status and throws RpcException with given status otherwise.
+ /// </summary>
+ public Task StreamingCallFinishedTask
+ {
+ get
+ {
+ return streamingCallFinishedTcs.Task;
+ }
+ }
+
+ /// <summary>
+ /// Get the task that completes once response headers are received.
+ /// </summary>
+ public Task<Metadata> ResponseHeadersAsync
+ {
+ get
+ {
+ return responseHeadersTcs.Task;
+ }
+ }
+
+ /// <summary>
/// Gets the resulting status if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
@@ -281,51 +317,31 @@ namespace Grpc.Core.Internal
}
}
- /// <summary>
- /// On client-side, we only fire readCompletionDelegate once all messages have been read
- /// and status has been received.
- /// </summary>
- protected override void ProcessLastRead(AsyncCompletionDelegate<TResponse> completionDelegate)
+ protected override void OnAfterReleaseResources()
{
- if (completionDelegate != null && readingDone && finishedStatus.HasValue)
- {
- bool shouldComplete;
- lock (myLock)
- {
- shouldComplete = !readObserverCompleted;
- readObserverCompleted = true;
- }
-
- if (shouldComplete)
- {
- var status = finishedStatus.Value.Status;
- if (status.StatusCode != StatusCode.OK)
- {
- FireCompletion(completionDelegate, default(TResponse), new RpcException(status));
- }
- else
- {
- FireCompletion(completionDelegate, default(TResponse), null);
- }
- }
- }
+ details.Channel.RemoveCallReference(this);
}
- protected override void OnReleaseResources()
+ private void Initialize(CompletionQueueSafeHandle cq)
{
- details.Channel.Environment.DebugStats.ActiveClientCalls.Decrement();
+ var call = CreateNativeCall(cq);
+ details.Channel.AddCallReference(this);
+ InitializeInternal(call);
+ RegisterCancellationCallback();
}
- private void Initialize(CompletionQueueSafeHandle cq)
+ private INativeCall CreateNativeCall(CompletionQueueSafeHandle cq)
{
+ if (injectedNativeCall != null)
+ {
+ return injectedNativeCall; // allows injecting a mock INativeCall in tests.
+ }
+
var parentCall = details.Options.PropagationToken != null ? details.Options.PropagationToken.ParentCall : CallSafeHandle.NullInstance;
- var call = details.Channel.Handle.CreateCall(details.Channel.Environment.CompletionRegistry,
+ return details.Channel.Handle.CreateCall(environment.CompletionRegistry,
parentCall, ContextPropagationToken.DefaultMask, cq,
details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value));
- details.Channel.Environment.DebugStats.ActiveClientCalls.Increment();
- InitializeInternal(call);
- RegisterCancellationCallback();
}
// Make sure that once cancellationToken for this call is cancelled, Cancel() will be called.
@@ -348,31 +364,31 @@ namespace Grpc.Core.Internal
}
/// <summary>
- /// Handler for unary response completion.
+ /// Handles receive status completion for calls with streaming response.
/// </summary>
- private void HandleUnaryResponse(bool success, BatchContextSafeHandle ctx)
+ private void HandleReceivedResponseHeaders(bool success, Metadata responseHeaders)
{
- var fullStatus = ctx.GetReceivedStatusOnClient();
+ responseHeadersTcs.SetResult(responseHeaders);
+ }
+ /// <summary>
+ /// Handler for unary response completion.
+ /// </summary>
+ private void HandleUnaryResponse(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders)
+ {
lock (myLock)
{
finished = true;
- finishedStatus = fullStatus;
-
- halfclosed = true;
+ finishedStatus = receivedStatus;
ReleaseResourcesIfPossible();
}
- if (!success)
- {
- unaryResponseTcs.SetException(new RpcException(new Status(StatusCode.Internal, "Internal error occured.")));
- return;
- }
+ responseHeadersTcs.SetResult(responseHeaders);
- var status = fullStatus.Status;
+ var status = receivedStatus.Status;
- if (status.StatusCode != StatusCode.OK)
+ if (!success || status.StatusCode != StatusCode.OK)
{
unaryResponseTcs.SetException(new RpcException(status));
return;
@@ -380,7 +396,7 @@ namespace Grpc.Core.Internal
// TODO: handle deserialization error
TResponse msg;
- TryDeserialize(ctx.GetReceivedMessage(), out msg);
+ TryDeserialize(receivedMessage, out msg);
unaryResponseTcs.SetResult(msg);
}
@@ -388,22 +404,25 @@ namespace Grpc.Core.Internal
/// <summary>
/// Handles receive status completion for calls with streaming response.
/// </summary>
- private void HandleFinished(bool success, BatchContextSafeHandle ctx)
+ private void HandleFinished(bool success, ClientSideStatus receivedStatus)
{
- var fullStatus = ctx.GetReceivedStatusOnClient();
-
- AsyncCompletionDelegate<TResponse> origReadCompletionDelegate = null;
lock (myLock)
{
finished = true;
- finishedStatus = fullStatus;
-
- origReadCompletionDelegate = readCompletionDelegate;
+ finishedStatus = receivedStatus;
ReleaseResourcesIfPossible();
}
- ProcessLastRead(origReadCompletionDelegate);
+ var status = receivedStatus.Status;
+
+ if (!success || status.StatusCode != StatusCode.OK)
+ {
+ streamingCallFinishedTcs.SetException(new RpcException(status));
+ return;
+ }
+
+ streamingCallFinishedTcs.SetResult(null);
}
}
} \ No newline at end of file