fix: coalesce forced rediscovery instead of spawning a task per failure - #592
fix: coalesce forced rediscovery instead of spawning a task per failure#592sshaplygin wants to merge 2 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #592 +/- ##
==========================================
+ Coverage 86.91% 87.15% +0.24%
==========================================
Files 198 198
Lines 19492 19750 +258
==========================================
+ Hits 16941 17214 +273
+ Misses 2551 2536 -15
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
`discovery.rs` sat at 34.4% line coverage, with everything but two narrow cases reachable only through the `#[ignore]` tests that need a live YDB. Cover the module offline instead. - `DiscoveryState`: node exposure, the empty default, pessimization and its idempotence, the fallback that restores every node once all of them are pessimized, and `with_node_info`. - Endpoint conversion: http/https selection by the ssl flag, and rejection of a malformed authority. - `StaticDiscovery`: state, waiter, the no-op pessimization, the empty subscription, and a malformed endpoint. - `DiscoverySharedState`: no state before the first discovery, `state()` resolving when one is published, `wait()` surfacing a stored error, `subscribe()` filtering failures out, and pessimization updating the published state. - The rediscovery coalescing this PR adds: a pessimization storm past the threshold schedules exactly one refresh, and a later storm can schedule another once the first released the flag. The test waits for that release, so the spawned task body is actually exercised rather than merely spawned. - `background_discovery` exits once the shared state is dropped. Nothing contacts a server: refreshes point at a closed loopback port, which is refused immediately. Coverage of the file: lines 34.4% -> 87.3%, regions 38.8% -> 88.8%, functions 31.6% -> 94.9%. Excluding the test module itself, the production code goes from 36.3% to 97.4% of lines; what remains is the token-waiter error branch in `background_discovery`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Pushed
Those are whole-file numbers, so they include the inline test module. Excluding it, the production code alone goes 36.3% → 97.4% of lines. The module was almost entirely reachable only through the two
On the coalescing itselfThe first version of the storm test spawned the refresh but never waited for it, so lines 252–256 — the spawned task body, the core of this PR — stayed uncovered even though the test passed. It now waits for Nothing contacts a server: refreshes point at a closed loopback port, refused immediately. The one production branch still uncovered is the token-waiter error path in
🤖 Generated with Claude Code |
Problem
TimerDiscovery::pessimizespawns a forced rediscovery whenever at least half the nodes are pessimized:pessimizeis called per failing request. During an outage — exactly when that condition holds — every failure spawns another task. They all queue on the samediscovery_lock, so the work is serialised but the tasks are not: the SDK piles up rediscovery tasks against a cluster that is already struggling, which is the opposite of what a backoff should do.Change
Guard the spawn with an
AtomicBoolonDiscoverySharedState. The first caller claims the refresh viacompare_exchange; further callers observe one already in flight and skip. The flag is released oncediscovery_nowreturns, so the next burst can trigger a fresh refresh.This coalesces the storm without changing when a refresh happens, or the existing
discovery_lockserialisation.Test
forced_discovery_coalesces_requests_until_completionasserts a second claim is refused while one is in flight, and that a claim succeeds again after completion.Note for reviewers
The flag is cleared after
discovery_now().awaitreturns, including onErr. If that spawned task were cancelled — for example at runtime shutdown — the flag would stay set and forced rediscovery would be disabled for the remaining life of the state. That seemed acceptable given the task only runs for as long as the driver does, but say the word if you would rather see it held by a guard that clears on drop.Verification
🤖 Generated with Claude Code