disc/patch: fast_capture mode — breadth-first recovery (#50)
A PatchOptions.fast_capture pass reads each bad range ONCE at the full batch and leaves every FAILED block NonTrimmed for a later pass — no bisect, no per-sector grind, no retry. This lets a first retry pass grab the readable blocks (the sweep's good skip-ahead overshoot) of EVERY section quickly, before any single section's slow per-sector recovery — instead of grinding section 1 to exhaustion before even touching section 2. A later pass (fast_capture = false) does the granular bisect/retry on what's left. Load-bearing invariant (fixture test): NO data is dropped. A failed block becomes NonTrimmed (pending, retried by a granular pass), NEVER Unreadable. The test pins that the readable half of a range recovers, the bad half stays NonTrimmed (not Unreadable), and the bad block is marked in ONE batch read with zero bisection. Disc::copy's internal patch keeps fast_capture=false (single-call full recovery).
This commit is contained in:
@@ -2884,6 +2884,10 @@ impl Disc {
|
||||
progress: opts.progress,
|
||||
halt: opts.halt.clone(),
|
||||
key_fetch: opts.key_fetch.clone(),
|
||||
// Disc::copy's internal patch grinds each range fully (it's a
|
||||
// single-call recovery); the breadth-first fast-capture ordering is
|
||||
// an autorip multi-pass concern.
|
||||
fast_capture: false,
|
||||
};
|
||||
let pr = self.patch(reader, path, &patch_opts)?;
|
||||
tracing::info!(
|
||||
@@ -3799,6 +3803,18 @@ pub struct PatchOptions<'a> {
|
||||
/// On-decrypt-miss key fetch (see [`CopyOptions::key_fetch`]). Lets Pass N
|
||||
/// recover an orphan CPS unit's key when re-reading its bad range.
|
||||
pub key_fetch: Option<crate::sector::KeyFetch>,
|
||||
/// Fast-capture pass: read each bad range ONCE at the full batch and leave
|
||||
/// every failed block `NonTrimmed` for a later pass — WITHOUT bisecting,
|
||||
/// re-reading, or grinding it here. This lets a first retry pass grab the
|
||||
/// readable blocks (the sweep's good skip-ahead overshoot) of EVERY range
|
||||
/// quickly, before any single range's slow per-sector recovery — so
|
||||
/// recovered data surfaces across the whole disc first instead of grinding
|
||||
/// section 1 to exhaustion before even touching section 2. A later pass
|
||||
/// (`fast_capture = false`) does the granular bisect/retry on what's left.
|
||||
/// No data is dropped: a failed block stays `NonTrimmed` until a granular
|
||||
/// pass recovers it or finally gives up. A transport fault (bridge crash)
|
||||
/// still aborts — it isn't a recoverable bad sector.
|
||||
pub fast_capture: bool,
|
||||
}
|
||||
|
||||
/// Result returned by [`Disc::patch`].
|
||||
|
||||
@@ -1972,6 +1972,30 @@ impl<R: SectorSource + ?Sized> PatchCtx<'_, '_, R> {
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
// Fast-capture pass: don't grind this block. Mark it
|
||||
// NonTrimmed for a later granular pass and move on, so the
|
||||
// readable blocks of EVERY range are captured before any
|
||||
// single range's slow per-sector recovery. A transport fault
|
||||
// (bridge crash) still falls through below — it isn't a
|
||||
// recoverable bad sector. No data is dropped: the block stays
|
||||
// NonTrimmed until a granular pass recovers or gives up on it.
|
||||
if self.opts.fast_capture && !err.is_scsi_transport_failure() {
|
||||
send_or_abort(
|
||||
self.pipe,
|
||||
PatchItem::NonTrimmed {
|
||||
pos,
|
||||
len: block_bytes,
|
||||
},
|
||||
)?;
|
||||
self.state.blocks_read_failed += 1;
|
||||
if self.opts.reverse {
|
||||
frame.block_end = frame.block_end.saturating_sub(block_bytes);
|
||||
} else {
|
||||
frame.block_end += block_bytes;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// First failure in this range: the fast-batched pass over
|
||||
// the clean overshoot is done. A genuine transport fault
|
||||
// (bridge crash) is NOT a recoverable bad sector — let
|
||||
@@ -2481,6 +2505,7 @@ mod tests {
|
||||
halt: None,
|
||||
|
||||
key_fetch: None,
|
||||
fast_capture: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user