@@ -61,14 +61,16 @@ internal sealed partial class GrpcCall<TRequest, TResponse> : GrpcCall, IGrpcCal
6161 public HttpContentClientStreamWriter < TRequest , TResponse > ? ClientStreamWriter { get ; private set ; }
6262 public HttpContentClientStreamReader < TRequest , TResponse > ? ClientStreamReader { get ; private set ; }
6363
64- public GrpcCall ( Method < TRequest , TResponse > method , GrpcMethodInfo grpcMethodInfo , CallOptions options , GrpcChannel channel , int attemptCount )
64+ public GrpcCall ( Method < TRequest , TResponse > method , GrpcMethodInfo grpcMethodInfo , CallOptions options , GrpcChannel channel , int attemptCount , bool forceAsyncHttpResponse )
6565 : base ( options , channel )
6666 {
6767 // Validate deadline before creating any objects that require cleanup
6868 ValidateDeadline ( options . Deadline ) ;
6969
7070 _callCts = new CancellationTokenSource ( ) ;
71- _httpResponseTcs = new TaskCompletionSource < HttpResponseMessage > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
71+ // Retries and hedging can run multiple calls at the same time and use locking for thread-safety.
72+ // Running HTTP response continuation asynchronously is required for locking to work correctly.
73+ _httpResponseTcs = new TaskCompletionSource < HttpResponseMessage > ( forceAsyncHttpResponse ? TaskCreationOptions . RunContinuationsAsynchronously : TaskCreationOptions . None ) ;
7274 // Run the callTcs continuation immediately to keep the same context. Required for Activity.
7375 _callTcs = new TaskCompletionSource < Status > ( ) ;
7476 Method = method ;
@@ -142,7 +144,10 @@ public void StartDuplexStreaming()
142144
143145 internal void StartUnaryCore ( HttpContent content )
144146 {
145- _responseTcs = new TaskCompletionSource < TResponse > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
147+ // Not created with RunContinuationsAsynchronously to avoid unnecessary dispatch to the thread pool.
148+ // The TCS is set from RunCall but it is the last operation before the method exits so there shouldn't
149+ // be an impact from running the response continutation synchronously.
150+ _responseTcs = new TaskCompletionSource < TResponse > ( ) ;
146151
147152 var timeout = GetTimeout ( ) ;
148153 var message = CreateHttpRequestMessage ( timeout ) ;
@@ -161,7 +166,10 @@ internal void StartServerStreamingCore(HttpContent content)
161166
162167 internal void StartClientStreamingCore ( HttpContentClientStreamWriter < TRequest , TResponse > clientStreamWriter , HttpContent content )
163168 {
164- _responseTcs = new TaskCompletionSource < TResponse > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
169+ // Not created with RunContinuationsAsynchronously to avoid unnecessary dispatch to the thread pool.
170+ // The TCS is set from RunCall but it is the last operation before the method exits so there shouldn't
171+ // be an impact from running the response continutation synchronously.
172+ _responseTcs = new TaskCompletionSource < TResponse > ( ) ;
165173
166174 var timeout = GetTimeout ( ) ;
167175 var message = CreateHttpRequestMessage ( timeout ) ;
@@ -431,9 +439,6 @@ private void CancelCall(Status status)
431439 // Cancellation will also cause reader/writer to throw if used afterwards.
432440 _callCts . Cancel ( ) ;
433441
434- // Ensure any logic that is waiting on the HttpResponse is unstuck.
435- _httpResponseTcs . TrySetCanceled ( ) ;
436-
437442 // Cancellation token won't send RST_STREAM if HttpClient.SendAsync is complete.
438443 // Dispose HttpResponseMessage to send RST_STREAM to server for in-progress calls.
439444 HttpResponse ? . Dispose ( ) ;
@@ -652,6 +657,9 @@ private async Task RunCall(HttpRequestMessage request, TimeSpan? timeout)
652657 // Verify that FinishCall is called in every code path of this method.
653658 // Should create an "Unassigned variable" compiler error if not set.
654659 Debug . Assert ( finished ) ;
660+ // Should be completed before exiting.
661+ Debug . Assert ( _httpResponseTcs . Task . IsCompleted ) ;
662+ Debug . Assert ( _responseTcs == null || _responseTcs . Task . IsCompleted ) ;
655663 }
656664 }
657665
0 commit comments