Search code examples
c#.netasync-awaitpollyretry-logic

Result<T> - if result has faulted, throw an exception in order to trigger Polly retries


I recently found out about a nuget called LanguageExt.Core and why it is not so efficient to throw exceptions while handling them via middleware or so.

Speaking of it, I would like to know what is the best way to simply check if the result has faulted, so I can throw the exception in order to trigger Polly's retry pattern logic.

The best I could think of:

private async Task RunAsync(Uri uri)
{
    await ConnectAsync(uri);

    var result = await ConnectAsync(uri);

    if (result.IsFaulted)
    {
        // Cannot access result.Exception because it's internal
    }

    _ = result.IfFail(ex =>
    {
        _logger.LogError(ex, "Error while connecting to the endpoint");
        throw ex;
    });

    ...
private async Task<Result<Unit>> ConnectAsync(Uri uri)
{
    if (_ws is not null)
    {
        return new Result<Unit>(new InvalidOperationException("The websocket client is already connected"));
    }

    var ws = Options.ClientFactory();
    try
    {
        using var connectTimeout = new CancellationTokenSource(Options.Timeout);
        await ws.ConnectAsync(uri, connectTimeout.Token).ConfigureAwait(false);
    }
    catch
    {
        ws.Dispose();
        return new Result<Unit>(new InvalidOperationException("Failed to connect to the endpoint"));
    }

    await _connectedEvent.InvokeAsync(new ConnectedEventArgs(uri));

    _ws = ws;
    IsRunning = true;

    return Unit.Default;
}

Solution

  • Based on the comments it seems like that ConnectAsync can fail with an InvalidOperationException inside a Result object. In this particular case the Result's IsFaulted is set to true only if an IOE is passed to the Result's ctor.

    So, if you want to perform a retry then you should define your policy something like this:

    var retryPolicy = Policy<Result<Unit>>
        .HandleResult(r => r.IsFaulted)
        .WaitAndRetryAsync(...)