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); }
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); }
public ValueTask DisposeAsync() { GrpcCall?.Dispose(); return(default);
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();