Reformat/cleanup
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m10s
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m10s
This commit is contained in:
@@ -1,50 +1,53 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ZB.MOM.WW.CBDDC.Core.Exceptions;
|
||||
using ZB.MOM.WW.CBDDC.Core.Network;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using ZB.MOM.WW.CBDDC.Core.Exceptions;
|
||||
using ZB.MOM.WW.CBDDC.Core.Network;
|
||||
using TimeoutException = ZB.MOM.WW.CBDDC.Core.Exceptions.TimeoutException;
|
||||
|
||||
namespace ZB.MOM.WW.CBDDC.Core.Resilience;
|
||||
|
||||
/// <summary>
|
||||
/// Provides retry logic for transient failures.
|
||||
/// Provides retry logic for transient failures.
|
||||
/// </summary>
|
||||
public class RetryPolicy : IRetryPolicy
|
||||
{
|
||||
private readonly IPeerNodeConfigurationProvider _peerNodeConfigurationProvider;
|
||||
private readonly ILogger<RetryPolicy> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RetryPolicy"/> class.
|
||||
/// </summary>
|
||||
/// <param name="peerNodeConfigurationProvider">The provider for retry configuration values.</param>
|
||||
/// <param name="logger">The logger instance.</param>
|
||||
public RetryPolicy(IPeerNodeConfigurationProvider peerNodeConfigurationProvider, ILogger<RetryPolicy>? logger = null)
|
||||
{
|
||||
_logger = logger ?? NullLogger<RetryPolicy>.Instance;
|
||||
_peerNodeConfigurationProvider = peerNodeConfigurationProvider
|
||||
?? throw new ArgumentNullException(nameof(peerNodeConfigurationProvider));
|
||||
}
|
||||
public class RetryPolicy : IRetryPolicy
|
||||
{
|
||||
private readonly ILogger<RetryPolicy> _logger;
|
||||
private readonly IPeerNodeConfigurationProvider _peerNodeConfigurationProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Executes an operation with retry logic.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The result type returned by the operation.</typeparam>
|
||||
/// <param name="operation">The asynchronous operation to execute.</param>
|
||||
/// <param name="operationName">The operation name used for logging.</param>
|
||||
/// <param name="cancellationToken">A token used to cancel retry delays.</param>
|
||||
public async Task<T> ExecuteAsync<T>(
|
||||
Func<Task<T>> operation,
|
||||
string operationName,
|
||||
CancellationToken cancellationToken = default)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RetryPolicy" /> class.
|
||||
/// </summary>
|
||||
/// <param name="peerNodeConfigurationProvider">The provider for retry configuration values.</param>
|
||||
/// <param name="logger">The logger instance.</param>
|
||||
public RetryPolicy(IPeerNodeConfigurationProvider peerNodeConfigurationProvider,
|
||||
ILogger<RetryPolicy>? logger = null)
|
||||
{
|
||||
_logger = logger ?? NullLogger<RetryPolicy>.Instance;
|
||||
_peerNodeConfigurationProvider = peerNodeConfigurationProvider
|
||||
?? throw new ArgumentNullException(nameof(peerNodeConfigurationProvider));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an operation with retry logic.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The result type returned by the operation.</typeparam>
|
||||
/// <param name="operation">The asynchronous operation to execute.</param>
|
||||
/// <param name="operationName">The operation name used for logging.</param>
|
||||
/// <param name="cancellationToken">A token used to cancel retry delays.</param>
|
||||
public async Task<T> ExecuteAsync<T>(
|
||||
Func<Task<T>> operation,
|
||||
string operationName,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var config = await _peerNodeConfigurationProvider.GetConfiguration();
|
||||
Exception? lastException = null;
|
||||
|
||||
for (int attempt = 1; attempt <= config.RetryAttempts; attempt++)
|
||||
{
|
||||
for (var attempt = 1; attempt <= config.RetryAttempts; attempt++)
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Executing {Operation} (attempt {Attempt}/{Max})",
|
||||
@@ -55,7 +58,7 @@ public class RetryPolicy : IRetryPolicy
|
||||
catch (Exception ex) when (attempt < config.RetryAttempts && IsTransient(ex))
|
||||
{
|
||||
lastException = ex;
|
||||
var delay = config.RetryDelayMs * attempt; // Exponential backoff
|
||||
int delay = config.RetryDelayMs * attempt; // Exponential backoff
|
||||
|
||||
_logger.LogWarning(ex,
|
||||
"Operation {Operation} failed (attempt {Attempt}/{Max}). Retrying in {Delay}ms...",
|
||||
@@ -63,36 +66,31 @@ public class RetryPolicy : IRetryPolicy
|
||||
|
||||
await Task.Delay(delay, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastException != null)
|
||||
{
|
||||
_logger.LogError(lastException,
|
||||
"Operation {Operation} failed after {Attempts} attempts",
|
||||
operationName, config.RetryAttempts);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError(
|
||||
"Operation {Operation} failed after {Attempts} attempts",
|
||||
operationName, config.RetryAttempts);
|
||||
}
|
||||
if (lastException != null)
|
||||
_logger.LogError(lastException,
|
||||
"Operation {Operation} failed after {Attempts} attempts",
|
||||
operationName, config.RetryAttempts);
|
||||
else
|
||||
_logger.LogError(
|
||||
"Operation {Operation} failed after {Attempts} attempts",
|
||||
operationName, config.RetryAttempts);
|
||||
|
||||
throw new CBDDCException("RETRY_EXHAUSTED",
|
||||
$"Operation '{operationName}' failed after {config.RetryAttempts} attempts",
|
||||
lastException!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an operation with retry logic (void return).
|
||||
/// </summary>
|
||||
/// <param name="operation">The asynchronous operation to execute.</param>
|
||||
/// <param name="operationName">The operation name used for logging.</param>
|
||||
/// <param name="cancellationToken">A token used to cancel retry delays.</param>
|
||||
public async Task ExecuteAsync(
|
||||
Func<Task> operation,
|
||||
string operationName,
|
||||
CancellationToken cancellationToken = default)
|
||||
/// <summary>
|
||||
/// Executes an operation with retry logic (void return).
|
||||
/// </summary>
|
||||
/// <param name="operation">The asynchronous operation to execute.</param>
|
||||
/// <param name="operationName">The operation name used for logging.</param>
|
||||
/// <param name="cancellationToken">A token used to cancel retry delays.</param>
|
||||
public async Task ExecuteAsync(
|
||||
Func<Task> operation,
|
||||
string operationName,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await ExecuteAsync(async () =>
|
||||
{
|
||||
@@ -104,13 +102,13 @@ public class RetryPolicy : IRetryPolicy
|
||||
private static bool IsTransient(Exception ex)
|
||||
{
|
||||
// Network errors are typically transient
|
||||
if (ex is NetworkException or System.Net.Sockets.SocketException or System.IO.IOException)
|
||||
if (ex is NetworkException or SocketException or IOException)
|
||||
return true;
|
||||
|
||||
// Timeout errors are transient
|
||||
if (ex is Exceptions.TimeoutException or OperationCanceledException)
|
||||
if (ex is TimeoutException or OperationCanceledException)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user