For the complete documentation index, see llms.txt. This page is also available as Markdown.

Supporting Retry and Circuit Breaker

How-to · Applies to Brighter V10

Brighter is a Command Processor and supports a pipeline of Handlers to handle orthogonal requests.

Amongst the valuable uses of orthogonal requests is patterns to support Quality of Service in a distributed environment: Timeout, Retry, and Circuit Breaker.

Even if you don't believe that you are writing a distributed system that needs this protection, consider that as soon as you have multiple processes, such as a database server, you are distributed.

Brighter uses Polly to support Retry and Circuit-Breaker. Through our Russian Doll Model we are able to run the target handler in the context of a Policy Handler, that catches exceptions, and applies a Policy on how to deal with them.

Polly v8 Resilience Pipelines

Brighter supports Polly v8 Resilience Pipelines, which provides a modern, streamlined API for building resilience strategies:

  • Full Polly v8 Support: Access to all Polly v8 resilience strategies (Retry, Circuit Breaker, Timeout, Rate Limiter, Fallback, Hedging)

  • New UseResiliencePipeline Attribute: Replaces UsePolicy attribute for Polly v8 pipelines

  • Enhanced Context Integration: Request context integrates with Polly's resilience context

  • Proper CancellationToken Flow: Cancellation tokens flow correctly through resilience pipelines

  • Type-Scoped Pipelines: Support for per-handler-type pipelines (useful for Circuit Breakers)

Migration from V9 to V10

V9 (Polly v7)
V10 (Polly v8)
Notes

[UsePolicy]

[UseResiliencePipeline]

New attribute for Polly v8 pipelines

[TimeoutPolicy]

[UseResiliencePipeline] with Timeout strategy

TimeoutPolicy deprecated in V10, removed in V11

PolicyRegistry

ResiliencePipelineRegistry<string>

From Polly.Registry namespace

Policies configured with Policy.Handle<>()

Pipelines configured with ResiliencePipelineBuilder

New fluent API

⚠️ DEPRECATION NOTICE: The TimeoutPolicyAttribute is marked as obsolete in V10 and will be removed in V11. Migrate to UseResiliencePipeline with a Timeout strategy instead.


Using Brighter's UseResiliencePipeline Attribute

By adding the UseResiliencePipeline attribute, you instruct the Command Processor to insert a handler (filter) into the pipeline that runs all later steps using that Polly resilience pipeline.

Basic Example

Async Handlers Take UseResiliencePipelineAsync

UseResiliencePipeline decorates a synchronous Handle. An async handler takes the async attribute instead, and pairing them the other way round leaves the pipeline out of the chain:

Both attributes read from the same ResiliencePipelineRegistry<string>, so a pipeline name registered once serves handlers of either kind.

Configuring Resilience Pipelines

To configure a Polly resilience pipeline, you use the ResiliencePipelineRegistry<string> to register pipelines with a name. At runtime, Brighter looks up that pipeline by name.

Retry Strategy Example

Circuit Breaker Strategy Example

Timeout Strategy Example

V10: Use Polly's Timeout strategy instead of the deprecated TimeoutPolicyAttribute:

Handler Usage:


Combining Multiple Strategies

You can combine multiple resilience strategies in a single pipeline. The first strategy you add is the outermost: it wraps every strategy added after it. So to time out each attempt, retry the attempts that fail, and stop retrying while a service is known to be down, add the circuit breaker first and the timeout last.

Retry + Circuit Breaker + Timeout

Added the other way round, the timeout would wrap the retries and limit all of them together to 10 seconds, and the circuit breaker would sit inside the retry.

Handler Usage:

How it works:

  1. Circuit breaker checks if circuit is open (fails fast if open)

  2. Retry wraps the operation (retries on failures)

  3. Timeout wraps each individual attempt (times out after 10 seconds per attempt)

Note: If retries are exhausted, the exception will bubble out to the Circuit Breaker, which will count it as a failure.


Combining Resilience Strategies on a Handler

A handler method takes one [UseResiliencePipeline]. The attribute is not repeatable, so a second one on the same method does not compile (CS0579, "Duplicate 'UseResiliencePipeline' attribute"). To layer strategies, compose them in one pipeline, as Combining Multiple Strategies shows, and name that:

Other attributes, such as [FallbackPolicy] or [RejectMessageOnError], can still sit beside it at their own steps.


Type-Scoped Pipelines

For strategies like Circuit Breaker, you often want a separate instance per handler type (so failures in one handler don't affect others). Use UseTypePipeline = true to scope pipelines by handler type.

How it works: When UseTypePipeline = true, Brighter looks up the pipeline using a key composed of the handler type's full name + the policy name. This allows different instances of the same resilience strategy to be associated uniquely with each handler type.

Configuration:


All Available Polly v8 Strategies

Polly v8 provides the following resilience strategies, all of which can be used with Brighter:

Strategy
Description
Use Case

Retry

Retries operations that fail transiently

Network glitches, temporary service unavailability

Circuit Breaker

Prevents cascading failures by breaking the circuit

Dependency is down, prevent overwhelming failed services

Timeout

Limits operation execution time

Prevent hanging on slow operations

Rate Limiter

Controls the rate of operations

Throttle requests to external APIs

Fallback

Provides alternative value/action on failure

Graceful degradation, cached responses

Hedging

Executes parallel operations and takes first result

Improve latency in distributed systems

Rate Limiter Example

Hedging Example


CancellationToken Integration

Polly v8 resilience pipelines properly integrate with CancellationToken, allowing you to cancel operations in progress.


Request Context Integration

The request context integrates with Polly's resilience context, allowing you to access request metadata within resilience callbacks.

See Request Context documentation for more details on accessing the resilience context from handlers.


Registering Pipelines with CommandProcessor

When creating your CommandProcessor, pass the ResiliencePipelineRegistry<string> to Resilience:

Resilience takes the Polly v8 registry first and an optional Polly v7 IPolicyRegistry<string> second — one call, not two. If you have no legacy policies to carry, call DefaultResilience() instead and Brighter supplies its own registry.

Call AddBrighterDefault whenever you build the registry yourself. Brighter requires a pipeline registered under CommandProcessor.OutboxProducer, and Resilience throws ConfigurationException on its first statement when the registry does not have one — at startup, before any message is sent. Brighter only fills the gap for you when you supply no registry at all, because the container does it with ??=.

Or using dependency injection with ASP.NET Core:

There is no fluent ConfigureResiliencePipelines method on the DI builder. The registry is a settable property on BrighterOptions, set inside the AddBrighter options delegate as above. This is the same ??= case as the builder: assigning your own registry means Brighter never calls AddBrighterDefault for you, so call it yourself.


Retry and Circuit Breaker Best Practices

  1. Use Resilience Pipelines for New Code: Prefer UseResiliencePipeline over legacy UsePolicy for new handlers.

  2. Migrate Away from TimeoutPolicy: Replace [TimeoutPolicy] with [UseResiliencePipeline] using Polly's Timeout strategy. TimeoutPolicy will be removed in V11.

  3. Use Type-Scoped Pipelines for Circuit Breakers: Set UseTypePipeline = true when using Circuit Breakers to avoid failures in one handler affecting others.

  4. Combine Strategies Thoughtfully: When combining timeout, retry, and circuit breaker:

    • Put timeout innermost (times out individual attempts)

    • Put retry in the middle (retries failed attempts)

    • Put circuit breaker outermost (prevents retry when service is known to be down)

  5. Configure Appropriate Delays: Use exponential backoff for retries to avoid overwhelming recovering services.

  6. Monitor Circuit Breaker State: Use OnOpened, OnClosed, and OnHalfOpened callbacks to log and monitor circuit breaker state changes.

  7. Pass CancellationTokens: Always pass and respect CancellationToken in async handlers to support cancellation through resilience pipelines.

  8. Use Request Context: Leverage request context integration for correlation IDs, tracing, and logging within resilience callbacks.


Retry and Circuit Breaker Troubleshooting

Pipeline Not Found Exception

Symptom: InvalidOperationException: Resilience pipeline with key 'MyPipeline' not found

Solution: Ensure you've registered the pipeline with the exact name referenced in the attribute:

Type Pipeline Not Found Exception

Symptom: InvalidOperationException: Resilience pipeline with key 'MyNamespace.MyHandler.MyPipeline' not found

Solution: When using UseTypePipeline = true, register the pipeline with the full key:

TimeoutPolicy Obsolete Warning

Symptom: Compiler warning: 'TimeoutPolicyAttribute' is obsolete: 'It is recommended to use UsePolicyAttribute or UseResiliencePipelineAttribute instead'

Solution: Replace [TimeoutPolicy] with [UseResiliencePipeline] using Polly's Timeout strategy (see Migrating to Polly v8).


Further Reading

Last updated

Was this helpful?