// Separate source-bound extension after the frozen Context and AsyncBank bytes. // Returning to the inherited bootstrap stack owns a transaction independently // of ordinary two-thread dispatch, and never invents a bootstrap ThreadId. /// # Safety /// ReturnPrepared retains the actual source binding/capture and exact bootstrap /// stack/root/frame authority. Preparation and abort do not change execution. /// leave_return executes the frozen Table exit on the live source stack with /// IRQs masked. BeforeSwitch means every resource/Table/CPU byte is unchanged; /// any possible change is AfterSwitch. returned observes the actual bootstrap /// branch, SP/root/guard/canaries and full GP/SIMD/FP snapshot saved before Rust /// clobbers it, bound to the retained bootstrap640 bytes and this transaction. pub unsafe trait CompletionBackend: HandoffBackend { type ReturnPrepared; fn prepare_return( &mut self, source: &Self::Binding, capture: &Capture, ) -> Result; fn leave_return( &mut self, prepared: &mut Self::ReturnPrepared, source: &Self::Binding, ) -> Result<(), Failure>; fn returned(&self, prepared: &Self::ReturnPrepared) -> Result<(), Error>; /// Inert lookup of the original retained bootstrap root authority. Source /// bindings keep their distinct private roots through retirement. fn return_root(&self, prepared: &Self::ReturnPrepared) -> Result; fn finish_return( &mut self, prepared: Self::ReturnPrepared, ) -> Result<(), (Error, Self::ReturnPrepared)>; fn abort_return( &mut self, prepared: Self::ReturnPrepared, ) -> Result<(), (Error, Self::ReturnPrepared)>; } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ReturnStage { Staged, Departed, } pub struct OwnedReturn { bank: u64, nonce: u64, } impl OwnedReturn { pub fn nonce(&self) -> u64 { self.nonce } } pub enum ReturnFailure { Before(Error, OwnedReturn), Quarantined(Error), } struct ReturnTransaction

{ nonce: u64, source: Handle, capture: Capture, stage: ReturnStage, old_epoch: u64, next_epoch: u64, prepared: P, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ReturnObservation { pub nonce: u64, pub source: Handle, pub capture: Capture, pub stage: ReturnStage, } pub struct CompletionBank { handoff: AsyncBank, sequence: u64, pending: Option>, quarantined: bool, } impl CompletionBank { #[allow(clippy::result_large_err)] pub fn new(handoff: AsyncBank) -> Result)> { if handoff.pending.is_some() || handoff.quarantined || handoff.bank.faulted { return Err((Error::Pending, handoff)); } Ok(Self { handoff, sequence: 0, pending: None, quarantined: false, }) } pub fn handoff(&self) -> &AsyncBank { &self.handoff } pub fn quiescent(&mut self) -> Result<&mut AsyncBank, Error> { if self.pending.is_some() || self.quarantined { return Err(Error::Pending); } Ok(&mut self.handoff) } pub fn observation(&self) -> Option { self.pending.as_ref().map(|p| ReturnObservation { nonce: p.nonce, source: p.source, capture: p.capture, stage: p.stage, }) } pub fn with_return_observation(&self, f: impl FnOnce(Option<&B::ReturnPrepared>) -> R) -> R { f(self.pending.as_ref().map(|p| &p.prepared)) } pub fn quarantined(&self) -> bool { self.quarantined } pub fn prepare_return( &mut self, source: Handle, capture: Capture, ) -> Result { if self.pending.is_some() || self.quarantined || self.handoff.pending.is_some() || self.handoff.quarantined { return Err(Error::Pending); } let nonce = self.sequence.checked_add(1).ok_or(Error::Exhausted)?; let bank = &mut self.handoff.bank; let (i, r, e) = bank.current(source, false)?; if e.critical_depth != 0 || !e.safe_point || bank.deferred.is_some() { return Err(Error::Context); } bank.capture(i, r, capture, false, e)?; let next_epoch = bank.epoch.checked_add(1).ok_or(Error::Exhausted)?; let prepared = bank.backend.prepare_return( bank.slots[i].binding.as_ref().ok_or(Error::State)?, &capture, )?; self.pending = Some(ReturnTransaction { nonce, source, capture, stage: ReturnStage::Staged, old_epoch: bank.epoch, next_epoch, prepared, }); bank.uncertain_capture = Some(capture); bank.faulted = true; self.sequence = nonce; Ok(OwnedReturn { bank: bank.identity, nonce, }) } fn validate(&self, token: &OwnedReturn) -> Result<(), Error> { let p = self.pending.as_ref().ok_or(Error::Pending)?; let bank = &self.handoff.bank; if self.quarantined || !bank.faulted { return Err(Error::Quarantined); } if token.bank != bank.identity || token.nonce != p.nonce || bank.epoch != p.old_epoch || bank.active != Some(p.source) || bank.slots[p.source.slot].generation != p.source.generation || bank.uncertain_capture != Some(p.capture) { return Err(Error::Stale); } Ok(()) } fn before(&mut self) -> Result<(), Error> { let p = self.pending.as_ref().ok_or(Error::Pending)?; let bank = &mut self.handoff.bank; bank.faulted = false; let result = (|| { let (i, r, e) = bank.current(p.source, false)?; if e.critical_depth != 0 || !e.safe_point || bank.deferred.is_some() { return Err(Error::Context); } bank.capture(i, r, p.capture, false, e) })(); bank.faulted = true; result } fn quarantine(&mut self) { let p = self.pending.as_ref().unwrap(); self.handoff.bank.slots[p.source.slot].state = State::Quarantined; self.handoff.bank.faulted = true; self.quarantined = true; } pub fn depart_return(&mut self, token: OwnedReturn) -> Result { if let Err(e) = self.validate(&token) { return Err(ReturnFailure::Before(e, token)); } if self.pending.as_ref().unwrap().stage != ReturnStage::Staged { return Err(ReturnFailure::Before(Error::State, token)); } if let Err(e) = self.before() { return Err(ReturnFailure::Before(e, token)); } let p = self.pending.as_mut().unwrap(); let bank = &mut self.handoff.bank; let result = bank.backend.leave_return( &mut p.prepared, bank.slots[p.source.slot].binding.as_ref().unwrap(), ); if let Err(f) = result { return match f.phase { FailurePhase::BeforeSwitch => Err(ReturnFailure::Before(f.error, token)), FailurePhase::AfterSwitch => { self.quarantine(); Err(ReturnFailure::Quarantined(f.error)) } }; } self.pending.as_mut().unwrap().stage = ReturnStage::Departed; Ok(token) } pub fn return_landed(&mut self, token: OwnedReturn) -> Result<(), ReturnFailure> { if let Err(e) = self.validate(&token) { return Err(ReturnFailure::Before(e, token)); } let p = self.pending.as_ref().unwrap(); if p.stage != ReturnStage::Departed { return Err(ReturnFailure::Before(Error::State, token)); } let bank = &mut self.handoff.bank; let checked = (|| { bank.backend.returned(&p.prepared)?; bank.faulted = false; let e = bank.execution(false); bank.faulted = true; let e = e?; let r = bank.validate(p.source.slot)?; if r.state != thread::State::Dead || e.thread.is_some() || e.root != bank.backend.return_root(&p.prepared)? || e.sp >= r.layout.guard() && e.sp <= r.layout.base + r.layout.bytes { return Err(Error::Active); } Ok(()) })(); if let Err(e) = checked { self.quarantine(); return Err(ReturnFailure::Quarantined(e)); } let p = self.pending.take().unwrap(); if let Err((e, prepared)) = self.handoff.bank.backend.finish_return(p.prepared) { self.pending = Some(ReturnTransaction { prepared, ..p }); self.quarantine(); return Err(ReturnFailure::Quarantined(e)); } let bank = &mut self.handoff.bank; bank.slots[p.source.slot].image = p.capture.image; bank.slots[p.source.slot].last_capture = p.capture.episode; bank.slots[p.source.slot].state = State::Ready; bank.active = None; bank.epoch = p.next_epoch; bank.uncertain_capture = None; bank.faulted = false; Ok(()) } pub fn abort_return(&mut self, token: OwnedReturn) -> Result<(), ReturnFailure> { if let Err(e) = self.validate(&token) { return Err(ReturnFailure::Before(e, token)); } if self.pending.as_ref().unwrap().stage != ReturnStage::Staged { return Err(ReturnFailure::Before(Error::State, token)); } let p = self.pending.take().unwrap(); if let Err((e, prepared)) = self.handoff.bank.backend.abort_return(p.prepared) { self.pending = Some(ReturnTransaction { prepared, ..p }); self.quarantine(); return Err(ReturnFailure::Quarantined(e)); } self.handoff.bank.uncertain_capture = None; self.handoff.bank.faulted = false; Ok(()) } #[allow(clippy::result_large_err)] pub fn into_handoff(self) -> Result, (Error, Self)> { if self.pending.is_some() || self.quarantined { return Err((Error::Pending, self)); } Ok(self.handoff) } } impl AsyncBank { #[allow(clippy::result_large_err)] pub fn os_k1040_into_backend(self) -> Result { if self.pending.is_some() || self.quarantined { return Err((Error::Pending, self)); } match self.bank.into_backend() { Ok(backend) => Ok(backend), Err((error, bank)) => Err(( error, Self { bank, sequence: self.sequence, pending: self.pending, quarantined: self.quarantined, }, )), } } } #[cfg(test)] mod completion_tests { use super::*; use crate::test_support as saved; const SOURCE_ROOT: u64 = 0x43000000; const BOOTSTRAP_ROOT: u64 = 0x44000000; struct Device { inner: saved::Backend, root: core::cell::Cell, } impl core::ops::Deref for Device { type Target = saved::Backend; fn deref(&self) -> &Self::Target { &self.inner } } impl core::ops::DerefMut for Device { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } unsafe impl Backend for Device { type Binding = saved::Binding; type Prepared = saved::Prepared; type IdlePrepared = saved::IdlePrepared; fn execution(&self) -> Execution { Execution { root: self.root.get(), ..self.inner.execution() } } fn record(&self, b: &Self::Binding) -> Result { self.inner.record(b) } fn stack(&self, b: &Self::Binding) -> Result { self.inner.stack(b) } fn root(&self, _: &Self::Binding) -> Result { Ok(SOURCE_ROOT) } fn executable(&self, pc: u64) -> bool { self.inner.executable(pc) } fn initial_return_pc(&self, b: &Self::Binding) -> Result { self.inner.initial_return_pc(b) } fn capture_matches(&self, b: &Self::Binding, c: &Capture) -> bool { self.inner.capture_matches(b, c) } fn registers(&self) -> Registers { self.inner.registers() } fn prepare( &mut self, s: Option<&Self::Binding>, t: &Self::Binding, i: &Image, d: Disposition, ) -> Result { self.inner.prepare(s, t, i, d) } fn commit( &mut self, p: &mut Self::Prepared, s: Option<&Self::Binding>, t: &Self::Binding, i: &Image, d: Disposition, ) -> Result<(), Failure> { self.inner.commit(p, s, t, i, d) } fn finish(&mut self, p: Self::Prepared) -> Result<(), (Error, Self::Prepared)> { self.inner.finish(p) } fn abort(&mut self, p: Self::Prepared) -> Result<(), (Error, Self::Prepared)> { self.inner.abort(p) } fn release(&mut self, p: Self::Binding) -> Result<(), (Error, Self::Binding)> { self.inner.release(p) } fn ready_empty(&self) -> bool { self.inner.ready_empty() } fn wake_epoch(&self) -> u64 { self.inner.wake_epoch() } fn idle_prepare(&mut self, e: u64) -> Result { self.inner.idle_prepare(e) } fn idle_commit( &mut self, p: &mut Self::IdlePrepared, e: u64, ) -> Result { self.inner.idle_commit(p, e) } fn idle_finish( &mut self, p: Self::IdlePrepared, ) -> Result<(), (Error, Self::IdlePrepared)> { self.inner.idle_finish(p) } fn idle_abort(&mut self, p: Self::IdlePrepared) -> Result<(), (Error, Self::IdlePrepared)> { self.inner.idle_abort(p) } } unsafe impl HandoffBackend for Device { fn depart( &mut self, p: &mut Self::Prepared, s: Option<&Self::Binding>, n: u64, d: Disposition, ) -> Result<(), Failure> { self.inner.depart(p, s, n, d) } fn arrive( &mut self, p: &mut Self::Prepared, t: &Self::Binding, i: &Image, n: u64, ) -> Result<(), Error> { self.inner.arrive(p, t, i, n) } } pub(crate) struct ReturnPrepared { nonce: u64, source: thread::ThreadId, expected: Registers, bootstrap_root: u64, } fn bootstrap() -> Registers { Registers { sp: 0x90004000, x19_x30: core::array::from_fn(|i| 0x1040 + i as u64), q: core::array::from_fn(|i| [0x104000 + i as u64, 0x204000 + i as u64]), fpcr: 0x400000, fpsr: 0x8000000, pc: saved::ENTRY + 0x900, argument: 0, timer: None, } } unsafe impl CompletionBackend for Device { type ReturnPrepared = ReturnPrepared; fn prepare_return( &mut self, source: &Self::Binding, _: &Capture, ) -> Result { let mut h = self.hardware.borrow_mut(); if h.prepare_fail { return Err(Error::Backend); } h.prepares += 1; Ok(ReturnPrepared { nonce: h.prepares as u64, source: source.id, expected: bootstrap(), bootstrap_root: BOOTSTRAP_ROOT, }) } fn leave_return( &mut self, p: &mut ReturnPrepared, source: &Self::Binding, ) -> Result<(), Failure> { let fail = self.hardware.borrow().commit_fail; if fail == Some(FailurePhase::BeforeSwitch) { return Err(Failure { error: Error::Backend, phase: FailurePhase::BeforeSwitch, }); } assert_eq!(p.source, source.id); if self.hardware.borrow().bad_registers != 9 { self.table.borrow_mut().exit_current(source.id).unwrap(); } self.hardware.borrow_mut().current = None; if fail == Some(FailurePhase::AfterSwitch) { return Err(Failure { error: Error::Backend, phase: FailurePhase::AfterSwitch, }); } Ok(()) } fn returned(&self, p: &ReturnPrepared) -> Result<(), Error> { let h = self.hardware.borrow(); if h.current.is_some() || h.sp >= p.expected.sp || h.sp < p.expected.sp - 1024 || self.registers != p.expected { return Err(Error::Capture); } Ok(()) } fn return_root(&self, p: &ReturnPrepared) -> Result { Ok(p.bootstrap_root) } fn finish_return(&mut self, p: ReturnPrepared) -> Result<(), (Error, ReturnPrepared)> { let mut h = self.hardware.borrow_mut(); if h.finish_fail { return Err((Error::Backend, p)); } h.finishes += 1; Ok(()) } fn abort_return(&mut self, p: ReturnPrepared) -> Result<(), (Error, ReturnPrepared)> { let mut h = self.hardware.borrow_mut(); if h.abort_fail { return Err((Error::Backend, p)); } h.aborts += 1; Ok(()) } } type TestBank = CompletionBank; type Hardware = std::rc::Rc>; fn setup() -> (TestBank, [Handle; 2], Hardware, Capture) { let (mut bank, handles, h) = saved::setup(); saved::finish(bank.start(handles[0]).unwrap()); let c = saved::capture(&bank, handles[0], &h, 43, false); // Move every original private token into a model with distinct closed // source/bootstrap roots; no resource is reconstructed or released. let mut slots = bank.slots; for slot in &mut slots { if slot.binding.is_some() { slot.root = SOURCE_ROOT; } } let bank = ContextBank { backend: Device { inner: bank.backend, root: core::cell::Cell::new(SOURCE_ROOT), }, identity: bank.identity, cpu: bank.cpu, slots, active: bank.active, epoch: bank.epoch, deferred: bank.deferred, last_deferred: bank.last_deferred, faulted: bank.faulted, retained_switch: bank.retained_switch, retained_idle: bank.retained_idle, uncertain_capture: bank.uncertain_capture, _local: bank._local, }; ( CompletionBank::new(AsyncBank::new(bank).ok().unwrap()) .ok() .unwrap(), handles, h, c, ) } // Explicit host hardware model. This stores the modeled CPU observation // before the bootstrap callback; it is never an assembly execution claim. fn physical_step(bank: &mut TestBank) { let d = &mut bank.handoff.bank.backend; d.root.set(BOOTSTRAP_ROOT); let mut h = d.inner.hardware.borrow_mut(); h.sp = bootstrap().sp - 64; h.current = None; d.inner.registers = bootstrap(); } fn land(bank: &mut TestBank, token: OwnedReturn) { let token = bank.depart_return(token).ok().unwrap(); physical_step(bank); assert!(bank.return_landed(token).is_ok()); } #[test] fn actual_modeled_bootstrap_landing_precedes_publication_and_pin_release() { let (mut bank, [a, b], h, c) = setup(); let token = bank.prepare_return(a, c).unwrap(); assert!(bank.quiescent().is_err()); assert_eq!(bank.handoff.bank.record(a).unwrap().last_capture, 0); let token = bank.depart_return(token).ok().unwrap(); assert_eq!(bank.handoff.bank.record(a).unwrap().state, State::Active); assert_eq!(bank.handoff.bank.record(a).unwrap().last_capture, 0); assert_eq!(h.borrow().pins, 2); physical_step(&mut bank); assert!(bank.return_landed(token).is_ok()); assert_eq!(bank.handoff.bank.snapshot().active, 0); assert_eq!(bank.handoff.bank.record(a).unwrap().last_capture, c.episode); let q = bank.quiescent().unwrap().quiescent().unwrap(); let aid = q.record(a).unwrap().thread; q.retire(a).unwrap(); q.backend.table.borrow_mut().reap(aid).unwrap(); // The real frozen Table owns stack bytes independently of Context pins. let bid = q.record(b).unwrap().thread; q.backend.table.borrow_mut().cancel(bid).unwrap(); q.retire(b).unwrap(); q.backend.table.borrow_mut().reap(bid).unwrap(); assert_eq!(h.borrow().pins, 0); assert_eq!(h.borrow().stack_pages, 0); let handoff = bank.into_handoff().ok().unwrap(); assert!(handoff.os_k1040_into_backend().is_ok()); } #[test] fn before_exit_returns_same_owned_return_and_prepared_without_state_change() { let (mut bank, [a, _], h, c) = setup(); let token = bank.prepare_return(a, c).unwrap(); let nonce = token.nonce(); let p = bank.with_return_observation(|p| p.unwrap().nonce); h.borrow_mut().commit_fail = Some(FailurePhase::BeforeSwitch); let token = match bank.depart_return(token) { Err(ReturnFailure::Before(Error::Backend, t)) => t, _ => panic!(), }; assert_eq!(token.nonce(), nonce); assert_eq!(bank.with_return_observation(|p| p.unwrap().nonce), p); assert_eq!(bank.handoff.bank.record(a).unwrap().state, State::Active); assert_eq!( h.borrow().current, Some(bank.handoff.bank.record(a).unwrap().thread) ); h.borrow_mut().commit_fail = None; land(&mut bank, token); } #[test] fn after_exit_retains_prepared_full_source_capture_and_typed_bindings() { let (mut bank, [a, _], h, c) = setup(); let token = bank.prepare_return(a, c).unwrap(); let p = bank.with_return_observation(|p| p.unwrap().nonce); h.borrow_mut().commit_fail = Some(FailurePhase::AfterSwitch); assert!(matches!( bank.depart_return(token), Err(ReturnFailure::Quarantined(Error::Backend)) )); assert!(bank.quarantined()); assert!(bank.quiescent().is_err()); assert_eq!(bank.with_return_observation(|x| x.unwrap().nonce), p); assert_eq!(bank.handoff.bank.quarantined_capture(), Some(c)); assert_eq!(bank.handoff.bank.record(a).unwrap().last_capture, 0); assert_eq!(h.borrow().pins, 2); assert_eq!(h.borrow().stack_pages, 8); assert!(h.borrow().current.is_none()); } #[test] fn wrong_actual_bootstrap_pc_q31_fpcr_sp_or_current_identity_quarantines() { for bad in 0..5 { let (mut bank, [a, _], h, c) = setup(); let token = bank.prepare_return(a, c).unwrap(); let token = bank.depart_return(token).ok().unwrap(); physical_step(&mut bank); match bad { 0 => bank.handoff.bank.backend.registers.pc += 4, 1 => bank.handoff.bank.backend.registers.q[31][1] ^= 1, 2 => bank.handoff.bank.backend.registers.fpcr ^= 1, 3 => h.borrow_mut().sp = c.address, _ => h.borrow_mut().current = Some(bank.handoff.bank.record(a).unwrap().thread), } assert!(matches!( bank.return_landed(token), Err(ReturnFailure::Quarantined(_)) )); assert!(bank.quarantined()); assert_eq!(h.borrow().pins, 2); assert_eq!(bank.handoff.bank.record(a).unwrap().last_capture, 0); assert_eq!(bank.observation().unwrap().capture, c); } } #[test] fn distinct_bootstrap_root_is_required_while_source_private_root_stays_owned() { for wrong_root in [SOURCE_ROOT, BOOTSTRAP_ROOT + 4096] { let (mut bank, [a, _], h, c) = setup(); assert_eq!(bank.handoff.bank.record(a).unwrap().root, SOURCE_ROOT); let token = bank.prepare_return(a, c).unwrap(); assert_eq!( bank.with_return_observation(|p| p.unwrap().bootstrap_root), BOOTSTRAP_ROOT ); let token = bank.depart_return(token).ok().unwrap(); physical_step(&mut bank); bank.handoff.bank.backend.root.set(wrong_root); assert!(matches!( bank.return_landed(token), Err(ReturnFailure::Quarantined(Error::Active)) )); assert!(bank.quarantined()); assert_eq!(bank.handoff.bank.record(a).unwrap().root, SOURCE_ROOT); assert_eq!(bank.handoff.bank.record(a).unwrap().last_capture, 0); assert_eq!(h.borrow().pins, 2); assert_eq!(bank.observation().unwrap().capture, c); } } #[test] fn bootstrap_landing_without_actual_frozen_table_exit_refuses_publication() { let (mut bank, [a, _], h, c) = setup(); let token = bank.prepare_return(a, c).unwrap(); h.borrow_mut().bad_registers = 9; let token = bank.depart_return(token).ok().unwrap(); physical_step(&mut bank); assert!(matches!( bank.return_landed(token), Err(ReturnFailure::Quarantined(Error::Active)) )); assert!(bank.quarantined()); assert_eq!(bank.handoff.bank.record(a).unwrap().last_capture, 0); assert_eq!( bank.handoff .bank .backend .table .borrow() .record(bank.handoff.bank.record(a).unwrap().thread) .unwrap() .state, thread::State::Running ); assert_eq!(h.borrow().pins, 2); assert_eq!(bank.observation().unwrap().capture, c); } #[test] fn finish_and_abort_failure_keep_the_exact_return_prepared() { for finish in [false, true] { let (mut bank, [a, _], h, c) = setup(); let token = bank.prepare_return(a, c).unwrap(); let p = bank.with_return_observation(|p| p.unwrap().nonce); let result = if finish { let token = bank.depart_return(token).ok().unwrap(); physical_step(&mut bank); h.borrow_mut().finish_fail = true; bank.return_landed(token) } else { h.borrow_mut().abort_fail = true; bank.abort_return(token) }; assert!(matches!( result, Err(ReturnFailure::Quarantined(Error::Backend)) )); assert_eq!(bank.with_return_observation(|x| x.unwrap().nonce), p); assert_eq!(bank.handoff.bank.record(a).unwrap().last_capture, 0); assert_eq!(bank.observation().unwrap().capture, c); assert_eq!(h.borrow().pins, 2); } } #[test] fn stale_capture_before_exit_keeps_prepared_and_same_token() { let (mut bank, [a, _], h, c) = setup(); let token = bank.prepare_return(a, c).unwrap(); let nonce = token.nonce(); h.borrow_mut().capture = None; let token = match bank.depart_return(token) { Err(ReturnFailure::Before(Error::Capture, t)) => t, _ => panic!(), }; assert_eq!(token.nonce(), nonce); assert!(h.borrow().current.is_some()); h.borrow_mut().capture = Some(c); land(&mut bank, token); } #[test] fn return_sequence_and_epoch_exhaustion_preserve_source_and_inventory() { for epoch in [false, true] { let (mut bank, [a, _], h, c) = setup(); if epoch { bank.handoff.bank.epoch = u64::MAX; } else { bank.sequence = u64::MAX; } let before = bank.handoff.bank.snapshot(); let count = h.borrow().prepares; assert!(matches!(bank.prepare_return(a, c), Err(Error::Exhausted))); assert_eq!(bank.handoff.bank.snapshot(), before); assert_eq!(h.borrow().prepares, count); assert_eq!(h.borrow().pins, 2); assert!(bank.pending.is_none()); assert!(bank.quiescent().is_ok()); } } #[test] #[allow(clippy::drop_non_drop)] fn wrong_stage_or_forgotten_return_retains_all_ownership() { let (mut bank, [a, _], h, c) = setup(); let token = bank.prepare_return(a, c).unwrap(); let token = match bank.return_landed(token) { Err(ReturnFailure::Before(Error::State, t)) => t, _ => panic!(), }; let token = bank.depart_return(token).ok().unwrap(); let token = match bank.abort_return(token) { Err(ReturnFailure::Before(Error::State, t)) => t, _ => panic!(), }; drop(token); assert_eq!(bank.observation().unwrap().stage, ReturnStage::Departed); assert!(bank.quiescent().is_err()); assert_eq!(h.borrow().pins, 2); assert_eq!(bank.observation().unwrap().capture, c); } }