diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b0cf9c --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +Thumbs.db +*.obj +*.exe +*.pdb +*.user +*.ncb +*.suo +[Bb]in +[Dd]ebug*/ +[Rr]elease*/ +_ReSharper*/ +[Tt]est[Rr]esult* +*.csproj.user +*.orig +[Bb]in/ +[Oo]bj/ +[Oo]utput/ \ No newline at end of file diff --git a/.hgignore b/.hgignore deleted file mode 100644 index e9d486b..0000000 --- a/.hgignore +++ /dev/null @@ -1,6 +0,0 @@ -bin -obj -_ReSharper* -.suo -.user -ReliabilityPatterns.*.nupkg \ No newline at end of file diff --git a/ReliabilityPatterns/CircuitBreaker.cs b/ReliabilityPatterns/CircuitBreaker.cs index 4b8f8ee..68b3c57 100644 --- a/ReliabilityPatterns/CircuitBreaker.cs +++ b/ReliabilityPatterns/CircuitBreaker.cs @@ -98,6 +98,13 @@ public TResult Execute(Func operation) Interlocked.Increment(ref failureCount); OnServiceLevelChanged(new EventArgs()); + + //Re-evalutate the failure count to determine if the breaker should be tripped + if (failureCount >= threshold) + { + // Failure count has reached threshold, so trip circuit breaker + Trip(); + } } else if (failureCount >= threshold) { diff --git a/Tests/CircuitBreakerTestHelper.cs b/Tests/CircuitBreakerTestHelper.cs new file mode 100644 index 0000000..342b81e --- /dev/null +++ b/Tests/CircuitBreakerTestHelper.cs @@ -0,0 +1,61 @@ +using System; +using ReliabilityPatterns; + +namespace Tests +{ + public class CircuitBreakerTestHelper + { + readonly ushort _allowedRetries; + public CircuitBreaker Breaker { get; private set; } + public int CallCount { get; private set; } + + public CircuitBreakerTestHelper(uint threshold = 4, ushort allowedRetries = 0) + { + _allowedRetries = allowedRetries; + Breaker = new CircuitBreaker(threshold, TimeSpan.FromSeconds(120)); + } + + public void ExecuteFailingAction() + { + Console.WriteLine("Before: " + Breaker.State); + try + { + CallCount = 0; + if (_allowedRetries > 0) + { + Breaker.ExecuteWithRetries(() => + { + CallCount++; + throw new Exception("foo"); + }, new RetryOptions + { + AllowedRetries = _allowedRetries, + RetryInterval = TimeSpan.FromMilliseconds(10) + }); + } + else + { + Breaker.Execute(() => + { + CallCount++; + throw new Exception("foo"); + }); + } + } + catch (OpenCircuitException) + { + Console.WriteLine("Tripped due to open circuit"); + } + catch (OperationFailedException) + { + Console.WriteLine("Tripped due to unhandled exception"); + } + catch (AggregateException ex) + { + Console.WriteLine("Tripped due to unhandled exception. InnerExceptionCount: " + ex.InnerExceptions.Count); + } + + Console.WriteLine("After {0} attempt/s: {1}", CallCount, Breaker.State + Environment.NewLine); + } + } +} \ No newline at end of file diff --git a/Tests/CircuitBreakerTests.cs b/Tests/CircuitBreakerTests.cs index 7f05ec4..ca1b32b 100644 --- a/Tests/CircuitBreakerTests.cs +++ b/Tests/CircuitBreakerTests.cs @@ -60,5 +60,27 @@ public double ServiceLevel(string callPattern) return circuitBreaker.ServiceLevel; } + + [Test] + public void Execute_WhenThresholdIsSetTo4AndCalledWith4FailingActions_ShouldInvokeEachActionOnceAndTheCircuitBreakerShouldFinishOpen() + { + var testHelper = new CircuitBreakerTestHelper(4); + + testHelper.ExecuteFailingAction(); + Assert.AreEqual(1, testHelper.CallCount); + Assert.AreEqual(CircuitBreakerState.Closed, testHelper.Breaker.State); + + testHelper.ExecuteFailingAction(); + Assert.AreEqual(1, testHelper.CallCount); + Assert.AreEqual(CircuitBreakerState.Closed, testHelper.Breaker.State); + + testHelper.ExecuteFailingAction(); + Assert.AreEqual(1, testHelper.CallCount); + Assert.AreEqual(CircuitBreakerState.Closed, testHelper.Breaker.State); + + testHelper.ExecuteFailingAction(); + Assert.AreEqual(1, testHelper.CallCount); + Assert.AreEqual(CircuitBreakerState.Open, testHelper.Breaker.State); + } } } diff --git a/Tests/RetryExtensionsTests.cs b/Tests/RetryExtensionsTests.cs index d5514a2..b91d245 100644 --- a/Tests/RetryExtensionsTests.cs +++ b/Tests/RetryExtensionsTests.cs @@ -130,5 +130,19 @@ public void ShouldNotCallActionWhenCircuitBreakerIsOpen() } Assert.IsFalse(actionWasCalled); } + + [Test] + public void ExecuteWithRetries_WhenThresholdIsSetTo4AndAllowedRetriesIsSetTo3AndCalledWith2FailingActions_InvokesTheFirstAction3TimesAndInvokesTheSecondAction1TimeAndTheCircuitBreakerShouldFinishOpen() + { + var testHelper = new CircuitBreakerTestHelper(4, 3); + + testHelper.ExecuteFailingAction(); + Assert.AreEqual(3, testHelper.CallCount); + Assert.AreEqual(CircuitBreakerState.Closed, testHelper.Breaker.State); + + testHelper.ExecuteFailingAction(); + Assert.AreEqual(1, testHelper.CallCount); + Assert.AreEqual(CircuitBreakerState.Open, testHelper.Breaker.State); + } } } diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index c3b51f3..32cb1b9 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -49,6 +49,7 @@ +