disc: adaptive patch speed — enter each range fast, slow on first failure
Pass N (patch) pinned the slow recovery speed (0x0000) for the whole pass. But Pass 1's damage-jump overshoots, so most of a jumped range is clean data the reverse-walk reads first — grinding it at slow speed wastes minutes per gap. Now each range mirrors Pass 1's model: - Enter at max speed (0xFFFF) + reset to the initial batch (current_batch carries across ranges, so a prior single-sector grind would otherwise start the next range slow). Reads the clean overshoot fast. - First read failure in the range → drop to 0x0000 (once; idempotent SET CD SPEED) for the rest of that range — grind only the genuine damage. - 10 s halt-responsive cooldown between ranges, gated on "this range actually grinded" so a many-small-range pass (100+ ranges) doesn't stall ~20 min on unconditional pauses. No change to good/bad classification, the mapfile, or the abort math — purely read speed + an inter-range cooldown. Synthetic speed-transition test to follow.
This commit is contained in:
+43
-1
@@ -310,6 +310,10 @@ use crate::sector::SectorSource;
|
|||||||
// per-LBA NOT_READY retries so a persistently-not-ready disc cannot burn
|
// per-LBA NOT_READY retries so a persistently-not-ready disc cannot burn
|
||||||
// up to RANGE_BUDGET_CAP_SECS per range on a single LBA.
|
// up to RANGE_BUDGET_CAP_SECS per range on a single LBA.
|
||||||
const NOT_READY_MAX_RETRIES_PER_LBA: u32 = 3;
|
const NOT_READY_MAX_RETRIES_PER_LBA: u32 = 3;
|
||||||
|
/// Cooldown between patch ranges that actually grinded (dropped to the slow
|
||||||
|
/// recovery speed). Lets the drive settle before the next range re-enters at max
|
||||||
|
/// speed. Gated on "grinded" so a many-small-range pass doesn't stall on it.
|
||||||
|
const INTER_RANGE_COOLDOWN_SECS: u64 = 10;
|
||||||
const BRIDGE_DEGRADATION_PAUSE_SECS: u64 = 10;
|
const BRIDGE_DEGRADATION_PAUSE_SECS: u64 = 10;
|
||||||
const POST_FAILURE_PAUSE_SECS: u64 = 1;
|
const POST_FAILURE_PAUSE_SECS: u64 = 1;
|
||||||
const CONSECUTIVE_FAIL_LONG_PAUSE: u64 = 5;
|
const CONSECUTIVE_FAIL_LONG_PAUSE: u64 = 5;
|
||||||
@@ -1838,7 +1842,15 @@ impl Disc {
|
|||||||
);
|
);
|
||||||
let mut buf = vec![0u8; initial_batch as usize * 2048];
|
let mut buf = vec![0u8; initial_batch as usize * 2048];
|
||||||
|
|
||||||
reader.set_speed(0x0000);
|
// Adaptive patch speed (replaces the old pass-wide slow pin): each range
|
||||||
|
// now ENTERS at max speed + the initial batch and only drops to the slow
|
||||||
|
// recovery speed on its FIRST read failure (set per-range below). Pass 1's
|
||||||
|
// damage-jump overshoots, so most of a jumped range is clean data the
|
||||||
|
// reverse-walk reads first — reading it at max speed instead of grinding
|
||||||
|
// the whole range slow is the win. `cooldown_pending` arms the inter-range
|
||||||
|
// pause, but ONLY after a range that actually grinded (dropped to slow) —
|
||||||
|
// an unconditional pause would stall 20+ min on a many-small-range pass.
|
||||||
|
let mut cooldown_pending = false;
|
||||||
|
|
||||||
log_patch_start_snapshot(&initial_entries, &initial_stats, bytes_good_before);
|
log_patch_start_snapshot(&initial_entries, &initial_stats, bytes_good_before);
|
||||||
|
|
||||||
@@ -1864,6 +1876,18 @@ impl Disc {
|
|||||||
);
|
);
|
||||||
|
|
||||||
'outer: for (range_idx, (range_pos, range_size)) in bad_ranges.iter().enumerate() {
|
'outer: for (range_idx, (range_pos, range_size)) in bad_ranges.iter().enumerate() {
|
||||||
|
// Inter-range cooldown — only after a range that grinded (dropped to
|
||||||
|
// slow). Halt-responsive so a stop request interrupts the wait.
|
||||||
|
if cooldown_pending {
|
||||||
|
tracing::info!(
|
||||||
|
target: "freemkv::disc",
|
||||||
|
phase = "patch_inter_range_cooldown",
|
||||||
|
secs = INTER_RANGE_COOLDOWN_SECS,
|
||||||
|
"Cooldown before next range (previous range grinded at slow speed)"
|
||||||
|
);
|
||||||
|
super::sleep_secs_or_halt(INTER_RANGE_COOLDOWN_SECS, opts.halt.as_ref());
|
||||||
|
cooldown_pending = false;
|
||||||
|
}
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
target: "freemkv::disc",
|
target: "freemkv::disc",
|
||||||
phase = "patch_range_start",
|
phase = "patch_range_start",
|
||||||
@@ -1926,6 +1950,15 @@ impl Disc {
|
|||||||
range_budget_secs,
|
range_budget_secs,
|
||||||
"Per-range time budget computed"
|
"Per-range time budget computed"
|
||||||
);
|
);
|
||||||
|
// Enter the range at MAX speed + the full initial batch: read the
|
||||||
|
// clean overshoot fast. `current_batch` carries across ranges, so
|
||||||
|
// reset it here or a prior range's single-sector grind would start
|
||||||
|
// this range slow. `range_slowed` flips on the first read failure
|
||||||
|
// (below), dropping to the slow recovery speed for the rest of the
|
||||||
|
// range and arming the inter-range cooldown.
|
||||||
|
reader.set_speed(0xFFFF);
|
||||||
|
state.current_batch = initial_batch;
|
||||||
|
let mut range_slowed = false;
|
||||||
loop {
|
loop {
|
||||||
if let Some(ref h) = opts.halt {
|
if let Some(ref h) = opts.halt {
|
||||||
if h.load(std::sync::atomic::Ordering::Relaxed) {
|
if h.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
@@ -2043,6 +2076,15 @@ impl Disc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
// First failure in this range: the fast-batched pass over
|
||||||
|
// the clean overshoot is done; drop to the slow recovery
|
||||||
|
// speed for the rest of the range and arm the cooldown.
|
||||||
|
// Idempotent — only the first failure issues SET CD SPEED.
|
||||||
|
if !range_slowed {
|
||||||
|
reader.set_speed(0x0000);
|
||||||
|
range_slowed = true;
|
||||||
|
cooldown_pending = true;
|
||||||
|
}
|
||||||
match handle_read_failure(
|
match handle_read_failure(
|
||||||
&mut state,
|
&mut state,
|
||||||
&frame,
|
&frame,
|
||||||
|
|||||||
Reference in New Issue
Block a user