fix(download-clients): one circuit breaker per client, not one for all of them - #868
Open
m4bard wants to merge 1 commit into
Open
fix(download-clients): one circuit breaker per client, not one for all of them#868m4bard wants to merge 1 commit into
m4bard wants to merge 1 commit into
Conversation
…l of them AddDownloadClientHttpClients built a single circuit breaker and passed the same instance to the generic DownloadClient registration and to all four adapter clients. Polly's circuit breaker is stateful: the open and closed state and the failure count live in the policy object. Five registrations sharing one instance is one global breaker, not five, so three transient failures against any one client opened the circuit for every other one, including client types the deployment may not use. It compounds. Polly's simple breaker returns straight to Open on a single failed half-open trial, and with the breaker shared, that trial can be consumed by any of the five clients' next poll. Intermittent flakiness on one can keep re-tripping the breaker and starve the rest long after the original trigger cleared. Each client now builds its own via CreateCircuitBreakerPolicy(). The retry policy is deliberately still shared, because a retry policy is stateless and sharing one is both correct and how Polly is intended to be used. Only the stateful policy needed splitting, and the comment says which is which so the difference is not read as an oversight later. The test drives the policies directly rather than through HttpClient. Going through the named clients would also pass through the retry policy, whose backoff is 2, 4 and 8 seconds, so opening a breaker that way costs about 45 seconds for a property observable in milliseconds. It asserts the instances differ, opens the first, and then asserts the second still executes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AddDownloadClientHttpClientsbuilt one circuit breaker and passed the same instance to fiveHttpClientregistrations. Polly's circuit breaker is stateful, so that is one global breaker rather than one per client, and three transient failures against any single download client open the circuit for all of them.Full write-up in #867.
Changes
Fixed
HttpClientbuilds its own circuit breaker through aCreateCircuitBreakerPolicy()factory, rather than sharing one instance.Deliberately unchanged
WaitAndRetryAsyncis stateless, so sharing it is correct and is how Polly is intended to be used. Splitting it too would suggest the sharing itself was the mistake rather than the sharing of state. There is a comment at the registration saying which of the two is stateful, so the difference does not read as an oversight later.Why it outlasts its trigger
Polly's simple breaker returns straight to Open when one half-open trial fails, rather than requiring another run of three. With the breaker shared, that trial can be consumed by whichever client polls next, so a client that is still intermittently failing keeps re-tripping it and the others never get a successful trial through. A brief blip on one client becomes an open-ended outage for all of them.
Testing
DownloadClientCircuitBreakerIsolationTestsasserts the factory returns distinct instances, opens the first with three transient failures, confirms it then refuses calls, and confirms the second still executes.It drives the policies directly rather than through
HttpClienton purpose. Going through the named clients also passes through the retry policy, whose backoff is 2, 4 and 8 seconds, so opening a breaker that way costs about 45 seconds to observe a property that is visible in milliseconds otherwise.Verified as a real guard: with the factory changed to return one cached instance, which is the original defect, the test fails on
Assert.NotSame.Full suite: 3,030 passed, 0 failed, 125 skipped, against a 3,029 baseline on
03958c15.Note
Not a regression from recent work.
git diffon this file betweenf27c7989and current canary shows no changes.I have not reproduced the outage. What I verified is the registration, that one object reference reaches all five
AddPolicyHandlercalls, and Polly's documented behaviour for a stateful policy. The runtime evidence in the issue is a reporter's rather than mine, and the issue says so.