Hi ,
The function return can return two types of answers.
a) an "OrderAck" object if everything went well.
b) Or an "OrderException" response if there is a network error.
Is it possible to make a rule that combines the two possible answers?
I have tried this
InternalServerErrorPolicy = Policy.Handle<OrderAck>().or<OrderApiException>(
r => r.StatusCode == 400)
.WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt))
But I have this error:
There is no implicit reference conversion from OrderAck' to "System.Exception'.
An idea how I could to do it ?
Thank you
@MyPierre Step 1b of the Readme covers how to configure a policy which handles results, including examples handling both results and exceptions. The syntax for handling results is .HandleResult<TResult>(Func<TResult, bool>) rather than (what you have tried) .Handle<TResult>(Func<TResult, bool>)
EDIT: Is the OrderApiException being _thrown_ or _returned_? If _thrown_, the above documentation should answer your query. If _returned_, how is the code executed through the policy returning two types of answer? Is it returning them wrapped in something else? (for example as a JSON payload wrapped in an HttpResponse?). Or is it returning a common ancestor class? Please show the code of the call site (either with or without the policy in use), if possible.
Hi, i'm using Poly+Refit and i had this before:
private Task<T> Validate<T>(Func<Task<T>> func) => Policy.WrapAsync(rectryPolicy, breakPolicy).ExecuteAsync(func);
private readonly AsyncRetryPolicy retryPolicy = Policy.Handle<ApiException>()
.WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(ex, timeSpan, context) =>
{
//DO Something
});
private readonly AsyncCircuitBreakerPolicy breakPolicy = Policy.Handle<OperationCanceledException>()
.Or<ApiException>()
.CircuitBreakerAsync(2, TimeSpan.FromSeconds(10),
(ex, timeSpan, context) =>
{
//DO Something
}
Now i want to add HttpStatusCode Validation and in case of 401 - refresh token. I've seen in docs this example:
var authorisationEnsuringPolicy = Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.Unauthorized)
.RetryAsync(
retryCount: 1, // Consider how many retries. If auth lapses and you have valid credentials, one should be enough; too many tries can cause some auth systems to blacklist.
onRetryAsync: async (outcome, retryNumber, context) => FooRefreshAuthorizationAsync(context),
/* more configuration */);
var response = authorisationEnsuringPolicy.ExecuteAsync(context => DoSomethingThatRequiresAuthorization(context), cancellationToken);
But i can't connect it together.
Hi @andreybutko Is this the kind of pattern you are looking for?
private Task<T> Validate<T>(Func<Task<T>> func) => Policy.WrapAsync(retryPolicy, breakPolicy, authorisationEnsuringPolicy).ExecuteAsync(func);
An appropriate way to handle this is to put the re-authorisation policy nearest executing the underlying delegate (as above code does) - because you don't want a simple (successful) not-authorised-then-reauthorise cycle to count as a failure against the circuit-breaker. It also means you can define one retry for re-authorisation, but multiple retries for other failures - as your existing, separate retryPolicy does. You can use the same kind of policy more than once in a PolicyWrap, as my example above shows with retry policies.
If you want to expand your existing retryPolicy and breakPolicy to handle result codes as well as exceptions, see the documentation here. There is a code example titled // Handle both exceptions and return values in one policy. Define a policy handling both exceptions and results something like this:
var policyToHandleBothExceptionsAndResults = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
.RetryAsync(...);
Here are links to three blogs which provide fully worked examples:
@reisenberger Thank you for answer, i just misunderstand docs. But i've stucked at another problem.
Exceptions which throwed in Poly ExecuteAsync() - not throwing to caller method. What i'm doing wrong? Exception throwed but not handled in catch block of calling method.
I have method (Exception not reaching this code.)
Call(Func<Task<T>> apiMethod){
try
{
await ApiMethod()
}
catch(Exception e) //This catch not gonna be reached.
{
//Display message.
}
}
Api calling this method:
public async Task<Something> ApiMethod()
{
return await Validate(async () => await api.ReturnSomething());
}
My POLICY
private Task<T> Validate<T>(Func<Task<T>> func) => Policy.WrapAsync(rectryPolicy, breakPolicy).ExecuteAsync(func);
private readonly AsyncRetryPolicy retryPolicy = Policy.Handle<ApiException>()
.WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(ex, timeSpan, context) =>
{
//DO Something
});
private readonly AsyncCircuitBreakerPolicy breakPolicy = Policy.Handle<OperationCanceledException>()
.Or<ApiException>()
.CircuitBreakerAsync(2, TimeSpan.FromSeconds(10),
(ex, timeSpan, context) =>
{
//DO Something
}
@andreybutko Can you provide a complete, minimal, reproducible example?
~_A guess (might be wrong)_: One possibility could be that you have ended up with nested Tasks somewhere due to the syntax. In other words, T is turning out to be a Task<Something>, not a Something, meaning that the actual return type from ApiMethod() becomes Task<Task<T>>. It's just a possibility worth checking; it might not be the case. But it could explain an exception not being observed/thrown at the outermost caller.~
Hi @andreybutko . I made an attempted repro from your code, and I can't reproduce the problem. In my code sample below, if you uncomment the code throw new ApiException("Exception message"); so that the throw is active, the catch within the method Call<T>(Func<Task<T>> apiMethod) is reached.

Two things you could consider:
(1) If your code behaves differently: How is your code different from my sample?
(2) If you are using the debugger, are you sure the debugger has not just stopped to show you the exception at the "first chance", where the exception is originally thrown? If so, that doesn't mean the catch in the Call<>(...) method won't catch it later - it just means the debugger has stopped earlier, at the first-chance when the exception is first thrown, to show the exception to you. For more on this nuance, see this stack overflow question and our detailed wiki article here.
Please let me know if this helps.
using System;
using System.Threading.Tasks;
using Polly.CircuitBreaker;
using Polly;
using Polly.Retry;
namespace Issue662
{
public class ApiException : Exception
{
public ApiException(string message) : base(message) { }
}
public class Something
{
public string Str { get; set; }
}
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Hello World!");
var result = await Call(ApiMethod);
Console.WriteLine(result.Str);
}
static async Task<T> Call<T>(Func<Task<T>> apiMethod)
{
try
{
return await apiMethod();
}
catch (Exception e) //This catch is reached.
{
Console.WriteLine("Caught exception:" + e.Message);
throw;
}
}
public static async Task<Something> ApiMethod()
{
return await Validate(async () => await ReturnSomething());
}
public static async Task<Something> ReturnSomething()
{
await Task.CompletedTask;
/* throw new ApiException("Exception message"); */ // Uncomment this, and then see that you will see the text "Caught exception:" written by the catch in the Call<T>(...) method
return new Something(){Str = "Some text"};
}
private static Task<T> Validate<T>(Func<Task<T>> func) => Policy.WrapAsync(retryPolicy, breakPolicy).ExecuteAsync(func);
private static readonly AsyncRetryPolicy retryPolicy = Policy.Handle<ApiException>()
.WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(ex, timeSpan, context) =>
{
//DO Something
});
private static readonly AsyncCircuitBreakerPolicy breakPolicy = Policy.Handle<OperationCanceledException>()
.Or<ApiException>()
.CircuitBreakerAsync(2, TimeSpan.FromSeconds(10),
(ex, timeSpan, context) =>
{
//DO Something
}, context => { });
}
}
@reisenberger Hi! Sorry for delay, i didn't noticed your message. The problem was not in Poly, this is was related to not awaited task, which caused this problem. I didn't noticed it at the beginning.
@andreybutko Glad you got it sorted! If you resolve yourself a problem which you have raised with a github project, always let the project know as soon as possible - otherwise project maintainers may be spending unnecessary time trying to help ... 馃檪
Closing this issue as it looks like there are no problems with Polly here and everything is answered.
@MyPierre If you need further assistance, post or open a new issue.
Most helpful comment
@andreybutko Glad you got it sorted! If you resolve yourself a problem which you have raised with a github project, always let the project know as soon as possible - otherwise project maintainers may be spending unnecessary time trying to help ... 馃檪
Closing this issue as it looks like there are no problems with Polly here and everything is answered.
@MyPierre If you need further assistance, post or open a new issue.