-
Notifications
You must be signed in to change notification settings - Fork 128
/
Copy pathDemo06_WaitAndRetryNestingCircuitBreaker.cs
165 lines (146 loc) · 6.99 KB
/
Demo06_WaitAndRetryNestingCircuitBreaker.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
using System.Diagnostics;
using Polly.CircuitBreaker;
using PollyDemos.Helpers;
using PollyDemos.OutputHelpers;
namespace PollyDemos;
/// <summary>
/// <para>
/// Demonstrates using the Retry strategy nesting CircuitBreaker.<br/>
/// Loops through a series of HTTP requests, keeping track of each requested<br/>
/// item and reporting server failures when encountering exceptions.
/// </para>
/// <para>
/// Discussion: What if the underlying system was completely down? <br/>
/// Keeping retrying would be pointless...<br/>
/// and would leave the client hanging, retrying for successes which never come.
/// </para>
/// <para>
/// Enter circuit-breaker:
/// <list type="bullet">
/// <item>After too many failures, breaks the circuit for a period, during which it blocks calls and fails fast.</item>
/// <item>Protects the downstream system from too many calls if it's really struggling (reduces load, so it can recover).</item>
/// <item>Allows the client to get a fail response fast, not wait for ages, if downstream is AWOL.</item>
/// </list>
/// </para>
/// <para>
/// Observations:
/// <list type="bullet">
/// <item>Note how after the circuit decides to break, subsequent calls fail faster.</item>
/// <item>Note how breaker gives underlying system time to recover...<br/>
/// by the time circuit closes again, underlying system has recovered!</item>
/// </list>
/// </para>
/// <para>
/// How to read the demo logs:
/// <list type="bullet">
/// <item>"Response: ... request #N(...)": Response received on time.</item>
/// <item>"Request N failed with: BrokenCircuitException": Request is shortcut due to broken circuit.</item>
/// </list>
/// </para>
/// </summary>
public class Demo06_WaitAndRetryNestingCircuitBreaker : DemoBase
{
private int eventualFailuresDueToCircuitBreaking;
private int eventualFailuresForOtherReasons;
public override string Description =>
"This demonstrates CircuitBreaker. When an underlying system is completely down or seriously struggling, it can be better to fail fast and not put calls through.";
public override async Task ExecuteAsync(CancellationToken cancellationToken, IProgress<DemoProgress> progress)
{
ArgumentNullException.ThrowIfNull(progress);
EventualSuccesses = 0;
Retries = 0;
eventualFailuresDueToCircuitBreaking = 0;
eventualFailuresForOtherReasons = 0;
TotalRequests = 0;
PrintHeader(progress);
var retryStrategy = new ResiliencePipelineBuilder().AddRetry(new()
{
// Exception filtering - we don't retry if the inner circuit-breaker judges the underlying system is out of commission.
ShouldHandle = new PredicateBuilder().Handle<Exception>(ex => ex is not BrokenCircuitException),
MaxRetryAttempts = int.MaxValue,
Delay = TimeSpan.FromMilliseconds(200),
OnRetry = args =>
{
var exception = args.Outcome.Exception!;
progress.Report(ProgressWithMessage($"Strategy logging: {exception.Message}", Color.Yellow));
Retries++;
return default;
}
}).Build();
// Define our circuit breaker strategy: break if the action fails at least 4 times in a row.
var circuitBreakerStrategy = new ResiliencePipelineBuilder().AddCircuitBreaker(new()
{
ShouldHandle = new PredicateBuilder().Handle<Exception>(),
FailureRatio = 1.0,
SamplingDuration = TimeSpan.FromSeconds(2),
MinimumThroughput = 4,
BreakDuration = TimeSpan.FromSeconds(3),
OnOpened = args =>
{
progress.Report(ProgressWithMessage(
$".Breaker logging: Breaking the circuit for {args.BreakDuration.TotalMilliseconds}ms!",
Color.Magenta));
var exception = args.Outcome.Exception!;
progress.Report(ProgressWithMessage($"..due to: {exception.Message}", Color.Magenta));
return default;
},
OnClosed = args =>
{
progress.Report(ProgressWithMessage(".Breaker logging: Call OK! Closed the circuit again!", Color.Magenta));
return default;
},
OnHalfOpened = args =>
{
progress.Report(ProgressWithMessage(".Breaker logging: Half-open: Next call is a trial!", Color.Magenta));
return default;
}
}).Build();
var client = new HttpClient();
var internalCancel = false;
while (!(internalCancel || cancellationToken.IsCancellationRequested))
{
TotalRequests++;
var watch = Stopwatch.StartNew();
try
{
// Retry the following call according to the strategy.
await retryStrategy.ExecuteAsync(async outerToken =>
{
// This code is executed within the retry strategy.
var responseBody = await circuitBreakerStrategy.ExecuteAsync(async innerToken =>
{
// This code is executed within the circuit breaker strategy.
return await IssueRequestAndProcessResponseAsync(client, innerToken);
}, outerToken);
watch.Stop();
progress.Report(ProgressWithMessage($"Response : {responseBody} (after {watch.ElapsedMilliseconds}ms)", Color.Green));
EventualSuccesses++;
}, cancellationToken);
}
catch (BrokenCircuitException bce)
{
watch.Stop();
var logMessage = $"Request {TotalRequests} failed with: {bce.GetType().Name} (after {watch.ElapsedMilliseconds}ms)";
progress.Report(ProgressWithMessage(logMessage, Color.Red));
eventualFailuresDueToCircuitBreaking++;
}
catch (Exception e)
{
watch.Stop();
var logMessage = $"Request {TotalRequests} eventually failed with: {e.Message} (after {watch.ElapsedMilliseconds}ms)";
progress.Report(ProgressWithMessage(logMessage, Color.Red));
eventualFailuresForOtherReasons++;
}
await Task.Delay(TimeSpan.FromSeconds(0.5), cancellationToken);
internalCancel = ShouldTerminateByKeyPress();
}
}
public override Statistic[] LatestStatistics => new Statistic[]
{
new("Total requests made", TotalRequests),
new("Requests which eventually succeeded", EventualSuccesses, Color.Green),
new("Retries made to help achieve success", Retries, Color.Yellow),
new("Requests failed early by broken circuit", eventualFailuresDueToCircuitBreaking, Color.Magenta),
new("Requests which failed after longer delay", eventualFailuresForOtherReasons, Color.Red),
};
}