diff --git a/PollyDemos/Sync/Demo07_WaitAndRetryNestingCircuitBreakerUsingPipeline.cs b/PollyDemos/Sync/Demo07_WaitAndRetryNestingCircuitBreakerUsingPipeline.cs
new file mode 100644
index 0000000..5e84e61
--- /dev/null
+++ b/PollyDemos/Sync/Demo07_WaitAndRetryNestingCircuitBreakerUsingPipeline.cs
@@ -0,0 +1,168 @@
+using System.Diagnostics;
+using Polly.CircuitBreaker;
+using PollyDemos.OutputHelpers;
+
+namespace PollyDemos.Sync
+{
+ ///
+ /// Demonstrates using the Retry and CircuitBreaker strategies.
+ /// Same as Demo06 but this time combines the strategies by using ResiliencePipelineBuilder.
+ ///
+ /// Loops through a series of HTTP requests, keeping track of each requested
+ /// item and reporting server failures when encountering exceptions.
+ ///
+ /// Observations:
+ /// The operation is identical to Demo06.
+ /// The code demonstrates how using the ResiliencePipelineBuilder makes your combined pipeline more concise, at the point of execution.
+ ///
+ public class Demo07_WaitAndRetryNestingCircuitBreakerUsingPipeline : SyncDemo
+ {
+ private int totalRequests;
+ private int eventualSuccesses;
+ private int retries;
+ 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 void Execute(CancellationToken cancellationToken, IProgress progress)
+ {
+ ArgumentNullException.ThrowIfNull(progress);
+
+ // Let's call a web API service to make repeated requests to a server.
+ // The service is programmed to fail after 3 requests in 5 seconds.
+
+ eventualSuccesses = 0;
+ retries = 0;
+ eventualFailuresDueToCircuitBreaking = 0;
+ eventualFailuresForOtherReasons = 0;
+ totalRequests = 0;
+
+ progress.Report(ProgressWithMessage(nameof(Demo07_WaitAndRetryNestingCircuitBreakerUsingPipeline)));
+ progress.Report(ProgressWithMessage("======"));
+ progress.Report(ProgressWithMessage(string.Empty));
+
+ // New for demo07: here we define a pipeline builder which will be used to compose strategies gradually.
+ var pipelineBuilder = new ResiliencePipelineBuilder();
+
+ // New for demo07: the order of strategy definitions has changed.
+ // Circuit breaker comes first because that will be the inner strategy.
+ // Retry comes second because that will be the outer strategy.
+
+ // Define our circuit breaker strategy:
+ pipelineBuilder.AddCircuitBreaker(new()
+ {
+ ShouldHandle = new PredicateBuilder().Handle(),
+ FailureRatio = 1.0,
+ MinimumThroughput = 4,
+ BreakDuration = TimeSpan.FromSeconds(3),
+ OnOpened = args =>
+ {
+ progress.Report(ProgressWithMessage(
+ $".Breaker logging: Breaking the circuit for {args.BreakDuration.TotalMilliseconds}ms!",
+ Color.Magenta));
+
+ // Due to how we have defined ShouldHandle, this delegate is called only if an exception occurred.
+ // Note the ! sign (null-forgiving operator) at the end of the command.
+ var exception = args.Outcome.Exception!; //The Exception property is nullable
+ 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;
+ }
+ }); // New for demo07: here we are not calling the Build method because we want to add one more strategy to the pipeline.
+
+ // Define our retry strategy:
+ pipelineBuilder.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(ex => ex is not BrokenCircuitException),
+ MaxRetryAttempts = int.MaxValue, // Retry indefinitely
+ Delay = TimeSpan.FromMilliseconds(200), // Wait 200ms between each try
+ OnRetry = args =>
+ {
+ // Due to how we have defined ShouldHandle, this delegate is called only if an exception occurred.
+ // Note the ! sign (null-forgiving operator) at the end of the command.
+ var exception = args.Outcome.Exception!; //The Exception property is nullable
+
+ // Tell the user what happened
+ progress.Report(ProgressWithMessage($"Strategy logging: {exception.Message}", Color.Yellow));
+ retries++;
+ return default;
+ }
+ }); // New for demo07: here we are not calling the Build method because we will do it as a separate step to make the code cleaner.
+
+ // New for demo07: here we build the pipeline since we have added all the necessary strategies to it.
+ var pipeline = pipelineBuilder.Build();
+
+ var client = new HttpClient();
+ var internalCancel = false;
+ // Do the following until a key is pressed
+ while (!(internalCancel || cancellationToken.IsCancellationRequested))
+ {
+ totalRequests++;
+ var watch = Stopwatch.StartNew();
+
+ try
+ {
+ // Manage the call according to the pipeline.
+ var response = pipeline.Execute(ct =>
+ {
+ // This code is executed through both strategies in the pipeline:
+ // Retry is the outer, and circuit breaker is the inner.
+ // Demo 06 shows a broken-out version of what this is equivalent to.
+
+ // Make a request and get a response
+ var url = $"{Configuration.WEB_API_ROOT}/api/values/{totalRequests}";
+ var response = client.Send(new HttpRequestMessage(HttpMethod.Get, url), ct);
+
+ using var stream = response.Content.ReadAsStream(ct);
+ using var streamReader = new StreamReader(stream);
+ return streamReader.ReadToEnd();
+ }, cancellationToken);
+
+ watch.Stop();
+
+ // Display the response message on the console
+ progress.Report(ProgressWithMessage($"Response : {response} (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++;
+ }
+
+ Thread.Sleep(500);
+ internalCancel = TerminateDemosByKeyPress && Console.KeyAvailable;
+ }
+ }
+
+ 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),
+ };
+ }
+}
diff --git a/PollyDemos/Sync/Demo07_WaitAndRetryNestingCircuitBreakerUsingPolicyWrap.cs b/PollyDemos/Sync/Demo07_WaitAndRetryNestingCircuitBreakerUsingPolicyWrap.cs
deleted file mode 100644
index 4043fac..0000000
--- a/PollyDemos/Sync/Demo07_WaitAndRetryNestingCircuitBreakerUsingPolicyWrap.cs
+++ /dev/null
@@ -1,161 +0,0 @@
-using System.Diagnostics;
-using System.Net;
-using Polly.CircuitBreaker;
-using PollyDemos.OutputHelpers;
-
-namespace PollyDemos.Sync
-{
- ///
- /// Demonstrates using the WaitAndRetry policy nesting CircuitBreaker.
- /// Same as Demo06 - but this time demonstrates combining the policies using PolicyWrap.
- ///
- /// Loops through a series of Http requests, keeping track of each requested
- /// item and reporting server failures when encountering exceptions.
- ///
- /// Obervations from this demo:
- /// The operation is identical to Demo06.
- /// The code demonstrates how using the PolicyWrap makes your combined-Policy-strategy more concise, at the point of execution.
- ///
- public class Demo07_WaitAndRetryNestingCircuitBreakerUsingPolicyWrap : SyncDemo
- {
- private int totalRequests;
- private int eventualSuccesses;
- private int retries;
- private int eventualFailuresDueToCircuitBreaking;
- private int eventualFailuresForOtherReasons;
-
- public override string Description =>
- "This demonstrates CircuitBreaker (see Demo06), but uses the PolicyWrap syntax to compose the policies nicely. Only the underlying code differs.";
-
- public override void Execute(CancellationToken cancellationToken, IProgress progress)
- {
- if (progress == null) throw new ArgumentNullException(nameof(progress));
-
- // Let's call a web api service to make repeated requests to a server.
- // The service is programmed to fail after 3 requests in 5 seconds.
-
- eventualSuccesses = 0;
- retries = 0;
- eventualFailuresDueToCircuitBreaking = 0;
- eventualFailuresForOtherReasons = 0;
-
- progress.Report(ProgressWithMessage(nameof(Demo07_WaitAndRetryNestingCircuitBreakerUsingPolicyWrap)));
- progress.Report(ProgressWithMessage("======"));
- progress.Report(ProgressWithMessage(string.Empty));
-
- // Define our waitAndRetry policy: keep retrying with 200ms gaps.
- var waitAndRetryPolicy = Policy
- .Handle(e =>
- !(e is BrokenCircuitException)) // Exception filtering! We don't retry if the inner circuit-breaker judges the underlying system is out of commission!
- .WaitAndRetryForever(
- attempt => TimeSpan.FromMilliseconds(200),
- (exception, calculatedWaitDuration) =>
- {
- // This is your new exception handler!
- // Tell the user what they've won!
- progress.Report(ProgressWithMessage(".Log,then retry: " + exception.Message, Color.Yellow));
- retries++;
- });
-
- // Define our CircuitBreaker policy: Break if the action fails 4 times in a row.
- var circuitBreakerPolicy = Policy
- .Handle()
- .CircuitBreaker(
- 4,
- TimeSpan.FromSeconds(3),
- (ex, breakDelay) =>
- {
- progress.Report(ProgressWithMessage(
- ".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!",
- Color.Magenta));
- progress.Report(ProgressWithMessage("..due to: " + ex.Message, Color.Magenta));
- },
- () => progress.Report(ProgressWithMessage(".Breaker logging: Call ok! Closed the circuit again!",
- Color.Magenta)),
- () => progress.Report(ProgressWithMessage(".Breaker logging: Half-open: Next call is a trial!",
- Color.Magenta))
- );
-
- // New for demo07: combine the waitAndRetryPolicy and circuitBreakerPolicy into a PolicyWrap.
- var policyWrap = Policy.Wrap(waitAndRetryPolicy, circuitBreakerPolicy);
-
- using (var client = new WebClient())
- {
- var internalCancel = false;
- totalRequests = 0;
- // Do the following until a key is pressed
- while (!internalCancel && !cancellationToken.IsCancellationRequested)
- {
- totalRequests++;
- var watch = new Stopwatch();
- watch.Start();
-
- try
- {
- // Retry the following call according to the policy wrap
- var response = policyWrap.Execute(
- ct => // The Execute() overload takes a CancellationToken, but it happens the executed code does not honour it.
- {
- // This code is executed through both policies in the wrap: WaitAndRetry outer, then CircuitBreaker inner. Demo 06 shows a broken-out version of what this is equivalent to.
-
- return client.DownloadString(
- Configuration.WEB_API_ROOT + "/api/values/" + totalRequests);
- }
- , cancellationToken // The cancellationToken passed in to Execute() enables the policy instance to cancel retries, when the token is signalled.
- );
-
- // Without the extra comments in the anonymous method { } above, it could even be as concise as this:
- // string msg = policyWrap.Execute(() => client.DownloadString(Configuration.WEB_API_ROOT + "/api/values/" + i));
-
- watch.Stop();
-
- // Display the response message on the console
- progress.Report(ProgressWithMessage("Response : " + response
- + " (after " + watch.ElapsedMilliseconds +
- "ms)", Color.Green));
-
- eventualSuccesses++;
- }
- catch (BrokenCircuitException b)
- {
- watch.Stop();
-
- progress.Report(ProgressWithMessage("Request " + totalRequests + " failed with: " +
- b.GetType().Name
- + " (after " + watch.ElapsedMilliseconds + "ms)",
- Color.Red));
-
- eventualFailuresDueToCircuitBreaking++;
- }
- catch (Exception e)
- {
- watch.Stop();
-
- progress.Report(ProgressWithMessage("Request " + totalRequests + " eventually failed with: " +
- e.Message
- + " (after " + watch.ElapsedMilliseconds + "ms)",
- Color.Red));
-
- eventualFailuresForOtherReasons++;
- }
-
- // Wait half second
- Thread.Sleep(500);
-
- internalCancel = TerminateDemosByKeyPress && Console.KeyAvailable;
- }
- }
- }
-
- public override Statistic[] LatestStatistics => new[]
- {
- new Statistic("Total requests made", totalRequests),
- new Statistic("Requests which eventually succeeded", eventualSuccesses, Color.Green),
- new Statistic("Retries made to help achieve success", retries, Color.Yellow),
- new Statistic("Requests failed early by broken circuit", eventualFailuresDueToCircuitBreaking,
- Color.Magenta),
- new Statistic("Requests which failed after longer delay", eventualFailuresForOtherReasons, Color.Red),
- };
- }
-}
\ No newline at end of file
diff --git a/PollyDemos/Sync/Demo08_Pipeline-Fallback-WaitAndRetry-CircuitBreaker.cs b/PollyDemos/Sync/Demo08_Pipeline-Fallback-WaitAndRetry-CircuitBreaker.cs
new file mode 100644
index 0000000..b3ee56e
--- /dev/null
+++ b/PollyDemos/Sync/Demo08_Pipeline-Fallback-WaitAndRetry-CircuitBreaker.cs
@@ -0,0 +1,202 @@
+using System.Diagnostics;
+using Polly.CircuitBreaker;
+using PollyDemos.OutputHelpers;
+
+namespace PollyDemos.Sync
+{
+ ///
+ /// Demonstrates using a Retry, a CircuitBreaker and two Fallback strategies.
+ /// Same as Demo07 but now uses Fallback strategies to provide substitute values, when the call still fails overall.
+ ///
+ /// Loops through a series of HTTP requests, keeping track of each requested
+ /// item and reporting server failures when encountering exceptions.
+ ///
+ /// Observations:
+ /// - operation is identical to Demo06 and Demo07
+ /// - except fallback strategies provide nice substitute messages, if still fails overall
+ /// - OnFallback delegate captures the stats that were captured in try/catches in demos 06 and 07
+ /// - also demonstrates how you can use the same kind of strategy (Fallback in this case) twice (or more) in a pipeline.
+ ///
+ public class Demo08_Pipeline_Fallback_WaitAndRetry_CircuitBreaker : SyncDemo
+ {
+ private int totalRequests;
+ private int eventualSuccesses;
+ private int retries;
+ private int eventualFailuresDueToCircuitBreaking;
+ private int eventualFailuresForOtherReasons;
+
+ public override string Description =>
+ "This demo matches 06 and 07 (retry with circuit-breaker), but also introduces Fallbacks: we can provide graceful fallback messages, on overall failure.";
+
+ public override void Execute(CancellationToken cancellationToken, IProgress progress)
+ {
+ ArgumentNullException.ThrowIfNull(progress);
+
+ // Let's call a web API service to make repeated requests to a server.
+ // The service is programmed to fail after 3 requests in 5 seconds.
+
+ eventualSuccesses = 0;
+ retries = 0;
+ eventualFailuresDueToCircuitBreaking = 0;
+ eventualFailuresForOtherReasons = 0;
+ totalRequests = 0;
+
+ progress.Report(ProgressWithMessage(nameof(Demo08_Pipeline_Fallback_WaitAndRetry_CircuitBreaker)));
+ progress.Report(ProgressWithMessage("======"));
+ progress.Report(ProgressWithMessage(string.Empty));
+
+ Stopwatch? watch = null;
+
+ // New for demo08: we had to provide the return type (string) to be able to use Fallback.
+ var pipelineBuilder = new ResiliencePipelineBuilder();
+
+ // Define our circuit breaker strategy:
+ pipelineBuilder.AddCircuitBreaker(new()
+ {
+ // New for demo08: since pipeline is aware of the return type that's why the PredicateBuilder has to be as well.
+ ShouldHandle = new PredicateBuilder().Handle(),
+ FailureRatio = 1.0,
+ MinimumThroughput = 4,
+ BreakDuration = TimeSpan.FromSeconds(3),
+ OnOpened = args =>
+ {
+ progress.Report(ProgressWithMessage(
+ $".Breaker logging: Breaking the circuit for {args.BreakDuration.TotalMilliseconds}ms!",
+ Color.Magenta));
+
+ // Due to how we have defined ShouldHandle, this delegate is called only if an exception occurred.
+ // Note the ! sign (null-forgiving operator) at the end of the command.
+ var exception = args.Outcome.Exception!; //The Exception property is nullable
+ 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;
+ }
+ });
+
+ // Define our retry strategy:
+ pipelineBuilder.AddRetry(new()
+ {
+ // New for demo08: since pipeline is aware of the return type that's why the PredicateBuilder has to be as well.
+ // Exception filtering - we don't retry if the inner circuit-breaker judges the underlying system is out of commission.
+ ShouldHandle = new PredicateBuilder().Handle(ex => ex is not BrokenCircuitException),
+ MaxRetryAttempts = int.MaxValue, // Retry indefinitely
+ Delay = TimeSpan.FromMilliseconds(200), // Wait 200ms between each try
+ OnRetry = args =>
+ {
+ // Due to how we have defined ShouldHandle, this delegate is called only if an exception occurred.
+ // Note the ! sign (null-forgiving operator) at the end of the command.
+ var exception = args.Outcome.Exception!; //The Exception property is nullable
+
+ // Tell the user what happened
+ progress.Report(ProgressWithMessage($"Strategy logging: {exception.Message}", Color.Yellow));
+ retries++;
+ return default;
+ }
+ });
+
+ // Define a fallback strategy: provide a substitute message to the user, if we found the circuit was broken.
+ pipelineBuilder.AddFallback(new()
+ {
+ ShouldHandle = new PredicateBuilder().Handle(),
+ FallbackAction = args => Outcome.FromResultAsValueTask("Please try again later [message substituted by fallback strategy]"),
+ OnFallback = args =>
+ {
+ watch!.Stop();
+
+ // Due to how we have defined ShouldHandle, this delegate is called only if an exception occurred.
+ // Note the ! sign (null-forgiving operator) at the end of the command.
+ var exception = args.Outcome.Exception!; //The Exception property is nullable
+
+ progress.Report(ProgressWithMessage($"Fallback catches failed with: {exception.Message} (after {watch.ElapsedMilliseconds}ms)", Color.Red));
+ eventualFailuresDueToCircuitBreaking++;
+ return default;
+ }
+ });
+
+ // Define a fallback strategy: provide a substitute message to the user, for any exception.
+ pipelineBuilder.AddFallback(new()
+ {
+ ShouldHandle = new PredicateBuilder().Handle(),
+ FallbackAction = args => Outcome.FromResultAsValueTask("Please try again later [Fallback for any exception]"),
+ OnFallback = args =>
+ {
+ watch!.Stop();
+
+ // Due to how we have defined ShouldHandle, this delegate is called only if an exception occurred.
+ // Note the ! sign (null-forgiving operator) at the end of the command.
+ var exception = args.Outcome.Exception!; //The Exception property is nullable
+
+ progress.Report(ProgressWithMessage($"Fallback catches eventually failed with: {exception.Message} (after {watch.ElapsedMilliseconds}ms)", Color.Red));
+
+ eventualFailuresForOtherReasons++;
+ return default;
+ }
+ });
+
+ // Build the pipeline which now composes four strategies (from inner to outer):
+ // Circuit Breaker
+ // Retry
+ // Fallback for open circuit
+ // Fallback for any other exception
+ var pipeline = pipelineBuilder.Build();
+
+ var client = new HttpClient();
+ var internalCancel = false;
+ // Do the following until a key is pressed
+ while (!(internalCancel || cancellationToken.IsCancellationRequested))
+ {
+ totalRequests++;
+ watch = Stopwatch.StartNew();
+
+ try
+ {
+ // Manage the call according to the pipeline.
+ var response = pipeline.Execute(ct =>
+ {
+ // Make a request and get a response
+ var url = $"{Configuration.WEB_API_ROOT}/api/values/{totalRequests}";
+ var response = client.Send(new HttpRequestMessage(HttpMethod.Get, url), ct);
+
+ using var stream = response.Content.ReadAsStream(ct);
+ using var streamReader = new StreamReader(stream);
+ return streamReader.ReadToEnd();
+ }, cancellationToken);
+
+ watch.Stop();
+
+ // Display the response message on the console
+ progress.Report(ProgressWithMessage($"Response : {response} (after {watch.ElapsedMilliseconds}ms)", Color.Green));
+ eventualSuccesses++;
+ }
+ // This try-catch is not needed, since we have a Fallback for any Exceptions.
+ // It's only been left in to *demonstrate* it should never get hit.
+ catch (Exception e)
+ {
+ var errorMessage = "Should never arrive here. Use of fallback for any Exception should have provided nice fallback value for exceptions.";
+ throw new InvalidOperationException(errorMessage, e);
+ }
+
+ Thread.Sleep(500);
+ internalCancel = TerminateDemosByKeyPress && Console.KeyAvailable;
+ }
+ }
+
+ 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),
+ };
+ }
+}
diff --git a/PollyDemos/Sync/Demo08_Wrap-Fallback-WaitAndRetry-CircuitBreaker.cs b/PollyDemos/Sync/Demo08_Wrap-Fallback-WaitAndRetry-CircuitBreaker.cs
deleted file mode 100644
index 7c82c4a..0000000
--- a/PollyDemos/Sync/Demo08_Wrap-Fallback-WaitAndRetry-CircuitBreaker.cs
+++ /dev/null
@@ -1,185 +0,0 @@
-using System.Diagnostics;
-using System.Net;
-using Polly.CircuitBreaker;
-using PollyDemos.OutputHelpers;
-
-namespace PollyDemos.Sync
-{
- ///
- /// Demonstrates a PolicyWrap including two Fallback policies (for different exceptions), WaitAndRetry and CircuitBreaker.
- /// As Demo07 - but now uses Fallback policies to provide substitute values, when the call still fails overall.
- ///
- /// Loops through a series of Http requests, keeping track of each requested
- /// item and reporting server failures when encountering exceptions.
- ///
- /// Obervations from this demo:
- /// - operation identical to Demo06 and Demo07
- /// - except fallback policies provide nice substitute messages, if still fails overall
- /// - onFallback delegate captures the stats that were captured in try/catches in demos 06 and 07
- /// - also demonstrates how you can use the same kind of policy (Fallback in this case) twice (or more) in a wrap.
- ///
- public class Demo08_Wrap_Fallback_WaitAndRetry_CircuitBreaker : SyncDemo
- {
- private int totalRequests;
- private int eventualSuccesses;
- private int retries;
- private int eventualFailuresDueToCircuitBreaking;
- private int eventualFailuresForOtherReasons;
-
- public override string Description =>
- "This demo matches 06 and 07 (retry with circuit-breaker), but also introduces a Fallback: we can provide a graceful fallback message, on overall failure.";
-
- public override void Execute(CancellationToken cancellationToken, IProgress progress)
- {
- if (progress == null) throw new ArgumentNullException(nameof(progress));
-
- // Let's call a web api service to make repeated requests to a server.
- // The service is programmed to fail after 3 requests in 5 seconds.
-
- eventualSuccesses = 0;
- retries = 0;
- eventualFailuresDueToCircuitBreaking = 0;
- eventualFailuresForOtherReasons = 0;
-
- progress.Report(ProgressWithMessage(nameof(Demo08_Wrap_Fallback_WaitAndRetry_CircuitBreaker)));
- progress.Report(ProgressWithMessage("======"));
- progress.Report(ProgressWithMessage(string.Empty));
-
- Stopwatch watch = null;
-
- // Define our waitAndRetry policy: keep retrying with 200ms gaps.
- var waitAndRetryPolicy = Policy
- .Handle(e =>
- !(e is BrokenCircuitException)) // Exception filtering! We don't retry if the inner circuit-breaker judges the underlying system is out of commission!
- .WaitAndRetryForever(
- attempt => TimeSpan.FromMilliseconds(200),
- (exception, calculatedWaitDuration) =>
- {
- progress.Report(ProgressWithMessage(".Log,then retry: " + exception.Message, Color.Yellow));
- retries++;
- });
-
- // Define our CircuitBreaker policy: Break if the action fails 4 times in a row.
- var circuitBreakerPolicy = Policy
- .Handle()
- .CircuitBreaker(
- 4,
- TimeSpan.FromSeconds(3),
- (ex, breakDelay) =>
- {
- progress.Report(ProgressWithMessage(
- ".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!",
- Color.Magenta));
- progress.Report(ProgressWithMessage("..due to: " + ex.Message, Color.Magenta));
- },
- () => progress.Report(ProgressWithMessage(".Breaker logging: Call ok! Closed the circuit again!",
- Color.Magenta)),
- () => progress.Report(ProgressWithMessage(".Breaker logging: Half-open: Next call is a trial!",
- Color.Magenta))
- );
-
-
- // Define a fallback policy: provide a nice substitute message to the user, if we found the circuit was broken.
- var fallbackForCircuitBreaker = Policy
- .Handle()
- .Fallback(
- /* Demonstrates fallback value syntax */
- "Please try again later [message substituted by fallback policy]",
- b =>
- {
- watch.Stop();
-
- progress.Report(ProgressWithMessage("Fallback catches failed with: " + b.Exception.Message
- + " (after " +
- watch.ElapsedMilliseconds +
- "ms)", Color.Red));
-
- eventualFailuresDueToCircuitBreaking++;
- }
- );
-
- // Define a fallback policy: provide a substitute string to the user, for any exception.
- var fallbackForAnyException = Policy
- .Handle()
- .Fallback(
- /* Demonstrates fallback action/func syntax */ () =>
- {
- return "Please try again later [Fallback for any exception]";
- },
- e =>
- {
- watch.Stop();
-
- progress.Report(ProgressWithMessage(
- "Fallback catches eventually failed with: " + e.Exception.Message
- + " (after " + watch.ElapsedMilliseconds +
- "ms)", Color.Red));
-
- eventualFailuresForOtherReasons++;
- }
- );
-
-
- // As demo07: we combine the waitAndRetryPolicy and circuitBreakerPolicy into a PolicyWrap, using the *static* Policy.Wrap syntax.
- var myResilienceStrategy = Policy.Wrap(waitAndRetryPolicy, circuitBreakerPolicy);
-
- // Added in demo08: we wrap the two fallback policies onto the front of the existing wrap too. Demonstrates the *instance* wrap syntax. And the fact that the PolicyWrap myResilienceStrategy from above is just another Policy, which can be onward-wrapped too.
- // With this pattern, you can build an overall resilience strategy programmatically, reusing some common parts (eg PolicyWrap myResilienceStrategy) but varying other parts (eg Fallback) individually for different calls.
- var policyWrap = fallbackForAnyException.Wrap(fallbackForCircuitBreaker.Wrap(myResilienceStrategy));
- // For info: Equivalent to: PolicyWrap policyWrap = Policy.Wrap(fallbackForAnyException, fallbackForCircuitBreaker, waitAndRetryPolicy, circuitBreakerPolicy);
-
- using (var client = new WebClient())
- {
- var internalCancel = false;
- totalRequests = 0;
- // Do the following until a key is pressed
- while (!internalCancel && !cancellationToken.IsCancellationRequested)
- {
- totalRequests++;
- watch = new Stopwatch();
- watch.Start();
-
- try
- {
- // Manage the call according to the whole policy wrap.
- var response =
- policyWrap.Execute(
- ct => client.DownloadString(Configuration.WEB_API_ROOT + "/api/values/" +
- totalRequests), cancellationToken);
-
- watch.Stop();
-
- // Display the response message on the console
- progress.Report(ProgressWithMessage(
- "Response : " + response + " (after " + watch.ElapsedMilliseconds + "ms)", Color.Green));
-
- eventualSuccesses++;
- }
- catch (Exception e
- ) // try-catch not needed, now that we have a Fallback.Handle. It's only been left in to *demonstrate* it should never get hit.
- {
- throw new InvalidOperationException(
- "Should never arrive here. Use of fallbackForAnyException should have provided nice fallback value for any exceptions.",
- e);
- }
-
- // Wait half second
- Thread.Sleep(500);
-
- internalCancel = TerminateDemosByKeyPress && Console.KeyAvailable;
- }
- }
- }
-
- public override Statistic[] LatestStatistics => new[]
- {
- new Statistic("Total requests made", totalRequests),
- new Statistic("Requests which eventually succeeded", eventualSuccesses, Color.Green),
- new Statistic("Retries made to help achieve success", retries, Color.Yellow),
- new Statistic("Requests failed early by broken circuit", eventualFailuresDueToCircuitBreaking,
- Color.Magenta),
- new Statistic("Requests which failed after longer delay", eventualFailuresForOtherReasons, Color.Red),
- };
- }
-}
\ No newline at end of file
diff --git a/PollyDemos/Sync/Demo09_Pipeline-Fallback-Timeout-WaitAndRetry.cs b/PollyDemos/Sync/Demo09_Pipeline-Fallback-Timeout-WaitAndRetry.cs
new file mode 100644
index 0000000..f4eaab5
--- /dev/null
+++ b/PollyDemos/Sync/Demo09_Pipeline-Fallback-Timeout-WaitAndRetry.cs
@@ -0,0 +1,176 @@
+using System.Diagnostics;
+using Polly.Timeout;
+using PollyDemos.OutputHelpers;
+
+namespace PollyDemos.Sync
+{
+ ///
+ /// Demonstrates using a Retry, a Timeout and two Fallback strategies.
+ /// In this demo, the delay in the retry is deliberately so long that the timeout wrapping it will time it out
+ /// (in lieu for now of a demo server endpoint responding slowly).
+ ///
+ /// Loops through a series of HTTP requests, keeping track of each requested
+ /// item and reporting server failures when encountering exceptions.
+ ///
+ /// Observations:
+ /// - though the console logs that a retry will be made, the 4-second wait before the retry is pre-emptively timed-out by the two-second timeout
+ /// - a fallback strategy then provides substitute message for the user
+ /// - otherwise similar to demo08.
+ ///
+ public class Demo09_Pipeline_Fallback_Timeout_WaitAndRetry : SyncDemo
+ {
+ private int totalRequests;
+ private int eventualSuccesses;
+ private int retries;
+ private int eventualFailuresDueToTimeout;
+ private int eventualFailuresForOtherReasons;
+
+ public override string Description =>
+ "Demonstrates introducing a Timeout strategy. The timeout will eventually time-out on the retries. When we timeout, we again use a Fallback to substitute a more graceful message.";
+
+ public override void Execute(CancellationToken cancellationToken, IProgress progress)
+ {
+ ArgumentNullException.ThrowIfNull(progress);
+
+ // Let's call a web API service to make repeated requests to a server.
+ // The service is programmed to fail after 3 requests in 5 seconds.
+
+ eventualSuccesses = 0;
+ retries = 0;
+ eventualFailuresDueToTimeout = 0;
+ eventualFailuresForOtherReasons = 0;
+ totalRequests = 0;
+
+ progress.Report(ProgressWithMessage(nameof(Demo09_Pipeline_Fallback_Timeout_WaitAndRetry)));
+ progress.Report(ProgressWithMessage("======"));
+ progress.Report(ProgressWithMessage(string.Empty));
+
+ Stopwatch? watch = null;
+ var pipelineBuilder = new ResiliencePipelineBuilder();
+
+ // Define our timeout strategy: time out after 2 seconds.
+ pipelineBuilder.AddTimeout(new TimeoutStrategyOptions()
+ {
+ Timeout = TimeSpan.FromSeconds(2),
+ OnTimeout = args =>
+ {
+ var logMessage = $".The task was terminated because it ran out of time. Time cap was {args.Timeout.TotalSeconds}s";
+ progress.Report(ProgressWithMessage(logMessage, Color.Yellow));
+ return default;
+ }
+ });
+
+ // Define our retry strategy: keep retrying with 4 second gaps. This is (intentionally) too long: to demonstrate that the timeout strategy will time out on this before waiting for the retry.
+ pipelineBuilder.AddRetry(new()
+ {
+ ShouldHandle = new PredicateBuilder().Handle(),
+ Delay = TimeSpan.FromSeconds(4),
+ MaxRetryAttempts = int.MaxValue,
+ OnRetry = args =>
+ {
+ // Due to how we have defined ShouldHandle, this delegate is called only if an exception occurred.
+ // Note the ! sign (null-forgiving operator) at the end of the command.
+ var exception = args.Outcome.Exception!; //The Exception property is nullable
+ progress.Report(ProgressWithMessage($".Log,then retry: {exception.Message}", Color.Yellow));
+ retries++;
+ return default;
+ }
+ });
+
+ // Define a fallback strategy: provide a substitute message to the user, if we found the call was rejected due to timeout.
+ pipelineBuilder.AddFallback(new()
+ {
+ ShouldHandle = new PredicateBuilder().Handle(),
+ FallbackAction = args => Outcome.FromResultAsValueTask("Please try again later [Fallback for timeout]"),
+ OnFallback = args =>
+ {
+ watch!.Stop();
+
+ // Due to how we have defined ShouldHandle, this delegate is called only if an exception occurred.
+ // Note the ! sign (null-forgiving operator) at the end of the command.
+ var exception = args.Outcome.Exception!; //The Exception property is nullable
+
+ progress.Report(ProgressWithMessage($"Fallback catches failed with: {exception.Message} (after {watch.ElapsedMilliseconds}ms)", Color.Red));
+ eventualFailuresDueToTimeout++;
+ return default;
+ }
+ });
+
+ // Define a fallback strategy: provide a substitute message to the user, for any exception.
+ pipelineBuilder.AddFallback(new()
+ {
+ ShouldHandle = new PredicateBuilder().Handle(),
+ FallbackAction = args => Outcome.FromResultAsValueTask("Please try again later [Fallback for any exception]"),
+ OnFallback = args =>
+ {
+ watch!.Stop();
+
+ // Due to how we have defined ShouldHandle, this delegate is called only if an exception occurred.
+ // Note the ! sign (null-forgiving operator) at the end of the command.
+ var exception = args.Outcome.Exception!; //The Exception property is nullable
+
+ progress.Report(ProgressWithMessage($"Fallback catches eventually failed with: {exception.Message} (after {watch.ElapsedMilliseconds}ms)", Color.Red));
+
+ eventualFailuresForOtherReasons++;
+ return default;
+ }
+ });
+
+ // Build the pipeline which now composes four strategies (from inner to outer):
+ // Timeout
+ // Retry
+ // Fallback for timeout
+ // Fallback for any other exception
+ var pipeline = pipelineBuilder.Build();
+
+ var client = new HttpClient();
+ var internalCancel = false;
+ // Do the following until a key is pressed
+ while (!(internalCancel || cancellationToken.IsCancellationRequested))
+ {
+ totalRequests++;
+ watch = Stopwatch.StartNew();
+
+ try
+ {
+ // Manage the call according to the pipeline.
+ var response = pipeline.Execute(ct =>
+ {
+ // Make a request and get a response
+ var url = $"{Configuration.WEB_API_ROOT}/api/values/{totalRequests}";
+ var response = client.Send(new HttpRequestMessage(HttpMethod.Get, url), ct);
+
+ using var stream = response.Content.ReadAsStream(ct);
+ using var streamReader = new StreamReader(stream);
+ return streamReader.ReadToEnd();
+ }, cancellationToken);
+
+ watch.Stop();
+
+ // Display the response message on the console
+ progress.Report(ProgressWithMessage($"Response : {response} (after {watch.ElapsedMilliseconds}ms)", Color.Green));
+ eventualSuccesses++;
+ }
+ // This try-catch is not needed, since we have a Fallback for any Exceptions.
+ // It's only been left in to *demonstrate* it should never get hit.
+ catch (Exception e)
+ {
+ var errorMessage = "Should never arrive here. Use of fallback for any Exception should have provided nice fallback value for exceptions.";
+ throw new InvalidOperationException(errorMessage, e);
+ }
+
+ Thread.Sleep(500);
+ internalCancel = TerminateDemosByKeyPress && Console.KeyAvailable;
+ }
+ }
+
+ 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 timed out by timeout strategy", eventualFailuresDueToTimeout, Color.Magenta),
+ new("Requests which failed after longer delay", eventualFailuresForOtherReasons, Color.Red),
+ };
+ }
+}
diff --git a/PollyDemos/Sync/Demo09_Wrap-Fallback-Timeout-WaitAndRetry.cs b/PollyDemos/Sync/Demo09_Wrap-Fallback-Timeout-WaitAndRetry.cs
deleted file mode 100644
index 4786130..0000000
--- a/PollyDemos/Sync/Demo09_Wrap-Fallback-Timeout-WaitAndRetry.cs
+++ /dev/null
@@ -1,181 +0,0 @@
-using System.Diagnostics;
-using System.Net;
-using Polly.Timeout;
-using PollyDemos.OutputHelpers;
-
-namespace PollyDemos.Sync
-{
- ///
- /// Demonstrates a PolicyWrap including Fallback, Timeout and WaitAndRetry.
- /// In this demo, the wait in the wait-and-retry is deliberately so long that the timeout policy wrapping it will time it out
- /// (in lieu for now of a demo server endpoint responding slowly).
- ///
- /// Loops through a series of Http requests, keeping track of each requested
- /// item and reporting server failures when encountering exceptions.
- ///
- /// Obervations from this demo:
- /// - though the console logs that a retry will be made, the 4-second wait before the retry is pre-emptively timed-out by the two-second timeout
- /// - a fallback policy then provides substitute message for the user
- /// - otherwise similar to demo08.
- ///
- public class Demo09_Wrap_Fallback_Timeout_WaitAndRetry : SyncDemo
- {
- private int totalRequests;
- private int eventualSuccesses;
- private int retries;
- private int eventualFailuresDueToTimeout;
- private int eventualFailuresForOtherReasons;
-
- public override string Description =>
- "Demonstrates introducing a TimeoutPolicy. The TimeoutPolicy will eventually time-out on the retries that WaitAndRetry was orchestrating. When we timeout, we again use a Fallback policy to substitute a more graceful message.";
-
- public override void Execute(CancellationToken cancellationToken, IProgress progress)
- {
- if (progress == null) throw new ArgumentNullException(nameof(progress));
-
- // Let's call a web api service to make repeated requests to a server.
- // The service is programmed to fail after 3 requests in 5 seconds.
-
- eventualSuccesses = 0;
- retries = 0;
- eventualFailuresDueToTimeout = 0;
- eventualFailuresForOtherReasons = 0;
-
- progress.Report(ProgressWithMessage(nameof(Demo09_Wrap_Fallback_Timeout_WaitAndRetry)));
- progress.Report(ProgressWithMessage("======"));
- progress.Report(ProgressWithMessage(string.Empty));
-
- Stopwatch watch = null;
-
- // Define our timeout policy: time out after 2 seconds. We will use the pessimistic timeout strategy, which forces a timeout - even when the underlying delegate doesn't support it.
- var timeoutPolicy = Policy
- .Timeout(TimeSpan.FromSeconds(2), TimeoutStrategy.Pessimistic,
- // This use of onTimeout demonstrates the point about capturing walked-away-from Tasks with TimeoutStrategy.Pessimistic discussed in the Polly wiki, here: https://github.com/App-vNext/Polly/wiki/Timeout#pessimistic-timeout-1
- (ctx, span, abandonedTask) =>
- {
- {
- abandonedTask.ContinueWith(t =>
- {
- // ContinueWith important!: the abandoned task may very well still be executing, when the caller times out on waiting for it!
-
- if (t.IsFaulted)
- progress.Report(ProgressWithMessage(
- ".The task previously walked-away-from now terminated with exception: " +
- t.Exception.Message,
- Color.Yellow));
- else if (t.IsCanceled)
- // (If the executed delegates do not honour cancellation, this IsCanceled branch may never be hit. It can be good practice however to include, in case a Policy configured with TimeoutStrategy.Pessimistic is used to execute a delegate honouring cancellation.)
- progress.Report(ProgressWithMessage(
- ".The task previously walked-away-from now was canceled.", Color.Yellow));
- else
- // extra logic (if desired) for tasks which complete, despite the caller having 'walked away' earlier due to timeout.
- progress.Report(ProgressWithMessage(
- ".The task previously walked-away-from now eventually completed.",
- Color.Yellow));
- });
- }
- }
- );
-
- // Define our waitAndRetry policy: keep retrying with 4 second gaps. This is (intentionally) too long: to demonstrate that the timeout policy will time out on this before waiting for the retry.
- var waitAndRetryPolicy = Policy
- .Handle()
- .WaitAndRetryForever(
- attempt => TimeSpan.FromSeconds(4),
- (exception, calculatedWaitDuration) =>
- {
- progress.Report(ProgressWithMessage(".Log,then retry: " + exception.Message, Color.Yellow));
- retries++;
- });
-
- // Define a fallback policy: provide a nice substitute message to the user, if we found the call was rejected due to the timeout policy.
- var fallbackForTimeout = Policy
- .Handle()
- .Fallback(
- /* Demonstrates fallback value syntax */ "Please try again later [Fallback for timeout]",
- b =>
- {
- watch.Stop();
- progress.Report(ProgressWithMessage(
- "Fallback catches failed with: " + b.Exception.Message + " (after " +
- watch.ElapsedMilliseconds + "ms)", Color.Red));
- eventualFailuresDueToTimeout++;
- }
- );
-
- // Define a fallback policy: provide a substitute string to the user, for any exception.
- var fallbackForAnyException = Policy
- .Handle()
- .Fallback(
- /* Demonstrates fallback action/func syntax */ () =>
- {
- return "Please try again later [Fallback for any exception]";
- },
- e =>
- {
- watch.Stop();
-
- progress.Report(ProgressWithMessage(
- "Fallback catches eventually failed with: " + e.Exception.Message + " (after " +
- watch.ElapsedMilliseconds + "ms)", Color.Red));
-
- eventualFailuresForOtherReasons++;
- }
- );
-
-
- // Compared to previous demo08: here we use *instance* wrap syntax, to wrap all in one go.
- var policyWrap = fallbackForAnyException.Wrap(fallbackForTimeout).Wrap(timeoutPolicy)
- .Wrap(waitAndRetryPolicy);
-
- using (var client = new WebClient())
- {
- var internalCancel = false;
- totalRequests = 0;
- while (!internalCancel && !cancellationToken.IsCancellationRequested)
- {
- totalRequests++;
- watch = new Stopwatch();
- watch.Start();
-
- try
- {
- // Manage the call according to the whole policy wrap.
- var response =
- policyWrap.Execute(
- ct => client.DownloadString(Configuration.WEB_API_ROOT + "/api/values/" +
- totalRequests), cancellationToken);
-
- watch.Stop();
-
- progress.Report(ProgressWithMessage(
- "Response: " + response + "(after " + watch.ElapsedMilliseconds + "ms)", Color.Green));
-
- eventualSuccesses++;
- }
- catch (Exception e
- ) // try-catch not needed, now that we have a Fallback.Handle. It's only been left in to *demonstrate* it should never get hit.
- {
- throw new InvalidOperationException(
- "Should never arrive here. Use of fallbackForAnyException should have provided nice fallback value for any exceptions.",
- e);
- }
-
- // Wait half second
- Thread.Sleep(500);
-
- internalCancel = TerminateDemosByKeyPress && Console.KeyAvailable;
- }
- }
- }
-
- public override Statistic[] LatestStatistics => new[]
- {
- new Statistic("Total requests made", totalRequests),
- new Statistic("Requests which eventually succeeded", eventualSuccesses, Color.Green),
- new Statistic("Retries made to help achieve success", retries, Color.Yellow),
- new Statistic("Requests timed out by timeout policy", eventualFailuresDueToTimeout, Color.Magenta),
- new Statistic("Requests which failed after longer delay", eventualFailuresForOtherReasons, Color.Red),
- };
- }
-}
\ No newline at end of file