/// <summary> /// Initializes a new instance of the <see cref="WindowsFileSystemWatcher"/> class. /// </summary> /// <param name="watcherExecute"> Instance of IWatcherExecute</param> /// <param name="retryOption"> Instance of RetryOption, default can be null</param> public WindowsFileSystemWatcher(IWatcherExecute watcherExecute, RetryOption retryOption = null) { // Domain or WindowsService needs to inject IWatcherExecute implementation to this WindowsFileSystemWatcher // to avoid coupling of WindowsFileSystemWatcher and WatcherExecute in Persistence Layer this.watcherExecute = watcherExecute; this.retryOption = retryOption; }
/// <summary> /// Exponentially with jitter on each retries /// </summary> /// <typeparam name="T">The type of response.</typeparam> /// <param name="option">The retry option.</param> /// <param name="context">Request context.</param> /// <param name="action">Action to execute.</param> /// <param name="predicate">Handle result predicate.</param> /// <param name="onRetry">Handle custom on retries.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <remarks> /// exponential back-off: 2, 4, 8 etc /// plus some jitter: up to 1 second /// </remarks> public static Task <T> SimpleExponentialJitterBackoff <T>( RetryOption option, Context context, Func <Context, Task <T> > action, Func <T, bool> predicate, Action <DelegateResult <T>, TimeSpan, Context> onRetry) { if (option is null) { throw new ArgumentNullException(nameof(option)); } var jitterer = new Random(); return(Policy .HandleResult(predicate) .WaitAndRetryAsync( 5, retryAttempt => { return TimeSpan.FromSeconds(Math.Pow(2, retryAttempt - 1)) + TimeSpan.FromMilliseconds(jitterer.Next(option.JitterStart, option.JitterEnd)); }, onRetry) .ExecuteAsync(async(arg) => await action(arg).ConfigureAwait(false), context)); }
public static void AddNRetry(this IServiceCollection services, Action <RetryOption> actionOptions) { RetryOption retryOption = new RetryOption(); actionOptions.Invoke(retryOption); services.AddSingleton(retryOption); }
/// <summary> /// 检查是否满足重试条件 /// </summary> /// <param name="option"></param> /// <param name="record"></param> /// <param name="retryCount"></param> /// <returns></returns> private bool CanRetry(RetryOption option, RequestRecord record, int retryCount) { if (option == null || option.RetryCount <= 0 || retryCount >= option.RetryCount) { return(false); } if (option.CanRetry != null) { return(option.CanRetry(record)); } if ((option.TransientErrorRetry ?? false) && record.Exception != null) { if (record.Exception is TimeoutException || record.Exception is OperationCanceledException || record.Exception is HttpRequestException) { return(true); } return(false); } var statusCode = (int)record.Response.StatusCode; var method = record.Response.RequestMessage.Method.ToString(); if (option.RetryStatusCodes?.Contains(statusCode) ?? false && (option.RetryMethods?.Contains(method, StringComparer.CurrentCultureIgnoreCase) ?? false)) { return(true); } return(false); }
/// <summary> /// Initializes a new instance of the <see cref="MirrorFreezeCopyService"/> class. /// </summary> public MirrorFreezeCopyService() { this.watcherConfig = new WatcherConfig(); this.watcherConfigService = new WatcherConfigService(this.watcherConfig); this.Precheck(); this.retryOption = this.watcherConfigService.GetRetryOptionFromXMLFile(ConfigFilePath); this.listWatcher = this.watcherConfigService.PopulateWatchersFromXMLFile(ConfigFilePath); this.listWatcherExecute = new List <WatcherExecute>(); this.listWindowsFileSystemWatcher = new List <WindowsFileSystemWatcher>(); }
/// <summary> /// Create a linear retry delay of 1, 2, 3, 4s. /// </summary> /// <typeparam name="T">The type of response.</typeparam> /// <param name="option">The retry option.</param> /// <param name="context">Request context.</param> /// <param name="action">Action to execute.</param> /// <param name="predicate">Handle result predicate.</param> /// <param name="onRetry">Handle custom on retries.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public static Task <T> LinearBackoff <T>( RetryOption option, Context context, Func <Context, Task <T> > action, Func <T, bool> predicate, Action <DelegateResult <T>, TimeSpan, Context> onRetry) { if (option is null) { throw new ArgumentNullException(nameof(option)); } var delay = Backoff.LinearBackoff(TimeSpan.FromMilliseconds(option.MinDelayIsMs), retryCount: option.MaxRetry); return(HandleRetry(context, action, predicate, onRetry, delay)); }
/// <summary> /// First, new Random() is not quite random, when it comes to reducing concurrency. /// You can get multiple instances with the same seed, if many retries are issued simultaneously. /// https://github.com/App-vNext/Polly/issues/530. /// </summary> /// <typeparam name="T">The type of response.</typeparam> /// <param name="option">The retry option.</param> /// <param name="context">Request context.</param> /// <param name="action">Action to execute.</param> /// <param name="predicate">Handle result predicate.</param> /// <param name="onRetry">Handle custom on retries.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public static Task <T> DecorrelatedJitterBackoffV2 <T>( RetryOption option, Context context, Func <Context, Task <T> > action, Func <T, bool> predicate, Action <DelegateResult <T>, TimeSpan, Context> onRetry) { if (option is null) { throw new ArgumentNullException(nameof(option)); } var maxDelay = TimeSpan.FromSeconds(option.MaxDelayIsMs); var delay = Backoff .DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromMilliseconds(option.MinDelayIsMs), retryCount: option.MaxRetry) .Select(s => TimeSpan.FromTicks(Math.Min(s.Ticks, maxDelay.Ticks))); return(HandleRetry(context, action, predicate, onRetry, delay)); }
/// <summary> /// Create an retry after backoff retry delay which returned by server. /// </summary> /// <typeparam name="T">The type of response.</typeparam> /// <param name="option">The retry option.</param> /// <param name="context">Request context.</param> /// <param name="action">Action to execute.</param> /// <param name="predicate">Handle result predicate.</param> /// <param name="sleepDurationProvider">Sleep duration provider.</param> /// <param name="onRetryAsync">Handle custom on retries.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public static Task <T> RetryAfterBackOff <T>( RetryOption option, Context context, Func <Context, Task <T> > action, Func <T, bool> predicate, Func <int, DelegateResult <T>, Context, TimeSpan> sleepDurationProvider, Func <DelegateResult <T>, TimeSpan, int, Context, Task> onRetryAsync) { if (option is null) { throw new ArgumentNullException(nameof(option)); } return(Policy .HandleResult(predicate) .WaitAndRetryAsync( retryCount: option.MaxRetry, sleepDurationProvider, onRetryAsync) .ExecuteAsync(async(arg) => await action(arg).ConfigureAwait(false), context)); }
/// <summary> /// 重试逻辑 /// </summary> /// <param name="option"></param> /// <param name="request"></param> /// <param name="sending"></param> /// <param name="records"></param> /// <param name="timeoutToken"></param> /// <param name="retryCount"></param> /// <returns></returns> private async Task <RequestRecord> SendWithRetryAsync(RetryOption option, HttpRequest request, Func <HttpRequest, Task <HttpResponse> > sending, List <RequestRecord> records, CancellationToken timeoutToken, int retryCount = 0) { if (timeoutToken.IsCancellationRequested) { throw new ExecutionHttpException(request, new TimeoutException($"请求超时,在{option.TotalTimeout}ms内未获取到结果")); } records = records ?? new List <RequestRecord>(); var record = await HandleException(request, async() => { var response = await sending(request); var requestRecord = new RequestRecord { Request = request, Response = response, Duration = response.Duration }; return(requestRecord); }, requestRecord => { records.Add(requestRecord); return(requestRecord); }); if (!CanRetry(option, record, retryCount)) { return(record); } //重试间隔时间 var waitTime = option.WaitIntervals[retryCount]; //等待重试间隔时间 await Waiting(request, waitTime, option.TotalTimeout, timeoutToken); request = request.Clone(); //开始重试 return(await SendWithRetryAsync(option, request, sending, records, timeoutToken, ++retryCount)); }