Polly .NET Resilience and Transient-Fault-Handling Library

repository·main·Indexed 11 days ago

https://github.com/app-vnext/polly

A .NET library for implementing resilience and transient-fault-handling strategies. Polly v8 introduces resilience pipelines created via ResiliencePipelineBuilder, allowing developers to compose reactive strategies (Retry, Circuit Breaker, Fallback, Hedging) and proactive strategies (Timeout, Rate Limiter) in a fluent, thread-safe manner. It includes support for chaos engineering via Simmy starting in version 8.3.0 and provides core abstractions through the Polly.Core NuGet package.

Tokens
76.7K
Snippets
165
Records
210
Agent score
95%

What's inside Polly

  1. Overview of Polly resilience policies

    main

    Polly is a .NET library designed for resilience and transient-fault-handling. It allows you to define and express various resilience strategies in a fluent and thread-safe manner. Supported policies include:

    • Retry: Re-attempting an operation when it fails.
    • Circuit Breaker: Temporarily stopping operations to prevent system overload when a failure threshold is met.
    • Timeout: Ensuring an operation does not run longer than a specified duration.
    • Rate-limiting: Controlling the rate of operations to prevent overwhelming a resource.
    • Hedging: Running multiple concurrent operations and taking the result of the fastest one.
    • Fallback: Providing a default or alternative response when an operation fails.
  2. Polly.Extensions overview

    main

    Polly.Extensions provides additional capabilities for the core Polly resilience library, specifically focusing on integration with modern .NET application patterns. It enables:

    • Dependency Injection Support: Integrates Polly with IServiceCollection, allowing you to configure and resolve resilience policies within the standard .NET DI container.
    • Telemetry Support: Provides built-in telemetry by implementing TelemetryListener. This translates native Polly events into actionable logs and metrics for monitoring resilience behavior.
  3. Use Polly-Contrib extensions

    main

    Polly-Contrib is a collection of community-contributed policies and enhancements for Polly. Instead of being part of the core Polly library, these are distributed as separate NuGet packages.

    Available extensions include:

    • Polly.Contrib.WaitAndRetry: Helper methods for common wait-and-retry strategies, including a jitter formula combining exponential back-off with even random distribution.
    • Polly.Contrib.AzureFunctions.CircuitBreaker: A distributed circuit-breaker implemented in Azure Functions, accessible via HTTP.
    • Simmy: A chaos engineering library for injecting faults.
    • Polly.Contrib.TimingPolicy: A policy for publishing execution timings of calls.
    • Polly.Contrib.LoggingPolicy: A policy for logging handled exceptions/faults before rethrowing or bubbling them.
  4. Chaos engineering with Simmy

    main

    Simmy is an extension for the Polly library that enables chaos engineering and fault injection. It provides strategies to selectively inject faults, latency, custom behaviors, or fake results into your system to test its resilience against turbulent conditions.

    Simmy strategies are implemented as ResilienceStrategy objects, meaning they integrate directly into the Polly v8 ResiliencePipeline architecture.

    // Example of building a pipeline with chaos strategies
    var builder = new ResiliencePipelineBuilder<HttpResponseMessage>();
    
    builder
        .AddRetry(new RetryStrategyOptions<HttpResponseMessage> { /* ... */ })
        .AddChaosFault(0.02, () => new InvalidOperationException("Injected by chaos strategy!"))
        .AddChaosLatency(0.50, TimeSpan.FromMinutes(1));
  5. Overview of Polly resilience strategies

    main

    Polly provides several resilience strategies to handle transient faults and improve application stability. You can use these strategies individually or combine them into pipelines to handle various failure scenarios:

    • Retry: Automatically retries an operation if it fails, useful for temporary issues.
    • Circuit Breaker: Stops execution if a service is detected as broken or busy, preventing resource waste and allowing the system to recover.
    • Timeout: Limits the time an operation can take, freeing up resources if a task hangs.
    • Rate Limiter: Controls the number of requests made or accepted to prevent overloading a system.
    • Fallback: Provides an alternative action or result when the primary operation fails, improving user experience.
    • Hedging: Executes multiple concurrent operations and accepts the result from the fastest one to improve responsiveness.
  6. What is ResilienceContext and how to use it

    main

    The ResilienceContext class provides an execution-scoped instance that accompanies an operation through a Polly resilience pipeline. It allows you to share data and information between different phases of execution (pre-execution, mid-execution, and post-execution) and across different strategies within the same pipeline.

    Key Properties

    • OperationKey: A user-defined identifier for the operation (useful for telemetry).
    • CancellationToken: The cancellation token linked to the operation.
    • Properties: An instance of ResilienceProperties used to attach custom data.
    • ContinueOnCapturedContext: Specifies whether the asynchronous execution should continue on the captured context.

    Best Practice: Defining Property Keys

    To maintain and discover keys easily, define a static class to hold your ResiliencePropertyKey<T> instances. For simple scenarios, you can create them on the fly as they are cheap, struct-based APIs.

    // Recommended: Define keys in a static class
    public static class MyResilienceKeys
    {
        public static readonly ResiliencePropertyKey<string> Key1 = new("my-key-1");
        public static readonly ResiliencePropertyKey<int> Key2 = new("my-key-2");
    }
    
    // Usage in a pipeline strategy
    ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
        .AddRetry(new() 
        {
            OnRetry = static args => 
            {
                if (args.Context.Properties.TryGetValue(MyResilienceKeys.Key1, out var data)) 
                {
                    Console.WriteLine($"OnRetry, Custom Data: {data}");
                }
                return default;
            }
        })
        .Build();
  7. How Hedging manages ResilienceContexts

    main

    The hedging strategy executes multiple actions concurrently. To prevent concurrency issues when these actions access the same ResilienceContext, the strategy provides each hedged action with its own unique context.

    There are two types of contexts:

    • Primary context: The original resilience context received by the hedging strategy.
    • Action context: A deep copy of the primary context containing a distinct CancellationToken specific to a single hedged action.

    Lifecycle and Merging:

    1. The strategy deep-clones the primary context to create action contexts.
    2. Once a hedged action produces an accepted result, its ActionContext is merged back into the PrimaryContext.
    3. During merging, new properties created in the action context are added to the primary context, and existing properties are updated or set to null (soft-deleted).
    4. All other ongoing hedged actions are cancelled and discarded, awaiting cancellation propagation before the strategy completes.
  8. How the Circuit Breaker resilience strategy works

    main

    The Circuit Breaker is a reactive resilience strategy that prevents execution if an underlying resource is detected as unhealthy via sampling.

    How it works:

    1. Sampling: The strategy monitors the failure-to-success ratio of executions within a SamplingDuration.
    2. Breaking: If the failure ratio exceeds a predefined FailureRatio and a MinimumThroughput is met, the circuit "breaks" (opens).
    3. Blocking: While the circuit is open, all new executions are immediately shortcut by throwing a BrokenCircuitException.
    4. Probing: After a BreakDuration expires, the circuit enters a HalfOpen state to perform a probe. If the probe succeeds, the circuit closes; otherwise, it remains open.

    Important Notes:

    • Exception Handling: The Circuit Breaker rethrows all exceptions, including those it is configured to handle. It monitors faults but does not manage retries. To handle retries, combine it with a Retry strategy.
    • Throughput Requirement: If the MinimumThroughput is not reached within the SamplingDuration, the FailureRatio is ignored, and the circuit will not break even if all executions failed.
    // Example of a circuit breaker that breaks if 50% of actions fail
    // within a 10-second window, provided at least 8 actions occurred.
    var optionsComplex = new CircuitBreakerStrategyOptions
    {
        FailureRatio = 0.5,
        SamplingDuration = TimeSpan.FromSeconds(10),
        MinimumThroughput = 8,
        BreakDuration = TimeSpan.FromSeconds(30),
        ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>()
    };
  9. Use the OnRejected delegate in Rate Limiter

    main

    The OnRejected delegate is called just before the strategy throws a RateLimiterRejectedException.

    Use OnRejected when you need to perform an action (like logging) immediately when a limit is hit, especially in pipelines with multiple strategies. For example, if a Retry strategy is wrapping a Rate Limiter, the Retry strategy will catch the exception, but the OnRejected delegate provides the immediate notification that the limit was exceeded.

    Note: The RetryAfter value is not available inside the OnRejected callback; it is only available on the caught exception.

    var withOnRejected = new ResiliencePipelineBuilder()
        .AddRateLimiter(new RateLimiterStrategyOptions
        {
            DefaultRateLimiterOptions = new ConcurrencyLimiterOptions
            {
                PermitLimit = 10
            },
            OnRejected = args =>
            {
                Console.WriteLine("Rate limit has been exceeded");
                return default;
            }
        }).Build();
  10. Core components required to implement a new resilience strategy

    main

    To extend Polly with a new resilience strategy, you must implement the following four components:

    1. The Strategy: A class that inherits from ResilienceStrategy.
    2. Options: A class that inherits from ResilienceStrategyOptions to hold the strategy's configuration (e.g., retry counts, timeouts).
    3. Pipeline Extensions: Extension methods for ResiliencePipelineBuilder or ResiliencePipelineBuilder<T> to allow users to register your strategy into a pipeline using a method like .AddMyStrategy().
    4. Arguments: Custom argument types (typically structs) used by delegates to pass event-specific information to consumers.

    Note on Options: Options can contain common types (int, bool, TimeSpan), asynchronous delegates for events or value generation, and Arguments objects.

  11. Key differences between Polly v7 and v8

    main

    Polly v8 introduces several fundamental changes to the API and mental model:

    • Terminology Change: The term Policy is replaced by Strategy (e.g., resilience strategies).
    • Resilience Pipelines: Instead of individual policies, v8 uses ResiliencePipeline and ResiliencePipeline<T> to combine multiple strategies. This replaces the concept of a Policy Wrap.
    • Unified Execution: The separate sync and async interfaces (like ISyncPolicy and IAsyncPolicy) are unified into ResiliencePipeline, which supports both Execute and ExecuteAsync flows.
    • No Static APIs: v8 moves away from static APIs to improve testability and extensibility.
    • Options-based Configuration: Strategies are configured using options objects (e.g., RetryStrategyOptions), providing better flexibility.
    • Performance & Telemetry: v8 features built-in telemetry and low-allocation, high-performance APIs.