fix(downloader): defer rescue timers at full concurrency instead of dropping them - #145
Open
detail-app[bot] wants to merge 1 commit into
Conversation
|
PR author is not in the allowed authors list. |
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.
Detail bug report: View on Detail
Bug
In
download_replicas(src/object_store/downloader/replicas.rs), the multi-replica read path bounds concurrent copies atMAX_CONCURRENT_COPIES = 3. The rescueselect!arm eagerly consumed the next rescue entry withrescues.pop_front(), while the actual launch was gated after the pop byactive.len() >= MAX_CONCURRENT_COPIES. When a rescue timer fired while 3 copies were already in flight, the loop hitcontinueand the rescue entry was permanently discarded — no code path ever re-enqueued it.With 4+ buckets, this could drop the rescue for an untried replica, leaving it never attempted. The bug is masked under the default config (
bucket_timeout = 5s<page_timeout = 10s): a copy'sbucket_timeoutexpires before the latest rescue, freeing a slot so the reactive fallback (routing.best) compensates. It becomes available withbucket_timeout >= page_timeout(a valid, validated configuration exercised by several simulation scenarios), where all copies can stall to the page deadline before any slot frees. The bug was introduced inc7c9dbd(#139), which placed the eagerpop_front()inside the rescue arm while gating launches only after the pop.Fix
Defer the rescue instead of dropping it by adding
active.len() < MAX_CONCURRENT_COPIESto the rescueselect!arm's guard:When at full capacity, the guard is false so
select!skips this arm — the rescue entry stays at the front of the queue and is re-polled on the next loop iteration once a slot frees. Thenext_rescueinstant is derived fromrescues.front()(unchanged), so the timer remains accurate. The existingtried[index]dedup guard for already-launched replicas is preserved. A#[allow(clippy::too_many_lines)]was added todownload_replicas(it grew from 100 to 101 lines), consistent with existing usage elsewhere in the codebase.Testing
src/object_store/downloader.rscovering the previously-untested 4+ bucket rescue-at-capacity path:bucket_timeout == page_timeout).bucket_timeout == page_timeoutscenarios such ascold_regional_failover,regional_deadline, andfinite_capacity) completes with no regression in completion counts or service work.-D warnings), nightly rustfmt, andgit diff --checkall pass.Closes #143
Automatic Fixes PRs can be configured here.