public static FromDeadline ( System.DateTime deadline ) : |
||
deadline | System.DateTime | The absolute deadline for a call without retry. |
return |
// 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> /// Returns a <see cref="CallSettings"/> which will have an effective deadline of at least <paramref name="deadline"/>. /// If <paramref name="settings"/> already observes an earlier deadline (with respect to <paramref name="clock"/>), /// or if <paramref name="deadline"/> is null, the original settings will be returned. /// </summary> /// <param name="settings">Existing settings. May be null, meaning there are currently no settings.</param> /// <param name="deadline">Deadline to enforce. May be null, meaning there is no deadline to enforce.</param> /// <param name="clock">The clock to use when computing deadlines. Must not be null.</param> /// <returns>The call settings to use to observe the given deadline.</returns> public static CallSettings WithEarlierDeadline(this CallSettings settings, DateTime?deadline, IClock clock) { GaxPreconditions.CheckNotNull(clock, nameof(clock)); // No deadline? Return what we already have. if (deadline == null) { return(settings); } // No settings, or no timing in the settings? Create a simple expiration. if (settings == null || settings.Timing == null) { // WithCallTiming (above) is an extension method - it's fine for settings to be null. return(settings.WithCallTiming(CallTiming.FromDeadline(deadline.Value))); } // Okay, we have settings with a genuine timing component and a new deadline. // We may still return the existing settings, if the new deadline is later than the existing // one in the settings. But if the new deadline is earlier, we need to build new settings to accommodate // it. var timing = settings.Timing; switch (timing.Type) { // For simple expirations, we can just replace one deadline for another simple one, // if necessary. case CallTimingType.Expiration: return(timing.Expiration.CalculateDeadline(clock) < deadline.Value ? settings : settings.WithCallTiming(CallTiming.FromDeadline(deadline.Value))); // For retries, we may need to create a new RetrySettings with all the same other aspects, // but a new deadline. case CallTimingType.Retry: if (timing.Retry.TotalExpiration.CalculateDeadline(clock) < deadline.Value) { return(settings); } var newTiming = CallTiming.FromRetry(timing.Retry.WithTotalExpiration(Expiration.FromDeadline(deadline.Value))); return(settings.WithCallTiming(newTiming)); default: throw new InvalidOperationException("Invalid call timing type"); } }