15 minlesson

Timeout and Policy Composition

Timeout and Policy Composition

Timeouts prevent operations from hanging indefinitely. Combining multiple policies creates comprehensive resilience strategies.

Timeout Policies

Optimistic Timeout

Relies on the operation supporting cancellation:

csharp
1var timeout = Policy
2 .TimeoutAsync(TimeSpan.FromSeconds(10));
3
4await timeout.ExecuteAsync(async ct =>
5{
6 // ct is a CancellationToken - pass it to operations
7 return await httpClient.GetAsync(url, ct);
8}, CancellationToken.None);

Pessimistic Timeout

Forces timeout even if operation doesn't support cancellation:

csharp
1var timeout = Policy
2 .TimeoutAsync(
3 TimeSpan.FromSeconds(10),
4 TimeoutStrategy.Pessimistic);
5
6// Warning: the underlying operation continues running!

Timeout with Callback

csharp
1var timeout = Policy
2 .TimeoutAsync(
3 TimeSpan.FromSeconds(10),
4 onTimeoutAsync: (context, timeSpan, task) =>
5 {
6 Console.WriteLine($"Timeout after {timeSpan.TotalSeconds}s");
7 return Task.CompletedTask;
8 });

Combining Policies with Wrap

csharp
1// Individual policies
2var timeout = Policy.TimeoutAsync(TimeSpan.FromSeconds(10));
3
4var retry = Policy
5 .Handle<HttpRequestException>()
6 .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));
7
8var breaker = Policy
9 .Handle<HttpRequestException>()
10 .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
11
12// Combine them
13var combined = Policy.WrapAsync(timeout, breaker, retry);
14
15// Execution order: timeout → breaker → retry → operation
16await combined.ExecuteAsync(() => httpClient.GetAsync(url));

Policy Execution Order

1timeout.Execute(
2 breaker.Execute(
3 retry.Execute(
4 operation()
5 )
6 )
7)

Order matters!

  • Timeout should wrap everything (fail if total time exceeded)
  • Circuit breaker wraps retry (each failed retry set counts as one failure)
  • Retry is innermost (retries the actual operation)

Timeout Per Retry vs Overall

Timeout Per Retry

csharp
1// 10s timeout for EACH attempt
2var timeoutPerRetry = Policy.TimeoutAsync(TimeSpan.FromSeconds(10));
3var retry = Policy
4 .Handle<HttpRequestException>()
5 .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));
6
7// Retry wraps timeout
8var policy = Policy.WrapAsync(retry, timeoutPerRetry);
9
10// Total max time: (10s + 1s) * 3 = 33s

Overall Timeout

csharp
1var overallTimeout = Policy.TimeoutAsync(TimeSpan.FromSeconds(30));
2var retry = Policy
3 .Handle<HttpRequestException>()
4 .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));
5
6// Timeout wraps retry
7var policy = Policy.WrapAsync(overallTimeout, retry);
8
9// Total max time: 30s (retries stop if overall timeout hit)

Both

csharp
1var overallTimeout = Policy.TimeoutAsync(TimeSpan.FromSeconds(30));
2var perRetryTimeout = Policy.TimeoutAsync(TimeSpan.FromSeconds(10));
3var retry = Policy
4 .Handle<HttpRequestException>()
5 .Or<TimeoutRejectedException>() // Handle timeout as retryable
6 .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));
7
8// Overall wraps retry wraps per-retry timeout
9var policy = Policy.WrapAsync(overallTimeout, retry, perRetryTimeout);

Fallback Policy

Provide a default when all else fails:

csharp
1var fallback = Policy<string>
2 .Handle<Exception>()
3 .FallbackAsync(
4 fallbackValue: "Default data",
5 onFallbackAsync: (result, context) =>
6 {
7 Console.WriteLine($"Using fallback: {result.Exception?.Message}");
8 return Task.CompletedTask;
9 });
10
11var retry = Policy<string>
12 .Handle<Exception>()
13 .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));
14
15var policy = Policy.WrapAsync(fallback, retry);
16
17// Returns "Default data" if all retries fail
18string result = await policy.ExecuteAsync(() => FetchDataAsync());

Complete Resilience Stack

csharp
1class ResilientClient
2{
3 private readonly HttpClient _client;
4 private readonly IAsyncPolicy<HttpResponseMessage> _policy;
5
6 public ResilientClient()
7 {
8 _client = new HttpClient();
9
10 // 1. Overall timeout (30 seconds max)
11 var overallTimeout = Policy
12 .TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(30));
13
14 // 2. Circuit breaker
15 var breaker = Policy<HttpResponseMessage>
16 .Handle<HttpRequestException>()
17 .OrResult(r => (int)r.StatusCode >= 500)
18 .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
19
20 // 3. Retry with exponential backoff
21 var retry = Policy<HttpResponseMessage>
22 .Handle<HttpRequestException>()
23 .Or<TimeoutRejectedException>()
24 .OrResult(r => (int)r.StatusCode >= 500)
25 .WaitAndRetryAsync(3, attempt =>
26 TimeSpan.FromSeconds(Math.Pow(2, attempt)));
27
28 // 4. Per-request timeout (10 seconds per attempt)
29 var perRequestTimeout = Policy
30 .TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10));
31
32 // Combine: overall → breaker → retry → per-request → operation
33 _policy = Policy.WrapAsync(
34 overallTimeout,
35 breaker,
36 retry,
37 perRequestTimeout);
38 }
39
40 public async Task<string> GetAsync(string url)
41 {
42 try
43 {
44 var response = await _policy.ExecuteAsync(
45 ct => _client.GetAsync(url, ct),
46 CancellationToken.None);
47
48 response.EnsureSuccessStatusCode();
49 return await response.Content.ReadAsStringAsync();
50 }
51 catch (BrokenCircuitException)
52 {
53 throw new ServiceUnavailableException("Service is down");
54 }
55 catch (TimeoutRejectedException)
56 {
57 throw new ServiceUnavailableException("Request timed out");
58 }
59 }
60}

Policy Registry

Manage policies centrally:

csharp
1var registry = new PolicyRegistry
2{
3 { "StandardRetry", Policy.Handle<Exception>().RetryAsync(3) },
4 { "AggressiveRetry", Policy.Handle<Exception>().WaitAndRetryAsync(5, _ => TimeSpan.FromSeconds(1)) },
5 { "StandardBreaker", Policy.Handle<Exception>().CircuitBreakerAsync(3, TimeSpan.FromSeconds(30)) }
6};
7
8// Use by name
9var policy = registry.Get<IAsyncPolicy>("StandardRetry");
10await policy.ExecuteAsync(() => DoWorkAsync());

Key Takeaways

  • Timeout prevents operations from hanging
  • Optimistic timeout requires CancellationToken support
  • Policy.Wrap combines multiple policies
  • Order: Timeout → Breaker → Retry (outermost to innermost)
  • Use both overall and per-retry timeouts
  • Fallback provides graceful degradation
  • Policy registry centralizes policy management