v0.12.1: halt-aware sleeps in Drive::read recovery

Stop was waiting up to 30 s to register when the drive hit L-EC
recovery mid-read — the recovery phase does 30 s sleeps between
retries and the halt flag was only checked at the start of each sleep.
UI feels broken ("stop isnt working") even though the halt was set.

halt_aware_sleep breaks each wait into 100 ms slices and returns
early on halt. Applied to all 4 sleeps in the recovery path (both
30 s retry delays, both 5 s reset-phase delays).
This commit is contained in:
MattJackson
2026-04-24 12:59:36 -07:00
parent dd7cbd70a9
commit 481b47d90d
+29 -4
View File
@@ -128,6 +128,23 @@ impl Drive {
self.halt.load(Ordering::Relaxed) self.halt.load(Ordering::Relaxed)
} }
/// Sleep for `total`, but wake within ~100 ms if the halt flag fires.
/// Returns `true` if halted during sleep. Used by the recovery path
/// where a single 30 s sleep would otherwise swallow a Stop request
/// and make the UI feel frozen.
fn halt_aware_sleep(&self, total: std::time::Duration) -> bool {
let slice = std::time::Duration::from_millis(100);
let deadline = std::time::Instant::now() + total;
while std::time::Instant::now() < deadline {
if self.is_halted() {
return true;
}
let remaining = deadline.saturating_duration_since(std::time::Instant::now());
std::thread::sleep(remaining.min(slice));
}
self.is_halted()
}
/// Close the drive cleanly. Unlocks tray, flushes SCSI state, closes fd. /// Close the drive cleanly. Unlocks tray, flushes SCSI state, closes fd.
/// Also runs automatically on Drop as a safety net. /// Also runs automatically on Drop as a safety net.
pub fn close(self) { pub fn close(self) {
@@ -562,7 +579,9 @@ impl Drive {
return Err(Error::Halted); return Err(Error::Halted);
} }
self.emit(EventKind::Retry { attempt }); self.emit(EventKind::Retry { attempt });
std::thread::sleep(std::time::Duration::from_secs(30)); if self.halt_aware_sleep(std::time::Duration::from_secs(30)) {
return Err(Error::Halted);
}
if let Ok(result) = self.scsi.as_mut().execute( if let Ok(result) = self.scsi.as_mut().execute(
&cdb, &cdb,
@@ -582,9 +601,13 @@ impl Drive {
// Phase 2: fresh start — close, reset, open, init. // Phase 2: fresh start — close, reset, open, init.
let device = std::path::PathBuf::from(&self.device_path); let device = std::path::PathBuf::from(&self.device_path);
std::thread::sleep(std::time::Duration::from_secs(5)); if self.halt_aware_sleep(std::time::Duration::from_secs(5)) {
return Err(Error::Halted);
}
let _ = crate::scsi::reset(&device); let _ = crate::scsi::reset(&device);
std::thread::sleep(std::time::Duration::from_secs(5)); if self.halt_aware_sleep(std::time::Duration::from_secs(5)) {
return Err(Error::Halted);
}
self.scsi = crate::scsi::open(&device)?; self.scsi = crate::scsi::open(&device)?;
let _ = self.init(); let _ = self.init();
let _ = self.wait_ready(); let _ = self.wait_ready();
@@ -600,7 +623,9 @@ impl Drive {
return Err(Error::Halted); return Err(Error::Halted);
} }
self.emit(EventKind::Retry { attempt }); self.emit(EventKind::Retry { attempt });
std::thread::sleep(std::time::Duration::from_secs(30)); if self.halt_aware_sleep(std::time::Duration::from_secs(30)) {
return Err(Error::Halted);
}
if let Ok(result) = self.scsi.as_mut().execute( if let Ok(result) = self.scsi.as_mut().execute(
&cdb, &cdb,