How do I make the following Polly Retry policy let the user specify a custom handler such as the one below:
.Handle<WebSocketException>(exception => IsWebSocketErrorRetryEligible(exception))
Snippet
public static async Task DoAsync(Func<Task> action, TimeSpan retryInterval, int retryCount = 3)
{
await DoAsync<object?>(async () =>
{
await action();
return null;
}, retryInterval, retryCount);
}
public static async Task<T> DoAsync<T>(Func<Task<T>> action, TimeSpan retryWait, int retryCount = 0)
{
var policyResult = await Policy
.Handle<Exception>()
.WaitAndRetryAsync(retryCount, retryAttempt => retryWait)
.ExecuteAndCaptureAsync(action);
if (policyResult.Outcome == OutcomeType.Failure)
{
throw policyResult.FinalException;
}
return policyResult.Result;
}
private bool IsWebSocketErrorRetryEligible(WebSocketException wex)
{
if (wex.InnerException is HttpRequestException)
{
// assume transient failure
return true;
}
return wex.WebSocketErrorCode switch
{
WebSocketError.ConnectionClosedPrematurely => true, // maybe a network blip?
WebSocketError.Faulted => true, // maybe a server error or cosmic radiation?
WebSocketError.HeaderError => true, // maybe a transient server error?
WebSocketError.InvalidMessageType => false,
WebSocketError.InvalidState => false,
WebSocketError.NativeError => true, // maybe a transient server error?
WebSocketError.NotAWebSocket => Regex.IsMatch(wex.Message, "\\b(5\\d\\d|408)\\b"), // 5xx errors timeouts
WebSocketError.Success => true, // should never happen, but try again
WebSocketError.UnsupportedProtocol => false,
WebSocketError.UnsupportedVersion => false,
_ => throw new ArgumentOutOfRangeException(nameof(wex))
};
}
CodePudding user response:
If you want to allow the consumer of your DoAsync
method do define a custom predicate for the retry policy then you can do that like this:
public static async Task<T> DoAsync<T, TEx>(Func<Task<T>> action, TimeSpan retryWait, int retryCount = 0, Func<TEx, bool> exceptionFilter) where TEx: Exception
{
var policyResult = await Policy
.Handle<TEx>(exceptionFilter)
.WaitAndRetryAsync(retryCount, retryAttempt => retryWait)
.ExecuteAndCaptureAsync(action);
if (policyResult.Outcome == OutcomeType.Failure)
{
throw policyResult.FinalException;
}
return policyResult.Result;
}
- Added
TEx
as a new generic type parameter and constrained it asException
- Added a new
Func<TEx, bool>
parameter to the method - Replaced your catch-all-exception trigger (
.Handle<Exception>()
) to the user provided one
Note #1
If you need to allow to your consumers to define any number of exception filters then this technique can't be used because the TEx
is part of the signature.
Note #2
There is no need to use ExecuteAndCaptureAsync
and then branch based on the Outcome
because you just re-implemented how the ExecuteAsync
works.
public static async Task<T> DoAsync<T, TEx>(Func<Task<T>> action, TimeSpan retryWait, int retryCount = 0, Func<TEx, bool> exceptionFilter) where TEx: Exception
=> await Policy
.Handle<TEx>(exceptionFilter)
.WaitAndRetryAsync(retryCount, retryAttempt => retryWait)
.ExecuteAsync(action);