private void ThrowOnNotRegistered(string key, out ICircuitBreakerPolicy policy)
 {
     if (!Registry.TryGet(key, out policy))
     {
         throw new ArgumentException($"No circuitbreaker registered with this key {key}", nameof(key));
     }
 }
示例#2
0
        public async Task HttpClientFactory_ApplyMultiplePolicies()
        {
            IServiceCollection services = new ServiceCollection();

            ///// ADD HTTP CLIENT VIA HTTPCLIENTFACTORY AND POLICIES
            // this would typically happen in Startup.ConfigureServices
            // using a named client, can specify a <TClient> to inject it into a specific class

            // setup circuit breaker seperately so you can maintain a pointer to it for testing (validate state, reset, etc)
            var circuitBreakerPolicy = DemoPolicyFactory.GetHttpCircuitBreakerPolicy();
            ICircuitBreakerPolicy <HttpResponseMessage> circuitBreakerPointer = (ICircuitBreakerPolicy <HttpResponseMessage>)circuitBreakerPolicy;

            services.AddHttpClient("CustomerService",
                                   client =>
            {
                client.BaseAddress = new Uri(DemoHelper.DemoBaseUrl);
                client.Timeout     =
                    TimeSpan.FromSeconds(
                        3);         // this will apply as an outer timeout to the entire request flow, including waits, retries,etc
            })

            // add policies from outer (1st executed) to inner (closest to dependency call)
            // all policies must implement IASyncPolicy
            .AddPolicyHandler(DemoPolicyFactory.GetHttpFallbackPolicy("Default!"))
            .AddPolicyHandler(DemoPolicyFactory.GetHttpRetryPolicy())
            .AddPolicyHandler(circuitBreakerPolicy)
            .AddPolicyHandler(DemoPolicyFactory.GetHttpInnerTimeoutPolicy());

            ///// TEST POLICIES /////
            await VerifyResiliencyPolicies(services, circuitBreakerPointer);
        }
示例#3
0
        // extracted this out to a seperate method since the above 2 demos are accomplishing the exact same thing
        private static async Task VerifyResiliencyPolicies(IServiceCollection services,
                                                           ICircuitBreakerPolicy <HttpResponseMessage> circuitBreakerPointer)
        {
            // you would typically inject an IHttpClientFactory (if using named clients) or HttpClient (if not) into your classes that need them
            var serviceProvider = services.BuildServiceProvider();
            var clientFactory   = serviceProvider.GetService <IHttpClientFactory>();

            // create client using factory
            var httpClient = clientFactory.CreateClient("CustomerService");

            // verify happy path
            var responseMessage = await httpClient.GetStringAsync("api/demo/success");

            Assert.AreEqual("Success!", responseMessage);

            // verify retry
            responseMessage = await httpClient.GetStringAsync("api/demo/error?failures=1");

            Assert.AreEqual("Success!", responseMessage);

            // verify timeout
            responseMessage = await httpClient.GetStringAsync("api/demo/slow");

            Assert.AreEqual("Default!", responseMessage);

            // verify circuit breaker
            for (int i = 0; i < 5; i++)
            {
                await httpClient.GetStringAsync("api/demo/error");
            }

            Assert.AreEqual(CircuitState.Open, circuitBreakerPointer.CircuitState);
        }
        public void Should_be_able_to_use_LastHandledResult_via_interface()
        {
            ICircuitBreakerPolicy <ResultPrimitive> breaker = Policy
                                                              .HandleResult(ResultPrimitive.Fault)
                                                              .CircuitBreaker(2, TimeSpan.FromMinutes(1));

            breaker.LastHandledResult.Should().Be(default(ResultPrimitive));
        }
示例#5
0
        public void Should_be_able_to_use_LastException_via_interface()
        {
            ICircuitBreakerPolicy breaker = Policy
                                            .Handle <DivideByZeroException>()
                                            .CircuitBreaker(2, TimeSpan.FromMinutes(1));

            breaker.LastException.Should().BeNull();
        }
示例#6
0
        public void Should_be_able_to_use_CircuitState_via_interface()
        {
            ICircuitBreakerPolicy breaker = Policy
                                            .Handle <DivideByZeroException>()
                                            .CircuitBreaker(2, TimeSpan.FromMinutes(1));

            breaker.CircuitState.Should().Be(CircuitState.Closed);
        }
 public SendToEndpointPolicy(
     ICompassEnvironment compassEnvironment,
     ICircuitBreakerPolicy circuitBreakerPolicy
     )
 {
     _compassEnvironment   = compassEnvironment;
     _circuitBreakerPolicy = circuitBreakerPolicy;
 }
示例#8
0
        public async Task AddResiliencyToHttpCall()
        {
            ///////////////////////// OUTER TIMEOUT POLICY /////////////////////////
            // for sync code, you should replace this by setting the timeout on the HttpClient
            IAsyncPolicy <HttpResponseMessage> outerTimeoutPolicy = Policy
                                                                    .TimeoutAsync <HttpResponseMessage>(TimeSpan.FromSeconds(10), TimeoutStrategy.Optimistic,
                                                                                                        onTimeoutAsync: PolicyLoggingHelper.LogTimeoutAsync);

            ///////////////////////// RETRY POLICY /////////////////////////
            // we are retrying on inner timeout, this might be a bad idea for many scenerios
            // you never want to retry on a circuit breaker exception
            IAsyncPolicy <HttpResponseMessage> retryPolicy = HttpPolicyExtensions
                                                             .HandleTransientHttpError()
                                                             .Or <TimeoutRejectedException>()
                                                             .WaitAndRetryAsync(1, retryAttempt => TimeSpan.FromMilliseconds(500),
                                                                                onRetryAsync: PolicyLoggingHelper.LogWaitAndRetryAsync);

            ///////////////////////// CIRCUIT BREAKER POLICY /////////////////////////
            // typical duration of break should be 30-60 seconds.  Using 5 for this demo to make demo run faster
            IAsyncPolicy <HttpResponseMessage> circuitBreakerAsyncPolicy = Policy
                                                                           .HandleResult <HttpResponseMessage>(resp => !resp.IsSuccessStatusCode) // consider if you want to ignore 404s
                                                                           .Or <Exception>()
                                                                           .CircuitBreakerAsync(5, TimeSpan.FromSeconds(5),
                                                                                                onBreak: PolicyLoggingHelper.LogCircuitBroken,
                                                                                                onReset: PolicyLoggingHelper.LogCircuitReset)
                                                                           .WithPolicyKey(nameof(circuitBreakerAsyncPolicy));

            // pointer to the circuit breaker so we can read/update its state as needed (haven't figured out a better way to do this)
            // this is EXTREMELY useful for resetting between unit tests (expose a reset method to allow callers to reset your circuit breaker)
            ICircuitBreakerPolicy <HttpResponseMessage> circuitBreaker = (ICircuitBreakerPolicy <HttpResponseMessage>)circuitBreakerAsyncPolicy;

            ///////////////////////// INNER TIMOUT POLICY (Optional) /////////////////////////
            // inner timeout is totally optional and depends on use case.
            // The outer timeout is the most important for requests with callers waiting
            // Sometimes you might want an inner timeout slightly lower than the outer just so the circuit breaker will break on too many timeouts
            IAsyncPolicy <HttpResponseMessage> innerTimeoutPolicy = Policy
                                                                    .TimeoutAsync <HttpResponseMessage>(TimeSpan.FromSeconds(3), TimeoutStrategy.Optimistic, onTimeoutAsync: PolicyLoggingHelper.LogTimeoutAsync);

            ///////////////////////// POLICY WRAP /////////////////////////
            // wrap policies from outer to inner
            var commonResiliencyPolicy = Policy
                                         .WrapAsync <HttpResponseMessage>(outerTimeoutPolicy, retryPolicy, circuitBreakerAsyncPolicy, innerTimeoutPolicy);

            ///////////////////////// FALLBACK POLICY (Wraps wrapped policy) /////////////////////////
            // alternative would be to wrap ExecuteAsync in try/catch and return default from there
            // I am wrapping the above resiliency policy to show that you can wrap a wrapped policy inside another policy
            // Fallback is a good use case for this since it can vary a lot between different calls
            IAsyncPolicy <HttpResponseMessage> resiliencyPolicyWithFallback = HttpPolicyExtensions
                                                                              .HandleTransientHttpError()
                                                                              .Or <TimeoutRejectedException>()
                                                                              .Or <BrokenCircuitException>()
                                                                              .FallbackAsync(new HttpResponseMessage
            {
                // fetch default values or execute whatever behavior you want to fall back to
                Content = new StringContent("Default!")
            }, onFallbackAsync: PolicyLoggingHelper.LogFallbackAsync)
                                                                              .WrapAsync(commonResiliencyPolicy);

            // verify retry on error
            var response = await resiliencyPolicyWithFallback.ExecuteAsync((ctx, ct) => DemoHelper.DemoClient.GetAsync("api/demo/error?failures=1", ct), new Context($"{nameof(AddResiliencyToHttpCall)}_1"), CancellationToken.None);

            Assert.IsTrue(response.IsSuccessStatusCode);

            // verify retry on client timeout
            response = await resiliencyPolicyWithFallback.ExecuteAsync((ctx, ct) => DemoHelper.DemoClient.GetAsync("api/demo/slow?failures=1", ct), new Context($"{nameof(AddResiliencyToHttpCall)}_2"), CancellationToken.None);

            Assert.IsTrue(response.IsSuccessStatusCode);

            // verify default if every call times out
            DemoHelper.Reset();
            response = await resiliencyPolicyWithFallback.ExecuteAsync((ctx, ct) => DemoHelper.DemoClient.GetAsync("api/demo/slow", ct), new Context($"{nameof(AddResiliencyToHttpCall)}_3"), CancellationToken.None);

            var result = await response.Content.ReadAsStringAsync();

            Assert.AreEqual("Default!", result);

            // verify circuit broken on errors
            for (int i = 0; i < 6; i++)
            {
                response = await resiliencyPolicyWithFallback.ExecuteAsync((ctx, ct) =>
                                                                           DemoHelper.DemoClient.GetAsync("api/demo/error", ct), new Context($"{nameof(AddResiliencyToHttpCall)}_4"), CancellationToken.None);

                result = await response.Content.ReadAsStringAsync();

                Assert.AreEqual("Default!", result);
            }

            Assert.AreEqual(CircuitState.Open, circuitBreaker.CircuitState);

            // let circuit breaker reset (goes to 1/2 open after 5 seconds)
            await Task.Delay(5100);

            Assert.AreEqual(CircuitState.HalfOpen, circuitBreaker.CircuitState);

            // verify circuit breaker is back open
            await commonResiliencyPolicy.ExecuteAsync((ctx, ct) => DemoHelper.DemoClient.GetAsync("api/demo/success", ct), new Context($"{nameof(AddResiliencyToHttpCall)}_5"), CancellationToken.None);

            Assert.AreEqual(CircuitState.Closed, circuitBreaker.CircuitState);
        }