public async Task MultipleHandlers_CanReexecuteSendAsync_FirstResponseDisposed() { // Arrange var policy1 = HttpPolicyExtensions.HandleTransientHttpError() .RetryAsync(retryCount: 1); var policy2 = HttpPolicyExtensions.HandleTransientHttpError() .CircuitBreakerAsync(handledEventsAllowedBeforeBreaking: 2, durationOfBreak: TimeSpan.FromSeconds(10)); var callCount = 0; var fakeContent = new FakeContent(); var firstResponse = new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.InternalServerError, Content = fakeContent, }; var expected = new HttpResponseMessage(); var handler1 = new PolicyHttpMessageHandler(policy1); var handler2 = new PolicyHttpMessageHandler(policy2); handler1.InnerHandler = handler2; handler2.InnerHandler = new TestHandler() { OnSendAsync = (req, ct) => { if (callCount == 0) { callCount++; return(Task.FromResult(firstResponse)); } else if (callCount == 1) { callCount++; return(Task.FromResult(expected)); } else { throw new InvalidOperationException(); } } }; var invoke = new HttpMessageInvoker(handler1); // Act var response = await invoke.SendAsync(new HttpRequestMessage(), CancellationToken.None); // Assert Assert.Equal(2, callCount); Assert.Same(expected, response); Assert.True(fakeContent.Disposed); }
public async Task SendAsync_StaticPolicy_PolicyTriggers_CanReexecuteSendAsync_FirstResponseDisposed() { // Arrange var policy = HttpPolicyExtensions.HandleTransientHttpError() .RetryAsync(retryCount: 1); var callCount = 0; var fakeContent = new FakeContent(); var firstResponse = new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.InternalServerError, Content = fakeContent, }; var expected = new HttpResponseMessage(); var handler = new PolicyHttpMessageHandler(policy); handler.InnerHandler = new TestHandler() { OnSendAsync = (req, ct) => { if (callCount == 0) { callCount++; return(Task.FromResult(firstResponse)); } else if (callCount == 1) { callCount++; return(Task.FromResult(expected)); } else { throw new InvalidOperationException(); } } }; var invoke = new HttpMessageInvoker(handler); // Act var response = await invoke.SendAsync(new HttpRequestMessage(), CancellationToken.None); // Assert Assert.Equal(2, callCount); Assert.Same(expected, response); Assert.True(fakeContent.Disposed); }
private async Task <VssConnection> ConnectToAzureDevOpsAsync(WorkItemEventContext eventContext, VssCredentials clientCredentials, CancellationToken cancellationToken) { // see https://docs.microsoft.com/en-us/azure/devops/integrate/concepts/rate-limits#api-client-experience var policy = Policy .Handle <HttpRequestException>() // https://github.com/App-vNext/Polly/wiki/Retry#retryafter-when-the-response-specifies-how-long-to-wait .OrResult <HttpResponseMessage>(r => r.StatusCode == (HttpStatusCode)429) .WaitAndRetryAsync( retryCount: MaxRetries, sleepDurationProvider: (retryCount, response, context) => { return(response.Result?.Headers.RetryAfter.Delta.Value ?? TimeSpan.FromSeconds(BaseRetryInterval * retryCount)); }, #pragma warning disable CS1998 onRetryAsync: async(response, timespan, retryCount, context) => { logger.WriteInfo($"{Environment.NewLine}Waiting {timespan} before retrying (attemp #{retryCount}/{MaxRetries})..."); } #pragma warning restore CS1998 ); var handler = new PolicyHttpMessageHandler(policy); var vssHandler = new VssHttpMessageHandler(clientCredentials, VssClientHttpRequestSettings.Default.Clone()); var devops = new VssConnection(eventContext.CollectionUri, vssHandler, new DelegatingHandler[] { handler }); try { await devops.ConnectAsync(cancellationToken); logger.WriteInfo($"Connected to Azure DevOps"); } catch (System.Exception ex) { logger.WriteError(ex.Message); if (ex.InnerException != null) { logger.WriteError(ex.InnerException.Message); } throw; } return(devops); }
public RetryEnabledHttpClientProxy(HttpMessageHandler httpMessageHandler) { AsyncRetryPolicy <HttpResponseMessage> retryPolicy = HttpPolicyExtensions .HandleTransientHttpError() // HttpRequestException, 5XX and 408 .OrResult(r => r.StatusCode == HttpStatusCode.NotFound) .WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) }, (x, i) => x.Result.Dispose()); var policyHttpMessageHandler = new PolicyHttpMessageHandler(retryPolicy); try { var timeoutHandler = new TimeoutHandler { InnerHandler = httpMessageHandler }; policyHttpMessageHandler.InnerHandler = timeoutHandler; _httpClient = new HttpClient(policyHttpMessageHandler); } catch { policyHttpMessageHandler.Dispose(); throw; } }
private static async Task TestRetry(string collectionUrl, string devOpsToken, int worktItemId, CancellationToken cancellationToken) { // see https://docs.microsoft.com/en-us/azure/devops/integrate/concepts/rate-limits#api-client-experience int MaxRetries = 3; var policy = Policy .Handle <HttpRequestException>() // https://github.com/App-vNext/Polly/wiki/Retry#retryafter-when-the-response-specifies-how-long-to-wait .OrResult <HttpResponseMessage>(r => r.StatusCode == (HttpStatusCode)429) .WaitAndRetryAsync( retryCount: MaxRetries, sleepDurationProvider: (retryCount, response, context) => { return(response.Result?.Headers.RetryAfter.Delta.Value ?? TimeSpan.FromSeconds(30 * retryCount)); }, onRetryAsync: async(response, timespan, retryCount, context) => { await Console.Out.WriteLineAsync($"{Environment.NewLine}Waiting {timespan} before retrying (attemp #{retryCount}/{MaxRetries})..."); } ); var handler = new PolicyHttpMessageHandler(policy); var clientCredentials = new VssBasicCredential("", devOpsToken); var vssHandler = new VssHttpMessageHandler(clientCredentials, VssClientHttpRequestSettings.Default.Clone()); using var devops = new VssConnection(new Uri(collectionUrl), vssHandler, new DelegatingHandler[] { handler }); await devops.ConnectAsync(cancellationToken); using var witClient = await devops.GetClientAsync <WorkItemTrackingHttpClient>(cancellationToken); for (int i = 0; i < 10; i++) { _ = await witClient.GetWorkItemAsync(worktItemId); Console.Write('+'); } devops.Disconnect(); }