-
Notifications
You must be signed in to change notification settings - Fork 128
/
Demo07_WaitAndRetryNestingCircuitBreakerUsingPipeline.cs
155 lines (136 loc) · 6.46 KB
/
Demo07_WaitAndRetryNestingCircuitBreakerUsingPipeline.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
using System.Diagnostics;
using Polly.CircuitBreaker;
using PollyDemos.Helpers;
using PollyDemos.OutputHelpers;
namespace PollyDemos;
/// <summary>
/// <para>
/// Demonstrates using the Retry and CircuitBreaker strategies.<br/>
/// Same as Demo06 but this time combines the strategies by using ResiliencePipelineBuilder.
/// </para>
/// <para>
/// Loops through a series of HTTP requests, keeping track of each requested <br/>
/// item and reporting server failures when encountering exceptions.
/// </para>
/// <para>
/// Observations:
/// <list type="bullet">
/// <item>The operation is identical to Demo06.</item>
/// <item>The code demonstrates how using the ResiliencePipelineBuilder <br/>
/// makes your combined pipeline more concise, at the point of execution.</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 Demo07_WaitAndRetryNestingCircuitBreakerUsingPipeline : DemoBase
{
private int eventualFailuresDueToCircuitBreaking;
private int eventualFailuresForOtherReasons;
public override string Description =>
"This demonstrates CircuitBreaker (see Demo06), but uses the ResiliencePipelineBuilder to compose the strategies. Only the underlying code differs.";
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);
// Define a pipeline builder which will be used to compose strategies incrementally.
var pipelineBuilder = new ResiliencePipelineBuilder();
pipelineBuilder.AddRetry(new()
{
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;
}
}); // We are not calling the Build method here because we will do it as a separate step to make the code cleaner.
pipelineBuilder.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;
}
}); // We are not calling the Build method because we want to add one more strategy to the pipeline.
// Build the pipeline since we have added all the necessary strategies to it.
var pipeline = pipelineBuilder.Build();
var client = new HttpClient();
var internalCancel = false;
while (!(internalCancel || cancellationToken.IsCancellationRequested))
{
TotalRequests++;
var watch = Stopwatch.StartNew();
try
{
// Manage the call according to the pipeline.
var responseBody = await pipeline.ExecuteAsync(async token =>
{
// This code is executed through both strategies in the pipeline:
// Retry is the outer, and circuit breaker is the inner.
// Demo 06 shows a decomposed version of what this is equivalent to.
return await IssueRequestAndProcessResponseAsync(client, token);
}, cancellationToken);
watch.Stop();
progress.Report(ProgressWithMessage($"Response : {responseBody} (after {watch.ElapsedMilliseconds}ms)", Color.Green));
EventualSuccesses++;
}
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),
};
}