/// <summary> /// Constructs an instance with the specified settings. /// </summary> /// <param name="cancellationToken">Cancellation token that can be used for cancelling the call.</param> /// <param name="expiration"><see cref="Expiration"/> to use, or null for default expiration behavior.</param> /// <param name="retry"><see cref="Retry"/> to use, or null for default retry behavior.</param> /// <param name="headerMutation">Action to modify the headers to send at the beginning of the call.</param> /// <param name="writeOptions"><see cref="global::Grpc.Core.WriteOptions"/> that will be used for the call.</param> /// <param name="propagationToken"><see cref="ContextPropagationToken"/> for propagating settings from a parent call.</param> /// <param name="responseMetadataHandler">Action to invoke when response metadata is received.</param> /// <param name="trailingMetadataHandler">Action to invoke when trailing metadata is received.</param> public CallSettings( CancellationToken?cancellationToken, Expiration expiration, RetrySettings retry, Action <Metadata> headerMutation, WriteOptions writeOptions, ContextPropagationToken propagationToken, Action <Metadata> responseMetadataHandler, Action <Metadata> trailingMetadataHandler) #pragma warning disable CS0618 // Type or member is obsolete : this(cancellationToken, null, expiration, retry, headerMutation, writeOptions, propagationToken, responseMetadataHandler, trailingMetadataHandler) #pragma warning restore CS0618 // Type or member is obsolete { }
/// <summary> /// Returns a sequence of retry attempts. The sequence has <see cref="RetrySettings.MaxAttempts"/> elements, and calling /// <see cref="ShouldRetry(Exception)"/> on the last attempt will always return false. This overload assumes no deadline, /// and so does not require a clock. /// </summary> /// <param name="settings">The retry settings to create a sequence for. Must not be null.</param> /// <param name="scheduler">The scheduler to use for delays.</param> /// <param name="initialBackoffOverride">An override value to allow an initial backoff which is not the same /// as <see cref="RetrySettings.InitialBackoff"/>. This is typically to allow an "immediate first retry".</param> /// <returns></returns> public static IEnumerable <RetryAttempt> CreateRetrySequence(RetrySettings settings, IScheduler scheduler, TimeSpan?initialBackoffOverride = null) { GaxPreconditions.CheckNotNull(settings, nameof(settings)); GaxPreconditions.CheckNotNull(scheduler, nameof(scheduler)); TimeSpan backoff = initialBackoffOverride ?? settings.InitialBackoff; for (int i = 1; i <= settings.MaxAttempts; i++) { yield return(new RetryAttempt(settings, null, null, scheduler, i, settings.BackoffJitter.GetDelay(backoff))); backoff = settings.NextBackoff(backoff); } }
// By design, the code is mostly duplicated between the async and sync calls. // Async retry internal static Func <TRequest, CallSettings, Task <TResponse> > WithRetry <TRequest, TResponse>( this Func <TRequest, CallSettings, Task <TResponse> > fn, IClock clock, IScheduler scheduler, Func <TResponse, Task> postResponse) => async(request, callSettings) => { RetrySettings retrySettings = callSettings.Retry; if (retrySettings == null) { return(await fn(request, callSettings).ConfigureAwait(false)); } DateTime?overallDeadline = callSettings.Expiration.CalculateDeadline(clock); // Every attempt should use the same deadline, calculated from the start of the call. if (callSettings.Expiration?.Type == ExpirationType.Timeout) { callSettings = callSettings.WithDeadline(overallDeadline.Value); } TimeSpan backoffDelay = retrySettings.InitialBackoff; int attempt = 0; while (true) { try { attempt++; var response = await fn(request, callSettings).ConfigureAwait(false); if (postResponse != null) { await postResponse(response).ConfigureAwait(false); } return(response); } catch (RpcException e) when(attempt < retrySettings.MaxAttempts && retrySettings.RetryFilter(e)) { TimeSpan actualDelay = retrySettings.BackoffJitter.GetDelay(backoffDelay); DateTime expectedRetryTime = clock.GetCurrentDateTimeUtc() + actualDelay; if (expectedRetryTime > overallDeadline) { throw; } await scheduler.Delay(actualDelay, callSettings.CancellationToken.GetValueOrDefault()).ConfigureAwait(false); backoffDelay = retrySettings.NextBackoff(backoffDelay); } } };
/// <summary> /// Returns a sequence of retry attempts. The sequence has <see cref="RetrySettings.MaxAttempts"/> elements, and calling /// <see cref="ShouldRetry(Exception)"/> on the last attempt will always return false. /// </summary> /// <param name="settings">The retry settings to create a sequence for. Must not be null.</param> /// <param name="scheduler">The scheduler to use for delays.</param> /// <param name="deadline">The overall deadline for the operation.</param> /// <param name="clock">The clock to use to compare the current time with the deadline.</param> /// <param name="initialBackoffOverride">An override value to allow an initial backoff which is not the same /// as <see cref="RetrySettings.InitialBackoff"/>. This is typically to allow an "immediate first retry".</param> /// <returns></returns> public static IEnumerable <RetryAttempt> CreateRetrySequence(RetrySettings settings, IScheduler scheduler, DateTime?deadline, IClock clock, TimeSpan?initialBackoffOverride = null) { GaxPreconditions.CheckNotNull(settings, nameof(settings)); GaxPreconditions.CheckNotNull(clock, nameof(clock)); GaxPreconditions.CheckNotNull(scheduler, nameof(scheduler)); // It's simpler not to need nullable logic when computing deadlines. var effectiveDeadline = deadline ?? DateTime.MaxValue; TimeSpan backoff = initialBackoffOverride ?? settings.InitialBackoff; for (int i = 1; i <= settings.MaxAttempts; i++) { yield return(new RetryAttempt(settings, effectiveDeadline, clock, scheduler, i, settings.BackoffJitter.GetDelay(backoff))); backoff = settings.NextBackoff(backoff); } }
// By design, the code is mostly duplicated between the async and sync calls. // Async retry internal static Func <TRequest, CallSettings, Task <TResponse> > WithRetry <TRequest, TResponse>( this Func <TRequest, CallSettings, Task <TResponse> > fn, IClock clock, IScheduler scheduler, Func <TResponse, Task> postResponse) => async(request, callSettings) => { RetrySettings retrySettings = callSettings.Timing?.Retry; if (retrySettings == null) { return(await fn(request, callSettings).ConfigureAwait(false)); } DateTime?overallDeadline = retrySettings.TotalExpiration.CalculateDeadline(clock); TimeSpan retryDelay = retrySettings.RetryBackoff.Delay; TimeSpan callTimeout = retrySettings.TimeoutBackoff.Delay; while (true) { DateTime attemptDeadline = clock.GetCurrentDateTimeUtc() + callTimeout; // Note: this handles a null total deadline due to "<" returning false if overallDeadline is null. DateTime combinedDeadline = overallDeadline < attemptDeadline ? overallDeadline.Value : attemptDeadline; CallSettings attemptCallSettings = callSettings.WithCallTiming(CallTiming.FromDeadline(combinedDeadline)); try { var response = await fn(request, attemptCallSettings).ConfigureAwait(false); if (postResponse != null) { await postResponse(response).ConfigureAwait(false); } return(response); } catch (RpcException e) when(retrySettings.RetryFilter(e)) { TimeSpan actualDelay = retrySettings.DelayJitter.GetDelay(retryDelay); DateTime expectedRetryTime = clock.GetCurrentDateTimeUtc() + actualDelay; if (expectedRetryTime > overallDeadline) { throw; } await scheduler.Delay(actualDelay, callSettings.CancellationToken.GetValueOrDefault()).ConfigureAwait(false); retryDelay = retrySettings.RetryBackoff.NextDelay(retryDelay); callTimeout = retrySettings.TimeoutBackoff.NextDelay(callTimeout); } } };
/// <summary> /// Constructs an instance with the specified settings. /// </summary> /// <param name="cancellationToken">Cancellation token that can be used for cancelling the call.</param> /// <param name="expiration"><see cref="Expiration"/> to use, or null for default expiration behavior.</param> /// <param name="retry"><see cref="Retry"/> to use, or null for default retry behavior.</param> /// <param name="headerMutation">Action to modify the headers to send at the beginning of the call.</param> /// <param name="writeOptions"><see cref="global::Grpc.Core.WriteOptions"/> that will be used for the call.</param> /// <param name="propagationToken"><see cref="ContextPropagationToken"/> for propagating settings from a parent call.</param> /// <param name="responseMetadataHandler">Action to invoke when response metadata is received.</param> /// <param name="trailingMetadataHandler">Action to invoke when trailing metadata is received.</param> public CallSettings( CancellationToken?cancellationToken, Expiration expiration, RetrySettings retry, Action <Metadata> headerMutation, WriteOptions writeOptions, ContextPropagationToken propagationToken, Action <Metadata> responseMetadataHandler, Action <Metadata> trailingMetadataHandler) { CancellationToken = cancellationToken; Expiration = expiration; Retry = retry; HeaderMutation = headerMutation; WriteOptions = writeOptions; PropagationToken = propagationToken; ResponseMetadataHandler = responseMetadataHandler; TrailingMetadataHandler = trailingMetadataHandler; }
private CallTiming(RetrySettings retry, Expiration expiration) { Retry = retry; Expiration = expiration; }
/// <summary> /// Create a <see cref="CallTiming"/> with retry. /// </summary> /// <param name="retry">The <see cref="RetrySettings"/> for a call.</param> /// <returns>A <see cref="CallTiming"/> with the specified retry settings.</returns> public static CallTiming FromRetry(RetrySettings retry) => new CallTiming(GaxPreconditions.CheckNotNull(retry, nameof(retry)), null);
/// <summary> /// Creates a <see cref="CallSettings"/> for the specified retry settings, or returns null /// if <paramref name="retry"/> is null. /// </summary> /// <param name="retry">The call timing for the new settings.</param> /// <returns>A new instance or null if <paramref name="retry"/> is null..</returns> public static CallSettings FromRetry(RetrySettings retry) => retry == null ? null : new CallSettings(null, null, null, retry, null, null, null);
private RetryAttempt(RetrySettings settings, DateTime?deadline, IClock clock, IScheduler scheduler, int attemptNumber, TimeSpan jitteredBackoff) => (_settings, _deadline, _clock, _scheduler, AttemptNumber, JitteredBackoff) =