protected override void Dispose(bool disposing)
        {
            lock (Lock)
            {
                base.Dispose(disposing);

                _activeCall?.Dispose();
            }
        }
        private GrpcCall <TRequest, TResponse> CreateGrpcCall <TRequest, TResponse>(
            Method <TRequest, TResponse> method,
            CallOptions options,
            out Action disposeAction)
            where TRequest : class
            where TResponse : class
        {
            if (_client.BaseAddress == null)
            {
                throw new InvalidOperationException("Unable to send the gRPC call because no server address has been configured. " +
                                                    "Set HttpClient.BaseAddress on the HttpClient used to created to gRPC client.");
            }

            CancellationTokenSource?linkedCts = null;

            // Use propagated deadline if it is small than the specified deadline
            if (Deadline < options.Deadline)
            {
                options = options.WithDeadline(Deadline);
            }

            if (CancellationToken.CanBeCanceled)
            {
                if (options.CancellationToken.CanBeCanceled)
                {
                    // If both propagated and options cancellation token can be canceled
                    // then set a new linked token of both
                    linkedCts = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken, options.CancellationToken);
                    options   = options.WithCancellationToken(linkedCts.Token);
                }
                else
                {
                    options = options.WithCancellationToken(CancellationToken);
                }
            }

            var call = new GrpcCall <TRequest, TResponse>(method, options, Clock, _loggerFactory);

            // Clean up linked cancellation token
            disposeAction = linkedCts != null
                ? () => { call.Dispose(); linkedCts !.Dispose(); }
                : (Action)call.Dispose;

            return(call);
        }
Exemple #3
0
        private GrpcCall <TRequest, TResponse> CreateGrpcCall <TRequest, TResponse>(
            Method <TRequest, TResponse> method,
            CallOptions options,
            out Action disposeAction)
            where TRequest : class
            where TResponse : class
        {
            CancellationTokenSource?linkedCts = null;

            // Use propagated deadline if it is small than the specified deadline
            if (Deadline < options.Deadline)
            {
                options = options.WithDeadline(Deadline);
            }

            if (CancellationToken.CanBeCanceled)
            {
                if (options.CancellationToken.CanBeCanceled)
                {
                    // If both propagated and options cancellation token can be canceled
                    // then set a new linked token of both
                    linkedCts = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken, options.CancellationToken);
                    options   = options.WithCancellationToken(linkedCts.Token);
                }
                else
                {
                    options = options.WithCancellationToken(CancellationToken);
                }
            }

            var call = new GrpcCall <TRequest, TResponse>(method, options, Clock, _loggerFactory);

            // Clean up linked cancellation token
            disposeAction = linkedCts != null
                ? () => { call.Dispose(); linkedCts !.Dispose(); }
                : (Action)call.Dispose;

            return(call);
        }
Exemple #4
0
 public ValueTask DisposeAsync()
 {
     GrpcCall?.Dispose();
     return(default);
Exemple #5
0
        private async Task StartCall(Action <GrpcCall <TRequest, TResponse> > startCallFunc)
        {
            GrpcCall <TRequest, TResponse>?call = null;

            try
            {
                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);

                    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?responseStatus;

                HttpResponseMessage?httpResponse = null;
                try
                {
                    httpResponse = await call.HttpResponseTask.ConfigureAwait(false);

                    responseStatus = GrpcCall.ValidateHeaders(httpResponse, out _);
                }
                catch (RpcException ex)
                {
                    // A "drop" result from the load balancer should immediately stop the call,
                    // including ignoring the retry policy.
                    var dropValue = ex.Trailers.GetValue(GrpcProtocolConstants.DropRequestTrailer);
                    if (dropValue != null && bool.TryParse(dropValue, out var isDrop) && isDrop)
                    {
                        CommitCall(call, CommitReason.Drop);
                        return;
                    }

                    call.ResolveException(GrpcCall <TRequest, TResponse> .ErrorStartingCallMessage, ex, out responseStatus, out _);
                }
                catch (Exception ex)
                {
                    call.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(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();
                    }
                    return;
                }

                lock (Lock)
                {
                    var status = responseStatus.Value;
                    if (IsDeadlineExceeded())
                    {
                        // Deadline has been exceeded so immediately commit call.
                        CommitCall(call, CommitReason.DeadlineExceeded);
                    }
                    else if (!_hedgingPolicy.NonFatalStatusCodes.Contains(status.StatusCode))
                    {
                        CommitCall(call, CommitReason.FatalStatusCode);
                    }
                    else if (_activeCalls.Count == 1 && AttemptCount >= MaxRetryAttempts)
                    {
                        // This is the last active call and no more will be made.
                        CommitCall(call, CommitReason.ExceededAttemptCount);
                    }
                    else
                    {
                        // Call failed but it didn't exceed deadline, have a fatal status code
                        // and there are remaining attempts available. Is a chance it will be retried.
                        //
                        // Increment call failure out. Needs to happen before checking throttling.
                        RetryAttemptCallFailure();

                        if (_activeCalls.Count == 1 && IsRetryThrottlingActive())
                        {
                            // This is the last active call and throttling is active.
                            CommitCall(call, CommitReason.Throttled);
                        }
                        else
                        {
                            var retryPushbackMS = GetRetryPushback(httpResponse);

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

                            // 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();
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                HandleUnexpectedError(ex);
            }
            finally
            {
                if (CommitedCallTask.IsCompletedSuccessfully() && CommitedCallTask.Result == call)
                {
                    // Wait until the commited call is finished and then clean up hedging call.
                    await call.CallTask.ConfigureAwait(false);

                    Cleanup();
                }
            }
        }
 public void Dispose() => GrpcCall?.Dispose();