Exemple #1
0
        public override void Complete()
        {
            switch (_state)
            {
            case InternalState.IncompleteBufferWriter:
                _state = InternalState.CompleteBufferWriter;

                if (!DirectSerializationSupported)
                {
                    CompatibilityHelpers.Assert(_bufferWriter != null, "Buffer writer has been set to get to this state.");

                    var data = _bufferWriter.WrittenSpan;

                    GrpcCallLog.SerializedMessage(_call.Logger, _call.RequestType, data.Length);
                    WriteMessage(data);
                }
                else
                {
                    GrpcCallLog.SerializedMessage(_call.Logger, _call.RequestType, _payloadLength.GetValueOrDefault());
                }
                break;

            default:
                ThrowInvalidState(_state);
                break;
            }
        }
Exemple #2
0
        public override IBufferWriter <byte> GetBufferWriter()
        {
            switch (_state)
            {
            case InternalState.Initialized:
                var bufferWriter = ResolveBufferWriter();

                // When writing directly to the buffer the header with message size needs to be written first
                if (DirectSerializationSupported)
                {
                    CompatibilityHelpers.Assert(_payloadLength != null, "A payload length is required for direct serialization.");

                    EnsureMessageSizeAllowed(_payloadLength.Value);

                    WriteHeader(_buffer, _payloadLength.Value, compress: false);
                    _bufferPosition += GrpcProtocolConstants.HeaderSize;
                }

                _state = InternalState.IncompleteBufferWriter;
                return(bufferWriter);

            case InternalState.IncompleteBufferWriter:
                return(ResolveBufferWriter());

            default:
                ThrowInvalidState(_state);
                return(default !);
            }
        }
Exemple #3
0
        public override Task ClientStreamWriteAsync(TRequest message)
        {
            // The retry client stream writer prevents multiple threads from reaching here.
            return(DoClientStreamActionAsync(async call =>
            {
                CompatibilityHelpers.Assert(call.ClientStreamWriter != null);

                if (ClientStreamWriteOptions != null)
                {
                    call.ClientStreamWriter.WriteOptions = ClientStreamWriteOptions;
                }

                await call.WriteClientStreamAsync(WriteNewMessage, message).ConfigureAwait(false);

                lock (Lock)
                {
                    BufferedCurrentMessage = false;
                }

                if (ClientStreamComplete)
                {
                    await call.ClientStreamWriter.CompleteAsync().ConfigureAwait(false);
                }
            }));
        }
Exemple #4
0
        private async Task <Metadata> GetResponseHeadersCoreAsync()
        {
            CompatibilityHelpers.Assert(_httpResponseTask != null);

            try
            {
                var httpResponse = await _httpResponseTask.ConfigureAwait(false);

                // Check if the headers have a status. If they do then wait for the overall call task
                // to complete before returning headers. This means that if the call failed with a
                // a status then it is possible to await response headers and then call GetStatus().
                var grpcStatus = GrpcProtocolHelpers.GetHeaderValue(httpResponse.Headers, GrpcProtocolConstants.StatusTrailer);
                if (grpcStatus != null)
                {
                    await CallTask.ConfigureAwait(false);
                }

                var metadata = GrpcProtocolHelpers.BuildMetadata(httpResponse.Headers);

                // https://github.com/grpc/proposal/blob/master/A6-client-retries.md#exposed-retry-metadata
                if (_attemptCount > 1)
                {
                    metadata.Add(GrpcProtocolConstants.RetryPreviousAttemptsHeader, (_attemptCount - 1).ToString(CultureInfo.InvariantCulture));
                }

                return(metadata);
            }
            catch (Exception ex) when(ResolveException(ErrorStartingCallMessage, ex, out _, out var resolvedException))
            {
                throw resolvedException;
            }
        }
        public BalancerHttpHandler(HttpMessageHandler innerHandler, HttpHandlerType httpHandlerType, ConnectionManager manager)
            : base(innerHandler)
        {
            _manager = manager;

#if NET5_0_OR_GREATER
            if (httpHandlerType == HttpHandlerType.SocketsHttpHandler)
            {
                var socketsHttpHandler = HttpRequestHelpers.GetHttpHandlerType <SocketsHttpHandler>(innerHandler);
                CompatibilityHelpers.Assert(socketsHttpHandler != null, "Should have handler with this handler type.");

                socketsHttpHandler.ConnectCallback = OnConnect;
            }
#endif
        }
Exemple #6
0
        private async Task HedgingDelayAsync(TimeSpan hedgingDelay)
        {
            CompatibilityHelpers.Assert(_hedgingDelayCts != null);
            CompatibilityHelpers.Assert(_delayInterruptTcs != null);

            while (true)
            {
                CompatibilityHelpers.Assert(_hedgingDelayCts != null);

                var completedTask = await Task.WhenAny(Task.Delay(hedgingDelay, _hedgingDelayCts.Token), _delayInterruptTcs.Task).ConfigureAwait(false);

                if (completedTask != _delayInterruptTcs.Task)
                {
                    // Task.Delay won. Check CTS to see if it won because of cancellation.
                    _hedgingDelayCts.Token.ThrowIfCancellationRequested();
                    return;
                }
                else
                {
                    // Cancel the Task.Delay that's no longer needed.
                    // https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/519ef7d231c01116f02bc04354816a735f2a36b6/AsyncGuidance.md#using-a-timeout
                    _hedgingDelayCts.Cancel();
                }

                lock (Lock)
                {
                    // If we reaching this point then the delay was interrupted.
                    // Need to recreate the delay TCS/CTS for the next cycle.
                    _delayInterruptTcs = new TaskCompletionSource <object?>(TaskCreationOptions.RunContinuationsAsynchronously);
                    _hedgingDelayCts   = new CancellationTokenSource();

                    // Interrupt could come from a pushback, or a failing call with a non-fatal status.
                    if (_pushbackDelay != null)
                    {
                        // Use pushback value and delay again
                        hedgingDelay = _pushbackDelay.GetValueOrDefault();

                        _pushbackDelay = null;
                    }
                    else
                    {
                        // Immediately return for non-fatal status.
                        return;
                    }
                }
            }
        }
Exemple #7
0
        private void SetFailedResult(Status status)
        {
            CompatibilityHelpers.Assert(_responseTcs != null);

            if (Channel.ThrowOperationCanceledOnCancellation && status.StatusCode == StatusCode.DeadlineExceeded)
            {
                // Convert status response of DeadlineExceeded to OperationCanceledException when
                // ThrowOperationCanceledOnCancellation is true.
                // This avoids a race between the client-side timer and the server status throwing different
                // errors on deadline exceeded.
                _responseTcs.TrySetCanceled();
            }
            else
            {
                _responseTcs.TrySetException(CreateRpcException(status));
            }
        }
Exemple #8
0
        protected bool TryGetTrailers([NotNullWhen(true)] out Metadata?trailers)
        {
            if (Trailers == null)
            {
                // Trailers are read from the end of the request.
                // If the request isn't finished then we can't get the trailers.
                if (!ResponseFinished)
                {
                    trailers = null;
                    return(false);
                }

                CompatibilityHelpers.Assert(HttpResponse != null);
                Trailers = GrpcProtocolHelpers.BuildMetadata(HttpResponse.TrailingHeaders());
            }

            trailers = Trailers;
            return(true);
        }
Exemple #9
0
        private ReadOnlySpan <byte> CompressMessage(ReadOnlySpan <byte> messageData)
        {
            CompatibilityHelpers.Assert(_compressionProvider != null, "Compression provider is not null to get here.");

            GrpcCallLog.CompressingMessage(_call.Logger, _compressionProvider.EncodingName);

            var output = new MemoryStream();

            // Compression stream must be disposed before its content is read.
            // GZipStream writes final Adler32 at the end of the stream on dispose.
            using (var compressionStream = _compressionProvider.CreateCompressionStream(output, CompressionLevel.Fastest))
            {
#if !NETSTANDARD2_0
                compressionStream.Write(messageData);
#else
                var array = messageData.ToArray();
                compressionStream.Write(array, 0, array.Length);
#endif
            }

            return(output.GetBuffer().AsSpan(0, (int)output.Length));
        }
Exemple #10
0
        private ICompressionProvider?ResolveCompressionProvider()
        {
            CompatibilityHelpers.Assert(
                _call.RequestGrpcEncoding != null,
                "Response encoding should have been calculated at this point.");

            var canCompress =
                GrpcProtocolHelpers.CanWriteCompressed(CallOptions.WriteOptions) &&
                !string.Equals(_call.RequestGrpcEncoding, GrpcProtocolConstants.IdentityGrpcEncoding, StringComparison.Ordinal);

            if (canCompress)
            {
                if (_call.Channel.CompressionProviders.TryGetValue(_call.RequestGrpcEncoding, out var compressionProvider))
                {
                    return(compressionProvider);
                }

                throw new InvalidOperationException($"Could not find compression provider for '{_call.RequestGrpcEncoding}'.");
            }

            return(null);
        }
        public override async Task ClientStreamWriteAsync(TRequest message, CancellationToken cancellationToken)
        {
            using var registration = (cancellationToken.CanBeCanceled && cancellationToken != Options.CancellationToken)
                ? RegisterRetryCancellationToken(cancellationToken)
                : default;

            // The retry client stream writer prevents multiple threads from reaching here.
            await DoClientStreamActionAsync(async call =>
            {
                CompatibilityHelpers.Assert(call.ClientStreamWriter != null);

                if (ClientStreamWriteOptions != null)
                {
                    call.ClientStreamWriter.WriteOptions = ClientStreamWriteOptions;
                }

                call.TryRegisterCancellation(cancellationToken, out var registration);
                try
                {
                    await call.WriteClientStreamAsync(WriteNewMessage, message).ConfigureAwait(false);
                }
                finally
                {
                    registration?.Dispose();
                }

                lock (Lock)
                {
                    BufferedCurrentMessage = false;
                }

                if (ClientStreamComplete)
                {
                    await call.ClientStreamWriter.CompleteAsync().ConfigureAwait(false);
                }
            }).ConfigureAwait(false);
        }
Exemple #12
0
        public async Task AsyncClientStreamingCall_CompressMetadataSentWithRequest_RequestMessageCompressed()
        {
            // Arrange
            HttpRequestMessage?httpRequestMessage = null;
            HelloRequest?      helloRequest1      = null;
            HelloRequest?      helloRequest2      = null;
            bool?isRequestCompressed1             = null;
            bool?isRequestCompressed2             = null;

            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                httpRequestMessage = request;

                var requestData   = await request.Content !.ReadAsByteArrayAsync().DefaultTimeout();
                var requestStream = new MemoryStream(requestData);

                isRequestCompressed1 = requestData[0] == 1;
                helloRequest1        = await StreamSerializationHelper.ReadMessageAsync(
                    requestStream,
                    ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer,
                    "gzip",
                    maximumMessageSize: null,
                    GrpcProtocolConstants.DefaultCompressionProviders,
                    singleMessage: false,
                    CancellationToken.None);

                isRequestCompressed2 = requestData[requestStream.Position] == 1;
                helloRequest2        = await StreamSerializationHelper.ReadMessageAsync(
                    requestStream,
                    ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer,
                    "gzip",
                    maximumMessageSize: null,
                    GrpcProtocolConstants.DefaultCompressionProviders,
                    singleMessage: false,
                    CancellationToken.None);

                var reply = new HelloReply
                {
                    Message = "Hello world"
                };

                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();

                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });

            var compressionProviders = GrpcProtocolConstants.DefaultCompressionProviders.Values.ToList();

            compressionProviders.Add(new TestCompressionProvider());

            var invoker = HttpClientCallInvokerFactory.Create(httpClient, configure: o => o.CompressionProviders = compressionProviders);

            var compressionMetadata = CreateClientCompressionMetadata("gzip");
            var callOptions         = new CallOptions(headers: compressionMetadata);

            // Act
            var call = invoker.AsyncClientStreamingCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, callOptions);

            await call.RequestStream.WriteAsync(new HelloRequest
            {
                Name = "Hello One"
            }).DefaultTimeout();

            call.RequestStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress);
            await call.RequestStream.WriteAsync(new HelloRequest
            {
                Name = "Hello Two"
            }).DefaultTimeout();

            await call.RequestStream.CompleteAsync().DefaultTimeout();

            // Assert
            var response = await call.ResponseAsync.DefaultTimeout();

            Assert.IsNotNull(response);
            Assert.AreEqual("Hello world", response.Message);

            CompatibilityHelpers.Assert(httpRequestMessage != null);
#if NET6_0_OR_GREATER
            Assert.AreEqual("identity,gzip,deflate,test", httpRequestMessage.Headers.GetValues(GrpcProtocolConstants.MessageAcceptEncodingHeader).Single());
#else
            Assert.AreEqual("identity,gzip,test", httpRequestMessage.Headers.GetValues(GrpcProtocolConstants.MessageAcceptEncodingHeader).Single());
#endif
            Assert.AreEqual("gzip", httpRequestMessage.Headers.GetValues(GrpcProtocolConstants.MessageEncodingHeader).Single());
            Assert.AreEqual(false, httpRequestMessage.Headers.Contains(GrpcProtocolConstants.CompressionRequestAlgorithmHeader));

            CompatibilityHelpers.Assert(helloRequest1 != null);
            Assert.AreEqual("Hello One", helloRequest1.Name);
            CompatibilityHelpers.Assert(helloRequest2 != null);
            Assert.AreEqual("Hello Two", helloRequest2.Name);

            Assert.IsTrue(isRequestCompressed1);
            Assert.IsFalse(isRequestCompressed2);
        }
Exemple #13
0
        public async Task AsyncUnaryCall_CompressMetadataSentWithRequest_RequestMessageCompressed(bool compressionDisabledOnOptions)
        {
            // Arrange
            HttpRequestMessage?httpRequestMessage = null;
            HelloRequest?      helloRequest       = null;
            bool?isRequestNotCompressed           = null;

            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                httpRequestMessage = request;

                var requestData        = await request.Content !.ReadAsByteArrayAsync().DefaultTimeout();
                isRequestNotCompressed = requestData[0] == 0;

                helloRequest = await StreamSerializationHelper.ReadMessageAsync(
                    new MemoryStream(requestData),
                    ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer,
                    "gzip",
                    maximumMessageSize: null,
                    GrpcProtocolConstants.DefaultCompressionProviders,
                    singleMessage: true,
                    CancellationToken.None);

                HelloReply reply = new HelloReply
                {
                    Message = "Hello world"
                };

                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();

                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });

            var compressionProviders = GrpcProtocolConstants.DefaultCompressionProviders.Values.ToList();

            compressionProviders.Add(new TestCompressionProvider());

            var invoker = HttpClientCallInvokerFactory.Create(httpClient, configure: o => o.CompressionProviders = compressionProviders);

            var compressionMetadata = CreateClientCompressionMetadata("gzip");
            var callOptions         = new CallOptions(headers: compressionMetadata);

            if (compressionDisabledOnOptions)
            {
                callOptions = callOptions.WithWriteOptions(new WriteOptions(WriteFlags.NoCompress));
            }

            // Act
            var call = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, callOptions, new HelloRequest
            {
                Name = "Hello"
            });

            // Assert
            var response = await call.ResponseAsync;

            Assert.IsNotNull(response);
            Assert.AreEqual("Hello world", response.Message);

            CompatibilityHelpers.Assert(httpRequestMessage != null);
            Assert.AreEqual("identity,gzip,test", httpRequestMessage.Headers.GetValues(GrpcProtocolConstants.MessageAcceptEncodingHeader).Single());
            Assert.AreEqual("gzip", httpRequestMessage.Headers.GetValues(GrpcProtocolConstants.MessageEncodingHeader).Single());
            Assert.AreEqual(false, httpRequestMessage.Headers.Contains(GrpcProtocolConstants.CompressionRequestAlgorithmHeader));

            CompatibilityHelpers.Assert(helloRequest != null);
            Assert.AreEqual("Hello", helloRequest.Name);

            Assert.AreEqual(compressionDisabledOnOptions, isRequestNotCompressed);
        }
Exemple #14
0
        private async Task RunCall(HttpRequestMessage request, TimeSpan?timeout)
        {
            using (StartScope())
            {
                var(diagnosticSourceEnabled, activity) = InitializeCall(request, timeout);

                // Unset variable to check that FinishCall is called in every code path
                bool finished;

                Status?status = null;

                try
                {
                    // User supplied call credentials could error and so must be run
                    // inside try/catch to handle errors.
                    if (Options.Credentials != null || Channel.CallCredentials?.Count > 0)
                    {
                        await ReadCredentials(request).ConfigureAwait(false);
                    }

                    // Fail early if deadline has already been exceeded
                    _callCts.Token.ThrowIfCancellationRequested();

                    try
                    {
                        // If a HttpClient has been specified then we need to call it with ResponseHeadersRead
                        // so that the response message is available for streaming
                        var httpResponseTask = (Channel.HttpInvoker is HttpClient httpClient)
                            ? httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, _callCts.Token)
                            : Channel.HttpInvoker.SendAsync(request, _callCts.Token);

                        HttpResponse = await httpResponseTask.ConfigureAwait(false);

                        _httpResponseTcs.TrySetResult(HttpResponse);
                    }
                    catch (Exception ex)
                    {
                        if (IsCancellationOrDeadlineException(ex))
                        {
                            throw;
                        }
                        else
                        {
                            GrpcCallLog.ErrorStartingCall(Logger, ex);
                            throw;
                        }
                    }

                    GrpcCallLog.ResponseHeadersReceived(Logger);
                    status = ValidateHeaders(HttpResponse, out var trailers);
                    if (trailers != null)
                    {
                        Trailers = trailers;
                    }

                    // A status means either the call has failed or grpc-status was returned in the response header
                    if (status != null)
                    {
                        if (_responseTcs != null)
                        {
                            // gRPC status in the header
                            if (status.Value.StatusCode != StatusCode.OK)
                            {
                                finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value);
                            }
                            else
                            {
                                // The server should never return StatusCode.OK in the header for a unary call.
                                // If it does then throw an error that no message was returned from the server.
                                GrpcCallLog.MessageNotReturned(Logger);

                                // Change the status code to a more accurate status.
                                // This is consistent with Grpc.Core client behavior.
                                status = new Status(StatusCode.Internal, "Failed to deserialize response message.");

                                finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value);
                            }

                            FinishResponseAndCleanUp(status.Value);

                            // Set failed result makes the response task thrown an error. Must be called after
                            // the response is finished. Reasons:
                            // - Finishing the response sets the status. Required for GetStatus to be successful.
                            // - We want GetStatus to always work when called after the response task is done.
                            SetFailedResult(status.Value);
                        }
                        else
                        {
                            finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value);
                            FinishResponseAndCleanUp(status.Value);
                        }
                    }
                    else
                    {
                        if (_responseTcs != null)
                        {
                            // Read entire response body immediately and read status from trailers
                            // Trailers are only available once the response body had been read
                            var responseStream = await HttpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);

                            var message = await ReadMessageAsync(
                                responseStream,
                                GrpcProtocolHelpers.GetGrpcEncoding(HttpResponse),
                                singleMessage : true,
                                _callCts.Token).ConfigureAwait(false);

                            status = GrpcProtocolHelpers.GetResponseStatus(HttpResponse, Channel.OperatingSystem.IsBrowser, Channel.HttpHandlerType == HttpHandlerType.WinHttpHandler);

                            if (message == null)
                            {
                                GrpcCallLog.MessageNotReturned(Logger);

                                if (status.Value.StatusCode == StatusCode.OK)
                                {
                                    // Change the status code if OK is returned to a more accurate status.
                                    // This is consistent with Grpc.Core client behavior.
                                    status = new Status(StatusCode.Internal, "Failed to deserialize response message.");
                                }

                                FinishResponseAndCleanUp(status.Value);
                                finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value);

                                // Set failed result makes the response task thrown an error. Must be called after
                                // the response is finished. Reasons:
                                // - Finishing the response sets the status. Required for GetStatus to be successful.
                                // - We want GetStatus to always work when called after the response task is done.
                                SetFailedResult(status.Value);
                            }
                            else
                            {
                                GrpcEventSource.Log.MessageReceived();

                                FinishResponseAndCleanUp(status.Value);
                                finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value);

                                if (status.Value.StatusCode == StatusCode.OK)
                                {
                                    _responseTcs.TrySetResult(message);
                                }
                                else
                                {
                                    // Set failed result makes the response task thrown an error. Must be called after
                                    // the response is finished. Reasons:
                                    // - Finishing the response sets the status. Required for GetStatus to be successful.
                                    // - We want GetStatus to always work when called after the response task is done.
                                    SetFailedResult(status.Value);
                                }
                            }
                        }
                        else
                        {
                            // Duplex or server streaming call
                            CompatibilityHelpers.Assert(ClientStreamReader != null);
                            ClientStreamReader.HttpResponseTcs.TrySetResult((HttpResponse, status));

                            // Wait until the response has been read and status read from trailers.
                            // TCS will also be set in Dispose.
                            status = await CallTask.ConfigureAwait(false);

                            finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value);
                            Cleanup(status.Value);
                        }
                    }
                }
                catch (Exception ex)
                {
                    ResolveException(ErrorStartingCallMessage, ex, out status, out var resolvedException);

                    finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value);

                    // Update HTTP response TCS before clean up. Needs to happen first because cleanup will
                    // cancel the TCS for anyone still listening.
                    _httpResponseTcs.TrySetException(resolvedException);

                    Cleanup(status.Value);

                    // Update response TCS after overall call status is resolved. This is required so that
                    // the call is completed before an error is thrown from ResponseAsync. If it happens
                    // afterwards then there is a chance GetStatus() will error because the call isn't complete.
                    _responseTcs?.TrySetException(resolvedException);
                }

                // Verify that FinishCall is called in every code path of this method.
                // Should create an "Unassigned variable" compiler error if not set.
                Debug.Assert(finished);
            }
        }
Exemple #15
0
        private async Task StartRetry(Action <GrpcCall <TRequest, TResponse> > startCallFunc)
        {
            Log.StartingRetryWorker(Logger);

            try
            {
                // This is the main retry loop. It will:
                // 1. Check the result of the active call was successful.
                // 2. If it was unsuccessful then evaluate if the call can be retried.
                // 3. If it can be retried then start a new active call and begin again.
                while (true)
                {
                    GrpcCall <TRequest, TResponse> currentCall;
                    lock (Lock)
                    {
                        // Start new call.
                        OnStartingAttempt();

                        currentCall = _activeCall = HttpClientCallInvoker.CreateGrpcCall <TRequest, TResponse>(Channel, Method, Options, AttemptCount);
                        startCallFunc(currentCall);

                        if (CommitedCallTask.IsCompletedSuccessfully())
                        {
                            // Call has already been commited. This could happen if written messages exceed
                            // buffer limits, which causes the call to immediately become commited and to clear buffers.
                            return;
                        }

                        SetNewActiveCallUnsynchronized(currentCall);
                    }

                    Status?responseStatus;

                    HttpResponseMessage?httpResponse = null;
                    try
                    {
                        currentCall.CancellationToken.ThrowIfCancellationRequested();

                        CompatibilityHelpers.Assert(currentCall._httpResponseTask != null, "Request should have been made if call is not preemptively cancelled.");
                        httpResponse = await currentCall._httpResponseTask.ConfigureAwait(false);

                        responseStatus = GrpcCall.ValidateHeaders(httpResponse, out _);
                    }
                    catch (Exception ex)
                    {
                        currentCall.ResolveException(GrpcCall <TRequest, TResponse> .ErrorStartingCallMessage, ex, out responseStatus, out _);
                    }

                    CancellationTokenSource.Token.ThrowIfCancellationRequested();

                    // Check to see the response returned from the server makes the call commited
                    // Null status code indicates the headers were valid and a "Response-Headers" response
                    // was received from the server.
                    // https://github.com/grpc/proposal/blob/master/A6-client-retries.md#when-retries-are-valid
                    if (responseStatus == null)
                    {
                        // Headers were returned. We're commited.
                        CommitCall(currentCall, CommitReason.ResponseHeadersReceived);

                        responseStatus = await currentCall.CallTask.ConfigureAwait(false);

                        if (responseStatus.GetValueOrDefault().StatusCode == StatusCode.OK)
                        {
                            RetryAttemptCallSuccess();
                        }

                        // Commited so exit retry loop.
                        return;
                    }

                    if (CommitedCallTask.IsCompletedSuccessfully())
                    {
                        // Call has already been commited. This could happen if written messages exceed
                        // buffer limits, which causes the call to immediately become commited and to clear buffers.
                        return;
                    }

                    Status status = responseStatus.Value;

                    var retryPushbackMS = GetRetryPushback(httpResponse);

                    // Failures only count towards retry throttling if they have a known, retriable status.
                    // This stops non-transient statuses, e.g. INVALID_ARGUMENT, from triggering throttling.
                    if (_retryPolicy.RetryableStatusCodes.Contains(status.StatusCode) ||
                        retryPushbackMS < 0)
                    {
                        RetryAttemptCallFailure();
                    }

                    var result = EvaluateRetry(status, retryPushbackMS);
                    Log.RetryEvaluated(Logger, status.StatusCode, AttemptCount, result == null);

                    if (result == null)
                    {
                        TimeSpan delayDuration;
                        if (retryPushbackMS != null)
                        {
                            delayDuration = TimeSpan.FromMilliseconds(retryPushbackMS.GetValueOrDefault());
                            _nextRetryDelayMilliseconds = retryPushbackMS.GetValueOrDefault();
                        }
                        else
                        {
                            delayDuration = TimeSpan.FromMilliseconds(Channel.GetRandomNumber(0, Convert.ToInt32(_nextRetryDelayMilliseconds)));
                        }

                        Log.StartingRetryDelay(Logger, delayDuration);
                        await Task.Delay(delayDuration, CancellationTokenSource.Token).ConfigureAwait(false);

                        _nextRetryDelayMilliseconds = CalculateNextRetryDelay();

                        // Check if dispose was called on call.
                        CancellationTokenSource.Token.ThrowIfCancellationRequested();

                        // Clean up the failed call.
                        currentCall.Dispose();
                    }
                    else
                    {
                        // Handle the situation where the call failed with a non-deadline status, but retry
                        // didn't happen because of deadline exceeded.
                        IGrpcCall <TRequest, TResponse> resolvedCall = (IsDeadlineExceeded() && !(currentCall.CallTask.IsCompletedSuccessfully() && currentCall.CallTask.Result.StatusCode == StatusCode.DeadlineExceeded))
                            ? CreateStatusCall(GrpcProtocolConstants.DeadlineExceededStatus)
                            : currentCall;

                        // Can't retry.
                        // Signal public API exceptions that they should finish throwing and then exit the retry loop.
                        CommitCall(resolvedCall, result.GetValueOrDefault());
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                HandleUnexpectedError(ex);
            }
            finally
            {
                Log.StoppingRetryWorker(Logger);
            }
        }
Exemple #16
0
 public Task <TResponse> GetResponseAsync()
 {
     CompatibilityHelpers.Assert(_responseTcs != null);
     return(_responseTcs.Task);
 }
Exemple #17
0
        private async Task StartCall(Action <GrpcCall <TRequest, TResponse> > startCallFunc)
        {
            GrpcCall <TRequest, TResponse> call;

            lock (Lock)
            {
                if (CommitedCallTask.IsCompletedSuccessfully())
                {
                    // Call has already been commited. This could happen if written messages exceed
                    // buffer limits, which causes the call to immediately become commited and to clear buffers.
                    return;
                }

                OnStartingAttempt();

                call = HttpClientCallInvoker.CreateGrpcCall <TRequest, TResponse>(Channel, Method, Options, AttemptCount);
                _activeCalls.Add(call);

                startCallFunc(call);

                SetNewActiveCallUnsynchronized(call);
            }

            Status?responseStatus;

            HttpResponseMessage?httpResponse = null;

            try
            {
                call.CancellationToken.ThrowIfCancellationRequested();

                CompatibilityHelpers.Assert(call._httpResponseTask != null, "Request should have been made if call is not preemptively cancelled.");
                httpResponse = await call._httpResponseTask.ConfigureAwait(false);

                responseStatus = GrpcCall.ValidateHeaders(httpResponse, out _);
            }
            catch (Exception ex)
            {
                call.ResolveException(GrpcCall <TRequest, TResponse> .ErrorStartingCallMessage, ex, out responseStatus, out _);
            }

            if (CancellationTokenSource.IsCancellationRequested)
            {
                CommitCall(call, CommitReason.Canceled);
                return;
            }

            // Check to see the response returned from the server makes the call commited
            // Null status code indicates the headers were valid and a "Response-Headers" response
            // was received from the server.
            // https://github.com/grpc/proposal/blob/master/A6-client-retries.md#when-retries-are-valid
            if (responseStatus == null)
            {
                // Headers were returned. We're commited.
                CommitCall(call, CommitReason.ResponseHeadersReceived);

                // Wait until the call has finished and then check its status code
                // to update retry throttling tokens.
                var status = await call.CallTask.ConfigureAwait(false);

                if (status.StatusCode == StatusCode.OK)
                {
                    RetryAttemptCallSuccess();
                }
            }
            else
            {
                var status = responseStatus.Value;

                var retryPushbackMS = GetRetryPushback(httpResponse);

                if (retryPushbackMS < 0)
                {
                    RetryAttemptCallFailure();
                }
                else if (_hedgingPolicy.NonFatalStatusCodes.Contains(status.StatusCode))
                {
                    // Needs to happen before interrupt.
                    RetryAttemptCallFailure();

                    // No need to interrupt if we started with no delay and all calls
                    // have already been made when hedging starting.
                    if (_delayInterruptTcs != null)
                    {
                        lock (Lock)
                        {
                            if (retryPushbackMS >= 0)
                            {
                                _pushbackDelay = TimeSpan.FromMilliseconds(retryPushbackMS.GetValueOrDefault());
                            }
                            _delayInterruptTcs.TrySetResult(null);
                        }
                    }
                }
                else
                {
                    CommitCall(call, CommitReason.FatalStatusCode);
                }
            }

            lock (Lock)
            {
                if (IsDeadlineExceeded())
                {
                    // Deadline has been exceeded so immediately commit call.
                    CommitCall(call, CommitReason.DeadlineExceeded);
                }
                else if (_activeCalls.Count == 1 && AttemptCount >= MaxRetryAttempts)
                {
                    // This is the last active call and no more will be made.
                    CommitCall(call, CommitReason.ExceededAttemptCount);
                }
                else if (_activeCalls.Count == 1 && IsRetryThrottlingActive())
                {
                    // This is the last active call and throttling is active.
                    CommitCall(call, CommitReason.Throttled);
                }
                else
                {
                    // Call isn't used and can be cancelled.
                    // Note that the call could have already been removed and disposed if the
                    // hedging call has been finalized or disposed.
                    if (_activeCalls.Remove(call))
                    {
                        call.Dispose();
                    }
                }
            }
        }