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));
        }
Пример #2
0
        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);
        }