/// <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); } }