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:
Matthew Jackson
2026-06-30 16:37:59 -07:00
parent acaae3d0a4
commit 2013ef8c44
4 changed files with 144 additions and 0 deletions
+25
View File
@@ -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,
}
}