You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
89 lines
3.3 KiB
89 lines
3.3 KiB
using System.Threading.Tasks;
|
|
using System.Threading;
|
|
using AIK.Models.HttpPolicy;
|
|
using AIK.Service.IService;
|
|
using Microsoft.Extensions.Logging;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace AIK.Service.Service
|
|
{
|
|
public class RetryPolicyService : IRetryPolicyService
|
|
{
|
|
private readonly ILogger<RetryPolicyService> _logger;
|
|
private readonly RetryPolicyConfig _config;
|
|
|
|
public RetryPolicyService(ILogger<RetryPolicyService> logger, RetryPolicyConfig config = null)
|
|
{
|
|
_logger = logger;
|
|
_config = config ?? new RetryPolicyConfig();
|
|
}
|
|
|
|
public async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation, string operationName, CancellationToken cancellationToken = default)
|
|
{
|
|
var exceptions = new List<Exception>();
|
|
|
|
for (int retryCount = 0; retryCount < _config.MaxRetryCount; retryCount++)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
try
|
|
{
|
|
return await operation();
|
|
}
|
|
catch (Exception ex) when (IsTransientException(ex) && retryCount < _config.MaxRetryCount - 1)
|
|
{
|
|
exceptions.Add(ex);
|
|
_logger.LogWarning(ex,
|
|
"操作 {OperationName} 第 {RetryCount} 次失败,{RetryDelay}ms 后重试",
|
|
operationName, retryCount + 1, _config.RetryDelay.TotalMilliseconds);
|
|
|
|
await Task.Delay(_config.RetryDelay, cancellationToken);
|
|
}
|
|
catch (Exception ex) when (IsTransientException(ex))
|
|
{
|
|
exceptions.Add(ex);
|
|
_logger.LogError(ex,
|
|
"操作 {OperationName} 最终失败,已重试 {MaxRetryCount} 次",
|
|
operationName, _config.MaxRetryCount);
|
|
|
|
throw new AggregateException($"操作 {operationName} 失败,已重试 {_config.MaxRetryCount} 次", exceptions);
|
|
}
|
|
}
|
|
|
|
throw new InvalidOperationException("不应该执行到这里,未知的情况");
|
|
}
|
|
|
|
public async Task ExecuteWithRetryAsync(Func<Task> operation, string operationName, CancellationToken cancellationToken = default)
|
|
{
|
|
await ExecuteWithRetryAsync<object>(async () =>
|
|
{
|
|
await operation();
|
|
return null;
|
|
}, operationName, cancellationToken);
|
|
}
|
|
|
|
private bool IsTransientException(Exception ex)
|
|
{
|
|
return ex is TimeoutException ||
|
|
ex is TaskCanceledException ||
|
|
ex is HttpRequestException ||
|
|
(ex is OperationCanceledException && !(ex is TaskCanceledException)) ||
|
|
IsNetworkRelatedException(ex);
|
|
}
|
|
|
|
private bool IsNetworkRelatedException(Exception ex)
|
|
{
|
|
var message = ex.Message.ToLower();
|
|
return message.Contains("connection") ||
|
|
message.Contains("network") ||
|
|
message.Contains("timeout") ||
|
|
message.Contains("socket") ||
|
|
message.Contains("dns");
|
|
}
|
|
}
|
|
}
|
|
|