From 86396d100b02e43cbac8db4af14f231c545fe509 Mon Sep 17 00:00:00 2001 From: Matthew Jackson Date: Fri, 24 Apr 2026 12:59:36 -0700 Subject: [PATCH] v0.12.1: halt-aware sleeps in Drive::read recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- src/drive/mod.rs | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/drive/mod.rs b/src/drive/mod.rs index 74b9c58..02edbe2 100644 --- a/src/drive/mod.rs +++ b/src/drive/mod.rs @@ -128,6 +128,23 @@ impl Drive { 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. /// Also runs automatically on Drop as a safety net. pub fn close(self) { @@ -562,7 +579,9 @@ impl Drive { return Err(Error::Halted); } 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( &cdb, @@ -582,9 +601,13 @@ impl Drive { // Phase 2: fresh start — close, reset, open, init. 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); - 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)?; let _ = self.init(); let _ = self.wait_ready(); @@ -600,7 +623,9 @@ impl Drive { return Err(Error::Halted); } 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( &cdb,