Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BreakDurationGeneratorArguments now includes half-open attempts #1898

Merged
merged 1 commit into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/Polly.Core/CircuitBreaker/BreakDurationGeneratorArguments.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.ComponentModel;

namespace Polly.CircuitBreaker;

#pragma warning disable CA1815 // Override equals and operator equals on value types
Expand All @@ -16,6 +18,7 @@ public readonly struct BreakDurationGeneratorArguments
/// This count is used to determine if the failure threshold has been reached.</param>
/// <param name="context">The resilience context providing additional information
/// about the execution state and failures.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public BreakDurationGeneratorArguments(
double failureRate,
int failureCount,
Expand All @@ -24,6 +27,29 @@ public BreakDurationGeneratorArguments(
FailureRate = failureRate;
FailureCount = failureCount;
Context = context;
HalfOpenAttempts = 0;
}

/// <summary>
/// Initializes a new instance of the <see cref="BreakDurationGeneratorArguments"/> struct.
/// </summary>
/// <param name="failureRate">The failure rate at which the circuit breaker should trip.
/// It represents the ratio of failed actions to the total executed actions.</param>
/// <param name="failureCount">The number of failures that have occurred.
/// This count is used to determine if the failure threshold has been reached.</param>
/// <param name="context">The resilience context providing additional information
/// about the execution state and failures.</param>
/// <param name="halfOpenAttempts">The number of half-open attempts.</param>
public BreakDurationGeneratorArguments(
double failureRate,
int failureCount,
ResilienceContext context,
int halfOpenAttempts)
{
FailureRate = failureRate;
FailureCount = failureCount;
Context = context;
HalfOpenAttempts = halfOpenAttempts;
}

/// <summary>
Expand All @@ -40,4 +66,9 @@ public BreakDurationGeneratorArguments(
/// Gets the context that provides additional information about the resilience operation.
/// </summary>
public ResilienceContext Context { get; }

/// <summary>
/// Gets the number of half-open attempts.
/// </summary>
public int HalfOpenAttempts { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal sealed class CircuitStateController<T> : IDisposable
private Outcome<T>? _lastOutcome;
private Exception? _breakingException;
private bool _disposed;
private int _halfOpenAttempts;

#pragma warning disable S107
public CircuitStateController(
Expand Down Expand Up @@ -132,6 +133,7 @@ public ValueTask CloseCircuitAsync(ResilienceContext context)
// check if circuit can be half-opened
if (_circuitState == CircuitState.Open && PermitHalfOpenCircuitTest_NeedsLock())
{
_halfOpenAttempts++;
_circuitState = CircuitState.HalfOpen;
_telemetry.Report(new(ResilienceEventSeverity.Warning, CircuitBreakerConstants.OnHalfOpenEvent), context, new OnCircuitHalfOpenedArguments(context));
isHalfOpen = true;
Expand Down Expand Up @@ -270,6 +272,7 @@ private void CloseCircuit_NeedsLock(Outcome<T> outcome, bool manual, ResilienceC

_blockedUntil = DateTimeOffset.MinValue;
_lastOutcome = null;
_halfOpenAttempts = 0;

CircuitState priorState = _circuitState;
_circuitState = CircuitState.Closed;
Expand Down Expand Up @@ -325,7 +328,7 @@ private void OpenCircuitFor_NeedsLock(Outcome<T> outcome, TimeSpan breakDuration
{
#pragma warning disable CA2012
#pragma warning disable S1226
breakDuration = _breakDurationGenerator(new(_behavior.FailureRate, _behavior.FailureCount, context)).GetAwaiter().GetResult();
breakDuration = _breakDurationGenerator(new(_behavior.FailureRate, _behavior.FailureCount, context, _halfOpenAttempts)).GetAwaiter().GetResult();
#pragma warning restore S1226
#pragma warning restore CA2012
}
Expand Down
2 changes: 2 additions & 0 deletions src/Polly.Core/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#nullable enable
Polly.CircuitBreaker.BreakDurationGeneratorArguments.BreakDurationGeneratorArguments(double failureRate, int failureCount, Polly.ResilienceContext! context, int halfOpenAttempts) -> void
Polly.CircuitBreaker.BreakDurationGeneratorArguments.HalfOpenAttempts.get -> int
Polly.Simmy.Behavior.BehaviorActionArguments
Polly.Simmy.Behavior.BehaviorActionArguments.BehaviorActionArguments() -> void
Polly.Simmy.Behavior.BehaviorActionArguments.BehaviorActionArguments(Polly.ResilienceContext! context) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Polly.Core.Tests.CircuitBreaker;
public class BreakDurationGeneratorArgumentsTests
{
[Fact]
public void Constructor_ShouldSetFailureRate()
public void Constructor_Old_Ok()
{
double expectedFailureRate = 0.5;
int failureCount = 10;
Expand All @@ -15,29 +15,22 @@ public void Constructor_ShouldSetFailureRate()
var args = new BreakDurationGeneratorArguments(expectedFailureRate, failureCount, context);

args.FailureRate.Should().Be(expectedFailureRate);
args.FailureCount.Should().Be(failureCount);
args.Context.Should().Be(context);
}

[Fact]
public void Constructor_ShouldSetFailureCount()
public void Constructor_Ok()
{
double failureRate = 0.5;
int expectedFailureCount = 10;
var context = new ResilienceContext();

var args = new BreakDurationGeneratorArguments(failureRate, expectedFailureCount, context);

args.FailureCount.Should().Be(expectedFailureCount);
}

[Fact]
public void Constructor_ShouldSetContext()
{
double failureRate = 0.5;
double expectedFailureRate = 0.5;
int failureCount = 10;
var expectedContext = new ResilienceContext();
var context = new ResilienceContext();

var args = new BreakDurationGeneratorArguments(failureRate, failureCount, expectedContext);
var args = new BreakDurationGeneratorArguments(expectedFailureRate, failureCount, context, 99);

args.Context.Should().Be(expectedContext);
args.FailureRate.Should().Be(expectedFailureRate);
args.FailureCount.Should().Be(failureCount);
args.Context.Should().Be(context);
args.HalfOpenAttempts.Should().Be(99);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,41 @@ public async Task OnActionFailureAsync_EnsureBreakDurationGeneration()
blockedTill.Should().Be(utcNow + TimeSpan.FromMinutes(42));
}

[Fact]
public async Task BreakDurationGenerator_EnsureHalfOpenAttempts()
{
// arrange
var halfOpenAttempts = new List<int>();

_options.BreakDurationGenerator = args =>
{
halfOpenAttempts.Add(args.HalfOpenAttempts);
return new ValueTask<TimeSpan>(TimeSpan.Zero);
};

using var controller = CreateController();

// act
await TransitionToState(controller, CircuitState.Closed);

for (int i = 0; i < 5; i++)
{
await TransitionToState(controller, CircuitState.Open);
await TransitionToState(controller, CircuitState.HalfOpen);
}

await TransitionToState(controller, CircuitState.Closed);

for (int i = 0; i < 3; i++)
{
await TransitionToState(controller, CircuitState.Open);
await TransitionToState(controller, CircuitState.HalfOpen);
}

// assert
halfOpenAttempts.Should().BeEquivalentTo([0, 1, 2, 3, 4, 0, 1, 2]);
}

[InlineData(true)]
[InlineData(false)]
[Theory]
Expand Down Expand Up @@ -502,6 +537,7 @@ private async Task TransitionToState(CircuitStateController<int> controller, Cir
switch (state)
{
case CircuitState.Closed:
await controller.CloseCircuitAsync(ResilienceContextPool.Shared.Get());
break;
case CircuitState.Open:
await OpenCircuit(controller);
Expand Down
Loading