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