|  | @@ -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);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -247,6 +261,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.
 | 
	
	
		
			
				|  | @@ -281,36 +317,6 @@ 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)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            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);
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          protected override void OnAfterReleaseResources()
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              details.Channel.RemoveCallReference(this);
 | 
	
	
		
			
				|  | @@ -318,16 +324,24 @@ namespace Grpc.Core.Internal
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private void Initialize(CompletionQueueSafeHandle cq)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | +            var call = CreateNativeCall(cq);
 | 
	
		
			
				|  |  | +            details.Channel.AddCallReference(this);
 | 
	
		
			
				|  |  | +            InitializeInternal(call);
 | 
	
		
			
				|  |  | +            RegisterCancellationCallback();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        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.AddCallReference(this);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            InitializeInternal(call);
 | 
	
		
			
				|  |  | -            RegisterCancellationCallback();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          // Make sure that once cancellationToken for this call is cancelled, Cancel() will be called.
 | 
	
	
		
			
				|  | @@ -350,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;
 | 
	
	
		
			
				|  | @@ -382,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);
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -390,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);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 |