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

Fix stack trace growing for opened circuit breaker #1878

Merged
merged 4 commits into from
Jan 3, 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
22 changes: 10 additions & 12 deletions src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal sealed class CircuitStateController<T> : IDisposable
private DateTimeOffset _blockedUntil;
private CircuitState _circuitState = CircuitState.Closed;
private Outcome<T>? _lastOutcome;
private BrokenCircuitException _breakingException = new();
private Exception? _breakingException;
private bool _disposed;

#pragma warning disable S107
Expand Down Expand Up @@ -139,8 +139,8 @@ public ValueTask CloseCircuitAsync(ResilienceContext context)

exception = _circuitState switch
{
CircuitState.Open => _breakingException,
CircuitState.HalfOpen when !isHalfOpen => _breakingException,
CircuitState.Open => CreateBrokenCircuitException(),
CircuitState.HalfOpen when !isHalfOpen => CreateBrokenCircuitException(),
CircuitState.Isolated => new IsolatedCircuitException(),
_ => null
};
Expand Down Expand Up @@ -302,17 +302,15 @@ private bool PermitHalfOpenCircuitTest_NeedsLock()
private void SetLastHandledOutcome_NeedsLock(Outcome<T> outcome)
{
_lastOutcome = outcome;

if (outcome.Exception is Exception exception)
{
_breakingException = new BrokenCircuitException(BrokenCircuitException.DefaultMessage, exception);
}
else
{
_breakingException = new BrokenCircuitException(BrokenCircuitException.DefaultMessage);
}
_breakingException = outcome.Exception;
}

private BrokenCircuitException CreateBrokenCircuitException() => _breakingException switch
{
Exception exception => new BrokenCircuitException(BrokenCircuitException.DefaultMessage, exception),
_ => new BrokenCircuitException(BrokenCircuitException.DefaultMessage)
};

private void OpenCircuit_NeedsLock(Outcome<T> outcome, bool manual, ResilienceContext context, out Task? scheduledTask)
{
OpenCircuitFor_NeedsLock(outcome, _breakDuration, manual, context, out scheduledTask);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class BrokenCircuitExceptionTests
[Fact]
public void Ctor_Ok()
{
new BrokenCircuitException().Message.Should().Be("The circuit is now open and is not allowing calls.");
new BrokenCircuitException("Dummy.").Message.Should().Be("Dummy.");
new BrokenCircuitException("Dummy.", new InvalidOperationException()).Message.Should().Be("Dummy.");
new BrokenCircuitException("Dummy.", new InvalidOperationException()).InnerException.Should().BeOfType<InvalidOperationException>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,44 @@ public async Task OnActionPreExecute_CircuitOpenedByValue()
GetBlockedTill(controller).Should().Be(_timeProvider.GetUtcNow() + _options.BreakDuration);
}

[InlineData(true)]
[InlineData(false)]
[Theory]
public async Task OnActionPreExecute_CircuitOpened_EnsureExceptionStackTraceDoesNotGrow(bool innerException)
{
var stacks = new List<string>();
var context = ResilienceContextPool.Shared.Get();
using var controller = CreateController();

await OpenCircuit(
controller,
innerException ? Outcome.FromException<int>(new InvalidOperationException()) : Outcome.FromResult(99));

for (int i = 0; i < 100; i++)
{
try
{
(await controller.OnActionPreExecuteAsync(context)).Value.ThrowIfException();
}
catch (BrokenCircuitException e)
{
stacks.Add(e.StackTrace!);
e.Message.Should().Be("The circuit is now open and is not allowing calls.");

if (innerException)
{
e.InnerException.Should().BeOfType<InvalidOperationException>();
}
else
{
e.InnerException.Should().BeNull();
}
}
}

stacks.Distinct().Should().HaveCount(1);
}

[Fact]
public async Task HalfOpen_EnsureBreakDuration()
{
Expand Down