@@ -61,14 +61,16 @@ internal sealed partial class GrpcCall<TRequest, TResponse> : GrpcCall, IGrpcCal
61
61
public HttpContentClientStreamWriter < TRequest , TResponse > ? ClientStreamWriter { get ; private set ; }
62
62
public HttpContentClientStreamReader < TRequest , TResponse > ? ClientStreamReader { get ; private set ; }
63
63
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 )
65
65
: base ( options , channel )
66
66
{
67
67
// Validate deadline before creating any objects that require cleanup
68
68
ValidateDeadline ( options . Deadline ) ;
69
69
70
70
_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 ) ;
72
74
// Run the callTcs continuation immediately to keep the same context. Required for Activity.
73
75
_callTcs = new TaskCompletionSource < Status > ( ) ;
74
76
Method = method ;
@@ -142,7 +144,10 @@ public void StartDuplexStreaming()
142
144
143
145
internal void StartUnaryCore ( HttpContent content )
144
146
{
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 > ( ) ;
146
151
147
152
var timeout = GetTimeout ( ) ;
148
153
var message = CreateHttpRequestMessage ( timeout ) ;
@@ -161,7 +166,10 @@ internal void StartServerStreamingCore(HttpContent content)
161
166
162
167
internal void StartClientStreamingCore ( HttpContentClientStreamWriter < TRequest , TResponse > clientStreamWriter , HttpContent content )
163
168
{
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 > ( ) ;
165
173
166
174
var timeout = GetTimeout ( ) ;
167
175
var message = CreateHttpRequestMessage ( timeout ) ;
@@ -431,9 +439,6 @@ private void CancelCall(Status status)
431
439
// Cancellation will also cause reader/writer to throw if used afterwards.
432
440
_callCts . Cancel ( ) ;
433
441
434
- // Ensure any logic that is waiting on the HttpResponse is unstuck.
435
- _httpResponseTcs . TrySetCanceled ( ) ;
436
-
437
442
// Cancellation token won't send RST_STREAM if HttpClient.SendAsync is complete.
438
443
// Dispose HttpResponseMessage to send RST_STREAM to server for in-progress calls.
439
444
HttpResponse ? . Dispose ( ) ;
@@ -652,6 +657,9 @@ private async Task RunCall(HttpRequestMessage request, TimeSpan? timeout)
652
657
// Verify that FinishCall is called in every code path of this method.
653
658
// Should create an "Unassigned variable" compiler error if not set.
654
659
Debug . Assert ( finished ) ;
660
+ // Should be completed before exiting.
661
+ Debug . Assert ( _httpResponseTcs . Task . IsCompleted ) ;
662
+ Debug . Assert ( _responseTcs == null || _responseTcs . Task . IsCompleted ) ;
655
663
}
656
664
}
657
665
0 commit comments