public void Should_Retry_Once_And_Interrupt_The_Attempt_Due_To_The_Retries_Exceeds_The_Timeout_And_Raises_A_TimeoutRejectedException() { var expectedRetries = 1; var expectedTimeout = GetExpectedTimeout(5); var builder = new SqlPolicyBuilder(); var resilientAsyncPolicy = builder .UseAsyncExecutor() .WithDefaultPolicies() .Build(); var watch = Stopwatch.StartNew(); var ex = Assert.ThrowsAsync <TimeoutRejectedException>(async() => { await resilientAsyncPolicy.ExecuteAsync(async() => { await DoSomethingAsync(45000); }); }); watch.Stop(); var currentTime = TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).TotalSeconds; Assert.IsInstanceOf <TimeoutRejectedException>(ex); Assert.AreEqual(expectedTimeout.TotalSeconds, Math.Round(currentTime)); }
public static IServiceCollection AddResilientStrategies(this IServiceCollection services, IConfiguration configuration) { // Resilient SQL Executor configuration. services.AddSingleton <IPolicyAsyncExecutor>(sp => { var sqlPolicyBuilder = new SqlPolicyBuilder(); return(sqlPolicyBuilder .UseAsyncExecutor() .WithDefaultPolicies() .Build()); }); // Create (and register with DI) a policy registry containing some policies we want to use. var policyRegistry = services.AddPolicyRegistry(); policyRegistry[HttpResiliencePolicy] = GetResiliencePolicy(configuration); // Resilient Http Invoker onfiguration. // Register a typed client via HttpClientFactory, set to use the policy we placed in the policy registry. services.AddHttpClient <ResilientHttpClient>(client => { client.Timeout = TimeSpan.FromSeconds(50); }).AddPolicyHandlerFromRegistry(HttpResiliencePolicy); return(services); }
public async Task Should_Break_The_Circuit_Across_Requests_And_Fail_Faster_Then_Wait_For_The_Break_And_Try_Again_Then_Does_Not_Raise_An_Exception() { var builderFirstRequest = new SqlPolicyBuilder(); var resilientAsyncPolicyFirstRequest = builderFirstRequest .UseAsyncExecutorWithSharedPolicies() .WithDefaultPolicies() .Build(); var exFirstAttempt = Assert.ThrowsAsync <BrokenCircuitException>(async() => { await resilientAsyncPolicyFirstRequest.ExecuteAsync(async() => { // will break the circuit since throws 3 times in a row the same exception await DoSomethingToBreakTheCircuitAndContinueAsync(); }); }); // simulates a different request var builderSecondRequest = new SqlPolicyBuilder(); var resilientAsyncPolicySecondRequest = builderSecondRequest .UseAsyncExecutorWithSharedPolicies() .WithDefaultPolicies() .Build(); var watch2 = Stopwatch.StartNew(); var exSecondAttempt = Assert.ThrowsAsync <BrokenCircuitException>(async() => { await resilientAsyncPolicySecondRequest.ExecuteAsync(async() => { // will fail immediately await DoSomethingToBreakTheCircuitAndContinueAsync(); }); }); watch2.Stop(); // await the break time await Task.Delay(TimeSpan.FromSeconds(30)); // simulates a different request var builderThirdRequest = new SqlPolicyBuilder(); var resilientAsyncPolicyThirdRequest = builderThirdRequest .UseAsyncExecutorWithSharedPolicies() .WithDefaultPolicies() .Build(); // will executes successfully because the circuit is half-open now var result = await resilientAsyncPolicyThirdRequest.ExecuteAsync(async() => { await DoSomethingToBreakTheCircuitAndContinueAsync(); return(true); }); var attemptTimeAfterCircuitBroke = TimeSpan.FromMilliseconds(watch2.ElapsedMilliseconds).Seconds; Assert.AreEqual(true, result); Assert.IsInstanceOf <BrokenCircuitException>(exFirstAttempt); Assert.IsInstanceOf <BrokenCircuitException>(exSecondAttempt); Assert.AreEqual(0, attemptTimeAfterCircuitBroke); }
public void Should_Retry_Four_Times_Without_Break_The_Circuit_And_Does_Not_Raise_An_Exception() { var expectedRetries = 4; var expectedTime = GetExpectedDelay(expectedRetries) + GetExpectedExecutionTime(expectedRetries); var builder = new SqlPolicyBuilder(); var resilientSyncPolicy = builder .UseSyncExecutor() .WithDefaultPolicies() .Build(); var watch = Stopwatch.StartNew(); var result = resilientSyncPolicy.Execute(() => { // won't break the circuit since throws random exceptions. DoSomethingSync(); return(true); }); watch.Stop(); var currentTime = TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).Seconds; Assert.AreEqual(true, result); Assert.AreEqual(expectedTime.Seconds, currentTime); }
public async Task Should_Fallback_After_Circuit_Breaker_Exception() { var result = false; var expectedRetries = 3; // the limitation with this way to handle a fallback is if we want to return a result we need to handle that result separately from the main method and the fallback method var builder = new SqlPolicyBuilder(); var resilientAsyncPolicy = builder .UseAsyncExecutor() .WithDefaultPolicies() .WithTransaction() .WithFallback(async() => result = await DoFallbackAsync()) .Build(); await resilientAsyncPolicy.ExecuteAsync(async() => { await Task.Delay(TimeSpan.FromMilliseconds(500)); ++_retriesCounter; throw TransientExceptions[1]; }); Assert.AreEqual(true, result); Assert.AreEqual(true, _fallbackExecuted); Assert.AreEqual(expectedRetries, _retriesCounter); }
public void Should_Interrupt_Retry_Due_To_Operation_Exceeds_Timeout_Per_Retry() { var expectedTime = TimeSpan.FromMilliseconds(300); var builder = new SqlPolicyBuilder(); var resilientAsyncPolicy = builder .UseAsyncExecutor() .WithDefaultPolicies() .WithTimeoutPerRetry(TimeSpan.FromMilliseconds(300)) .Build(); var watch = new Stopwatch(); var ex = Assert.ThrowsAsync <TimeoutRejectedException>(async() => { await resilientAsyncPolicy.ExecuteAsync(async() => { watch.Start(); await Task.Delay(TimeSpan.FromMilliseconds(400)); }); }); watch.Stop(); Assert.IsInstanceOf <TimeoutRejectedException>(ex); Assert.IsTrue(watch.ElapsedMilliseconds >= expectedTime.Milliseconds && watch.ElapsedMilliseconds < 350); }
public async Task Should_Retry_Transaction_Errors_Four_Times_Without_Break_The_Circuit_And_Does_Not_Raise_An_Exception() { var expectedRetries = 4; var expectedTime = GetExpectedDelay(expectedRetries) + GetExpectedExecutionTime(expectedRetries); var builder = new SqlPolicyBuilder(); var resilientAsyncPolicy = builder .UseAsyncExecutor() .WithDefaultPolicies() .WithTransaction() .Build(); var watch = Stopwatch.StartNew(); var result = await resilientAsyncPolicy.ExecuteAsync(async() => { await DoSomethingWithTransactionAsync(); return(true); }); watch.Stop(); var currentTime = TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).Seconds; Assert.AreEqual(true, result); Assert.AreEqual(_threshold, _retriesCounter); }
public async Task Should_Fallback_After_Timeout_Exception() { var result = false; var expectedRetries = 0; var expectedTimeout = GetExpectedTimeout(5); // the limitation with this way to handle a fallback is if we want to return a result we need to handle that result separately from the main method and the fallback method var builder = new SqlPolicyBuilder(); var resilientAsyncPolicy = builder .UseAsyncExecutor() .WithDefaultPolicies() .WithTransaction() .WithFallback(async() => result = await DoFallbackAsync()) .Build(); var watch = Stopwatch.StartNew(); await resilientAsyncPolicy.ExecuteAsync(async() => { result = await DoSomethingWithTransactionAsync(120000); }); watch.Stop(); var currentTime = TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).Seconds; Assert.AreEqual(true, result); Assert.AreEqual(true, _fallbackExecuted); Assert.AreEqual(expectedRetries, _retriesCounter); Assert.AreEqual(expectedTimeout.Seconds, currentTime); }
public void Should_Retry_Transaction_Errors_Three_Times_The_Same_Exception_And_The_Fourth_Attempt_Break_The_Circuit_And_Raise_A_BrokenCircuitException() { var expectedRetries = 3; var expectedTime = GetExpectedDelay(expectedRetries) + GetExpectedExecutionTime(expectedRetries); var builder = new SqlPolicyBuilder(); var resilientAsyncPolicy = builder .UseAsyncExecutor() .WithDefaultPolicies() .WithTransaction() .Build(); var watch = Stopwatch.StartNew(); var ex = Assert.ThrowsAsync <BrokenCircuitException>(async() => { await resilientAsyncPolicy.ExecuteAsync(async() => { // will break the circuit since throws 3 times in a row the same exception await DoSomethingWithTransactionToBreakTheCircuitAsync(); }); }); watch.Stop(); var currentTime = TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).Seconds; Assert.IsInstanceOf <BrokenCircuitException>(ex); Assert.AreEqual(ex.Message, "The circuit is now open and is not allowing calls."); Assert.AreEqual(expectedTime.Seconds, currentTime); Assert.AreEqual(expectedRetries, _retriesCounter); }
public void Should_Do_Not_Retry_Due_To_The_Operation_Exceeds_The_Timeout_And_Raises_A_TimeoutRejectedException() { var expectedRetries = 0; var expectedTimeout = GetExpectedTimeout(5); var builder = new SqlPolicyBuilder(); var resilientSyncPolicy = builder .UseSyncExecutor() .WithDefaultPolicies() .Build(); var watch = Stopwatch.StartNew(); var ex = Assert.Throws <TimeoutRejectedException>(() => { resilientSyncPolicy.Execute(() => { DoSomethingSync(120000); }); }); watch.Stop(); var currentTime = TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).TotalSeconds; Assert.IsInstanceOf <TimeoutRejectedException>(ex); Assert.AreEqual(expectedTimeout.TotalSeconds, Math.Round(currentTime)); Assert.AreEqual(expectedRetries, _retriesCounter); }
public async Task Should_Transaction_Errors_Break_The_Circuit_And_Wait_For_The_Break_And_Try_Again_And_Does_Not_Raise_An_Exception() { var expectedRetries = 3; // 3 retries with exponential back-off + 4 executions (last is the successful one) + circuit break time var expectedTime = GetExpectedDelay(expectedRetries) + GetExpectedExecutionTime(4) + TimeSpan.FromSeconds(30); // IMPORTANT NOTE: in a real scenario, this behavior will only happen if we share the same instance of policies among requests. var builder = new SqlPolicyBuilder(); var resilientAsyncPolicy = builder .UseAsyncExecutor() .WithDefaultPolicies() .WithTransaction() .Build(); var watch = Stopwatch.StartNew(); var exFirstAttempt = Assert.ThrowsAsync <BrokenCircuitException>(async() => { await resilientAsyncPolicy.ExecuteAsync(async() => { // will break the circuit since throws 3 times in a row the same exception await DoSomethingWithTransactionToBreakTheCircuitAndContinueAsync(); }); }); var watch2 = Stopwatch.StartNew(); var exSecondAttempt = Assert.ThrowsAsync <BrokenCircuitException>(async() => { await resilientAsyncPolicy.ExecuteAsync(async() => { // will fail immediately await DoSomethingWithTransactionToBreakTheCircuitAndContinueAsync(); }); }); watch2.Stop(); // await the break time await Task.Delay(TimeSpan.FromSeconds(30)); // will executes successfully because the circuit is half-open now var result = await resilientAsyncPolicy.ExecuteAsync(async() => { await DoSomethingWithTransactionToBreakTheCircuitAndContinueAsync(); return(true); }); watch.Stop(); var currentTime = TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).Seconds; var attemptTimeAfterCircuitBroke = TimeSpan.FromMilliseconds(watch2.ElapsedMilliseconds).Seconds; Assert.AreEqual(true, result); Assert.IsInstanceOf <BrokenCircuitException>(exFirstAttempt); Assert.IsInstanceOf <BrokenCircuitException>(exSecondAttempt); Assert.AreEqual(expectedTime.Seconds, currentTime); Assert.AreEqual(0, attemptTimeAfterCircuitBroke); }
public void Should_Fails_Due_To_There_Are_No_Policies() { var exception = Assert.Throws <InvalidOperationException>(() => { var builder = new SqlPolicyBuilder(); var resilientAsyncPolicy = builder .UseAsyncExecutor() .Build(); }); Assert.IsInstanceOf <InvalidOperationException>(exception); Assert.AreEqual(exception.Message, "There are no policies to execute."); }
public void Should_Fails_Due_To_There_Is_TimeOut_Per_Retry_Policy_But_There_Are_NotRetry_Policies() { var exception = Assert.Throws <InvalidOperationException>(() => { var builder = new SqlPolicyBuilder(); var resilientAsyncPolicy = builder .UseAsyncExecutor() .WithTimeoutPerRetry(TimeSpan.FromSeconds(20)) .Build(); }); Assert.IsInstanceOf <InvalidOperationException>(exception); Assert.AreEqual(exception.Message, "You're trying to use Timeout per retries but you don't have Retry policies configured."); }
public void Should_Fails_Due_To_There_Are_Duplicated_Policies() { var exception = Assert.Throws <InvalidOperationException>(() => { var builder = new SqlPolicyBuilder(); var resilientAsyncPolicy = builder .UseAsyncExecutor() .WithDefaultPolicies() .WithTransientErrors() .Build(); }); Assert.IsInstanceOf <InvalidOperationException>(exception); Assert.AreEqual(exception.Message, "There are duplicated policies. When you use WithDefaultPolicies method, you can't use either WithTransientErrors. WithCircuitBreaker or WithOverallTimeout methods at the same time, because those policies are already included."); }
public void Should_Retry_Transaction_Errors_Four_Times_Without_Break_The_Circuit_And_Does_Not_Raise_An_Exception() { var builder = new SqlPolicyBuilder(); var resilientSyncPolicy = builder .UseSyncExecutor() .WithDefaultPolicies() .WithTransaction() .Build(); var watch = Stopwatch.StartNew(); var result = resilientSyncPolicy.Execute(() => { DoSomethingWithTransactionSync(); return(true); }); watch.Stop(); Assert.AreEqual(true, result); Assert.AreEqual(_threshold, _retriesCounter); }
public void Should_Interrupt_Retry_Due_To_Operation_Exceeds_Timeout_Per_Retry() { var builder = new SqlPolicyBuilder(); var resilientSyncPolicy = builder .UseSyncExecutor() .WithDefaultPolicies() .WithTimeoutPerRetry(TimeSpan.FromMilliseconds(300)) .Build(); var watch = new Stopwatch(); var ex = Assert.Throws <TimeoutRejectedException>(() => { resilientSyncPolicy.Execute(() => { watch.Start(); SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); }); }); watch.Stop(); Assert.IsInstanceOf <TimeoutRejectedException>(ex); }
public void Should_Fallback_After_Sql_Exception() { var result = false; // the limitation with this way to handle a fallback is if we want to return a result we need to handle that result separately from the main method and the fallback method var builder = new SqlPolicyBuilder(); var resilientSyncPolicy = builder .UseSyncExecutor() .WithDefaultPolicies() .WithTransaction() .WithFallback(() => result = DoFallbackSync()) .Build(); resilientSyncPolicy.Execute(() => { Thread.Sleep(TimeSpan.FromMilliseconds(500)); throw GetRandomException(); }); Assert.AreEqual(true, result); Assert.AreEqual(true, _fallbackExecuted); }