Settings for retrying RPCs.
예제 #1
0
        /// <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
        {
        }
예제 #2
0
        /// <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);
                }
            }
        };
예제 #4
0
        /// <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);
            }
        }
예제 #5
0
        // 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);
                }
            }
        };
예제 #6
0
 /// <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;
 }
예제 #7
0
 private CallTiming(RetrySettings retry, Expiration expiration)
 {
     Retry = retry;
     Expiration = expiration;
 }
예제 #8
0
 /// <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);
예제 #9
0
 /// <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);
예제 #10
0
 private CallTiming(RetrySettings retry, Expiration expiration)
 {
     Retry      = retry;
     Expiration = expiration;
 }
예제 #11
0
 /// <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);
예제 #12
0
 private RetryAttempt(RetrySettings settings, DateTime?deadline, IClock clock, IScheduler scheduler, int attemptNumber, TimeSpan jitteredBackoff) =>
 (_settings, _deadline, _clock, _scheduler, AttemptNumber, JitteredBackoff) =