diff --git a/src-tauri/src/update_cancel.rs b/src-tauri/src/update_cancel.rs index 30b1acf3..29686dc6 100644 --- a/src-tauri/src/update_cancel.rs +++ b/src-tauri/src/update_cancel.rs @@ -92,8 +92,12 @@ pub async fn enter_or_abort<'a>( enter: impl Future>, ) -> Result, String> { if let Err(error) = enter.await { - install.finish_unstarted()?; - return Err(error); + return match install.finish_unstarted() { + Ok(()) => Err(error), + Err(lock) => Err(format!( + "{error} (releasing the update slot also failed: {lock})" + )), + }; } Ok(install) } @@ -374,6 +378,27 @@ mod tests { assert!(state.begin().is_err()); } + #[tokio::test] + async fn poisoned_slot_keeps_maintenance_entry_error() { + let state = UpdateCancel::default(); + let install = state.begin().unwrap(); + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let _guard = state.0.lock().unwrap(); + panic!("poison the update slot"); + })); + assert!(state.0.is_poisoned()); + let message = match enter_or_abort(install, ready(Err("drain failed".into()))).await { + Err(error) => error, + Ok(_) => panic!("entry failure must abort"), + }; + assert!( + message.starts_with("drain failed") + && message.contains("update cancellation state unavailable") + && message.contains("releasing the update slot also failed"), + "{message}" + ); + } + #[tokio::test] async fn drop_while_installing_keeps_slot_and_signals_fixed_error() { let state = UpdateCancel::default();