Skip to content

Instantly share code, notes, and snippets.

@henkmollema
Last active August 7, 2023 19:04
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save henkmollema/ba21bb90c35580a7189e77624d9ed8d1 to your computer and use it in GitHub Desktop.
Save henkmollema/ba21bb90c35580a7189e77624d9ed8d1 to your computer and use it in GitHub Desktop.
Transient failure handling for MediatR using Polly
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.Logging;
using Polly;
namespace Foo.Bar
{
/// <summary>
/// Applies a retry policy on the MediatR request.
/// Apply this attribute to the MediatR <see cref="IRequest"/> class (not on the handler).
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class RetryPolicyAttribute : Attribute
{
private int _retryCount = 3;
private int _sleepDuration = 200;
/// <summary>
/// Gets or sets the amount of times to retry the execution.
/// Each retry the sleep duration is incremented by <see cref="SleepDuration"/>.
/// Defaults to 3 times.
/// </summary>
public int RetryCount
{
get => _retryCount;
set
{
if (value < 1)
{
throw new ArgumentException("Retry count must be higher than 1.", nameof(value));
}
_retryCount = value;
}
}
/// <summary>
/// Gets or sets the sleep duration in milliseconds.
/// Each retry the sleep duration gets incremented by this value.
/// Defaults to 200 milliseconds.
/// </summary>
public int SleepDuration
{
get => _sleepDuration;
set
{
if (value < 1)
{
throw new ArgumentException("Sleep duration must be higher than 1ms.", nameof(value));
}
_sleepDuration = value;
}
}
}
/// <summary>
/// Wraps request handler execution of requests decorated with the <see cref="RetryPolicyAttribute"/>
/// inside a policy to handle transient failures and retry the execution.
/// </summary>
public class RetryPolicyBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger<RetryPolicyBehavior<TRequest, TResponse>> _logger;
public RetryPolicyBehavior(ILogger<RetryPolicyBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var retryAttr = typeof(TRequest).GetCustomAttribute<RetryPolicyAttribute>();
if (retryAttr == null)
{
return await next();
}
return await Policy.Handle<Exception>()
.WaitAndRetryAsync(
retryAttr.RetryCount,
i => TimeSpan.FromMilliseconds(i * retryAttr.SleepDuration),
(ex, ts, _) => _logger.LogWarning(ex, "Failed to execute handler for request {Request}, retrying after {RetryTimeSpan}s: {ExceptionMessage}", typeof(TRequest).Name, ts.TotalSeconds, ex.Message))
.ExecuteAsync(async () => await next());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment