Monday, 31 July 2017

Transient Fault Handling in Xamarin.Forms using Polly

Previously I wrote about transient fault handling in Xamarin.Forms, and discussed an implementation of the retry pattern that uses exponential backoff. The advantage of the implementation was that the retry pattern was implemented without requiring any library code, for those sensitive to bloating their application package size.

There are, however, transient fault handling libraries available and the go to library for .NET is Polly, which includes fluent support for the retry pattern, circuit breaker pattern, bulkhead isolation, and more. In Polly, these patterns are implemented via fault handling policies, which handle specific exceptions thrown by, or results returned by, the delegates that are executed through the policy.

This blog post will discuss using Polly’s RetryPolicy, which implements the retry pattern.

Implementation

The sample application, which can be found on GitHub, is similar to the sample application from my previous blog post, with the custom implementation of the retry pattern replaced with Polly’s.

Initialization

The App class in the sample application initializes the classes that are responsible for communicating with the REST service:

TodoManager = new TodoItemManager( new RestService( new ResilientRequestProvider()));

The RestService class provides data to the TodoItemManager class, with the RestService class making REST calls using the ResilientRequestProvider class, which uses Polly to implement the retry pattern, using exponential backoff.

ResilientRequestProvider

The following code example shows the GetAsync method from the ResilientRequestProvider class, which makes GET requests to a specified URI:

async Task<HttpResponseMessage> HttpInvoker(Func<Task<HttpResponseMessage>> operation) { return await retryPolicy.ExecuteAsync(operation); } public async Task<TResult> GetAsync<TResult>(string uri) { string serialized = null; var httpResponse = await HttpInvoker(async () => { var response = await client.GetAsync(uri); response.EnsureSuccessStatusCode(); serialized = await response.Content.ReadAsStringAsync(); return response; }); return JsonConvert.DeserializeObject<TResult>(serialized); }

The GetAsync method code is identical to my previous blog post. The lambda expression is passed to the HttpInvoker method, which in turn passes it to the ExecuteAsync method of the RetryPolicy instance. Therefore, the code in the lambda expression is what will be retried if the GET request fails.

The RetryPolicy type is a Polly type, which represents a retry policy that can be applied to delegates that return a value of type T. The following code example show the RetryPolicy<T> declaration from the sample application:

RetryPolicy<HttpResponseMessage> retryPolicy;

This declares a RetryPolicy that can be applied to delegates that return a HttpResponseMessage.

There are 3 steps to using a fault handling policy, including the RetryPolicy<T> type, in Polly:

  1. Specify the exceptions you want the policy to handle.
  2. Optionally specify the returned results you want the policy to handle.
  3. Specify how the policy should handle any faults.

The following code example shows all three steps for defining the operation of the RetryPolicy<T> instance:

HttpStatusCode[] httpStatusCodesToRetry = { HttpStatusCode.RequestTimeout, // 408 HttpStatusCode.InternalServerError, // 500 HttpStatusCode.BadGateway, // 502 HttpStatusCode.ServiceUnavailable, // 503 HttpStatusCode.GatewayTimeout // 504 }; retryPolicy = Policy .Handle<TimeoutException>() .Or<HttpRequestException>() .OrResult<HttpResponseMessage>(r => httpStatusCodesToRetry.Contains(r.StatusCode)) .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (response, delay, retryCount, context) => { Debug.WriteLine($"Retry {retryCount} after {delay.Seconds} seconds delay due to {response.Exception.Message}"); });

The Policy.Handle method is used to specify the exceptions and results you want the policy to handle. Here it specifies that the delegate should be retried if a TimeoutException or HttpRequestException occurs, or if the resulting HttpResponseMessage includes any of the HTTP status codes contained in the httpStatusCodesToRetry array. Therefore, the RetryPolicy<T> instance handles both exceptions and return values in a single policy.

After specifying the exceptions and results you want the policy to handle, you must specify how the policy should handle any faults. Several of Polly’s methods can be used here, including Retry, RetryForever, WaitAndRetry, or WaitAndRetryForever (along with their async variants). I chose to use one of the WaitAndRetryAsync overloads, for which three arguments must be specified:

  1. The maximum number of retries to make. Note that the overall number of attempts that will be made is one plus the number of retries configured. Therefore, four attempts can be made with this code: the initial attempt, plus up to three retries.
  2. A delegate, expressed here as a lambda expression, that calculates the duration to wait between retries based on the current retry attempt.
  3. An action to be called on each retry, that provides the current exception, duration, retry count, and context.

The advantage of using this overload is that it allows an exponential backoff strategy to be specified through calculation, and to output messages as retries are attempted.

Executing an Action through the Policy

The overall operation is that the GetAsync method in the ResilientRequestProvider class invokes the HttpInvoker method, passing an action that represents the GET request. The HttpInvoker method invokes Polly’s RetryPolicy.ExecuteAsync method, passing the received action as an argument.

The retry policy then attempts the action passed in via the ExecuteAsync method. If the action executes successfully, the return value is returned and the policy exits. If the action throws an unhandled exception, it’s rethrown and the policy exits – no further retries are made. However, if the action throws a handled exception, the policy performs the following operations:

  • Counts the exception.
  • Checks whether another retry is permitted:
    • If not, the exception is rethrown and the policy terminates.
    • If another try is permitted, the policy calculates the duration to wait from the supplied sleep duration configuration, waits for the calculation duration, and returns to the beginning of the cycle to retry executing the action again.

Running the Sample Application

The sample application, which can be found on GitHub, connects to a read-only REST service hosted by Xamarin, and it’s most likely that when running the sample the GET operation will succeed on first attempt. To observe the retry pattern in operation, change the RestUrl property in the Constants class to an address that doesn’t exist – this can be accomplished by adding a random character to the end of the existing string. Then run the application and observe the output window in Visual Studio. You should see something like:

Retry 1 after 2 seconds delay due to 404 (Not Found) Thread started: <Thread Pool> #10 Thread started: <Thread Pool> #11 Retry 2 after 4 seconds delay due to 404 (Not Found) Retry 3 after 8 seconds delay due to 404 (Not Found) ERROR: 404 (Not Found)

This shows the GET operation being retried 3 times, after an exponentially increasing delay. Remember that the number of retries, and backoff strategy can be specified with Polly’s WaitAndRetry method. This allows the RetryPolicy to be customized to fit individual application requirements.

The advantage of using Polly over implementing your own retry pattern is that Polly includes multiple transient fault handling patterns that can easily be combined for additional resilience when handling transient faults.

Summary

Polly is a .NET transient fault handling library, which includes fluent support for the retry pattern. In Polly, the retry pattern is implemented by the RetryPolicy type, which handles specific exceptions thrown by, or results returned by, the delegates that are executed through the policy.

The RetryPolicy type is highly configurable, allowing you to specify the exceptions to be handled, the return results to be handled, and how the policy should handle any faults.

The advantage of using Polly over implementing your own retry pattern is that Polly includes multiple transient fault handling patterns that can easily be combined for additional resilience when handling transient faults.

No comments:

Post a Comment