// NEW source-bound extension after the frozen Context/Async/Completion bytes. // The private binding remains in ContextBank while its actual inactive frozen // Table record is cancelled. No mutable backend or owner reference escapes. /// # Safety /// cancel_inactive may cancel only this exact private binding's live inactive /// Ready/Sleeping Table record, with IRQs masked. BeforeSwitch guarantees that /// every actual resource, Table record and CPU observation is unchanged. Any /// possible change or failed observation is AfterSwitch; all owners remain /// retained in the backend. It never releases the binding or its pages. pub unsafe trait InactiveBackend: Backend { fn cancel_inactive(&mut self, binding: &Self::Binding) -> Result<(), Failure>; } impl ContextBank { pub fn os_k1040_cancel_inactive(&mut self, handle: Handle) -> Result<(), Error> { let execution = self.execution(false)?; let index = self.index(handle)?; if execution.critical_depth != 0 || !execution.safe_point || self.active.is_some() || self.deferred.is_some() || self.retained_switch.is_some() || self.retained_idle.is_some() || self.slots[index].state != State::Ready { return Err(Error::Active); } let record = self.validate(index)?; if !matches!(record.state, thread::State::Ready | thread::State::Sleeping) || execution.thread == Some(record.id) || execution.sp >= record.layout.guard() && execution.sp <= record.layout.base + record.layout.bytes { return Err(Error::Active); } let result = self .backend .cancel_inactive(self.slots[index].binding.as_ref().ok_or(Error::State)?); match result { Err(Failure { error, phase: FailurePhase::BeforeSwitch, }) => return Err(error), Err(Failure { error, phase: FailurePhase::AfterSwitch, }) => { self.slots[index].state = State::Quarantined; self.faulted = true; return Err(error); } Ok(()) => {} } let after_execution = self.execution(false); if !after_execution.is_ok_and(|actual| { actual.root == execution.root && actual.thread == execution.thread && actual.daif == execution.daif && actual.critical_depth == 0 && actual.safe_point && !(actual.sp >= record.layout.guard() && actual.sp <= record.layout.base + record.layout.bytes) }) || !self.validate(index).is_ok_and(|actual| { actual.id == record.id && actual.layout == record.layout && actual.state == thread::State::Dead }) { self.slots[index].state = State::Quarantined; self.faulted = true; return Err(Error::Backend); } Ok(()) } } #[cfg(test)] mod retirement_tests { use super::*; use crate::test_support as saved; unsafe impl InactiveBackend for saved::Backend { fn cancel_inactive(&mut self, binding: &Self::Binding) -> Result<(), Failure> { let before = |error| Failure { error, phase: FailurePhase::BeforeSwitch, }; let fault = self.hardware.borrow().commit_fail; if fault == Some(FailurePhase::BeforeSwitch) { return Err(before(Error::Backend)); } if self.hardware.borrow().bad_registers == 99 { return Ok(()); // Deliberately broken backend observation. } self.table .borrow_mut() .cancel(binding.id) .map_err(|_| before(Error::State))?; if self .sleep_wait .as_ref() .is_some_and(|wait| wait.thread() == binding.id) { self.sleep_wait = None; } let bad = self.hardware.borrow().bad_registers; match bad { 100 => self.hardware.borrow_mut().cpu = 1, 101 => self.hardware.borrow_mut().daif &= !128, 102 => self.hardware.borrow_mut().current = Some(binding.id), 103 => { let layout = self.table.borrow().record(binding.id).unwrap().layout; self.hardware.borrow_mut().sp = layout.usable_bottom() + 16; } _ => {} } if fault == Some(FailurePhase::AfterSwitch) { return Err(Failure { error: Error::Backend, phase: FailurePhase::AfterSwitch, }); } Ok(()) } } #[test] fn inactive_cancel_retains_actual_stack_until_real_reap() { let (mut bank, handles, hardware) = saved::setup(); let id = bank.record(handles[1]).unwrap().thread; assert_eq!(bank.os_k1040_cancel_inactive(handles[1]), Ok(())); bank.with_backend_observation(|b| { assert_eq!( b.table.borrow().record(id).unwrap().state, thread::State::Dead ) }); assert_eq!(hardware.borrow().pins, 2); assert_eq!(hardware.borrow().stack_pages, 8); assert_eq!(bank.retire(handles[1]), Ok(())); assert_eq!(hardware.borrow().pins, 1); assert_eq!(hardware.borrow().stack_pages, 8); bank.with_backend_observation(|backend| backend.table.borrow_mut().reap(id).unwrap()); assert_eq!(hardware.borrow().stack_pages, 4); } #[test] fn before_failure_keeps_exact_metadata_and_binding_for_retry() { let (mut bank, handles, hardware) = saved::setup(); let original = bank.snapshot(); let record = bank.record(handles[1]).unwrap(); hardware.borrow_mut().commit_fail = Some(FailurePhase::BeforeSwitch); assert_eq!( bank.os_k1040_cancel_inactive(handles[1]), Err(Error::Backend) ); assert_eq!(bank.snapshot(), original); assert_eq!(bank.record(handles[1]), Ok(record)); assert_eq!(hardware.borrow().pins, 2); hardware.borrow_mut().commit_fail = None; assert_eq!(bank.os_k1040_cancel_inactive(handles[1]), Ok(())); } #[test] fn changed_table_failure_retains_and_quarantines_actual_owner() { let (mut bank, handles, hardware) = saved::setup(); let id = bank.record(handles[1]).unwrap().thread; hardware.borrow_mut().commit_fail = Some(FailurePhase::AfterSwitch); assert_eq!( bank.os_k1040_cancel_inactive(handles[1]), Err(Error::Backend) ); assert!(bank.snapshot().faulted); assert_eq!(bank.snapshot().quarantined, 1); assert_eq!(bank.snapshot().retained_bindings, 2); bank.with_backend_observation(|b| { assert_eq!( b.table.borrow().record(id).unwrap().state, thread::State::Dead ) }); assert_eq!(bank.retire(handles[1]), Err(Error::Quarantined)); assert_eq!(hardware.borrow().pins, 2); assert_eq!(hardware.borrow().stack_pages, 8); } #[test] fn false_success_without_actual_dead_table_quarantines() { let (mut bank, handles, hardware) = saved::setup(); hardware.borrow_mut().bad_registers = 99; assert_eq!( bank.os_k1040_cancel_inactive(handles[1]), Err(Error::Backend) ); assert!(bank.snapshot().faulted); assert_eq!(bank.snapshot().retained_bindings, 2); assert_eq!(hardware.borrow().pins, 2); } #[test] fn live_active_continuation_cannot_be_cancelled() { let (mut bank, handles, hardware) = saved::setup(); saved::finish(bank.start(handles[0]).unwrap()); let before = bank.snapshot(); assert_eq!( bank.os_k1040_cancel_inactive(handles[0]), Err(Error::Active) ); assert_eq!( bank.os_k1040_cancel_inactive(handles[1]), Err(Error::Active) ); assert_eq!(bank.snapshot(), before); assert_eq!(hardware.borrow().pins, 2); } #[test] fn corrupt_physical_guard_refuses_before_table_change() { let (mut bank, handles, hardware) = saved::setup(); let id = bank.record(handles[1]).unwrap().thread; let before = bank.snapshot(); hardware.borrow_mut().corrupt_guard = true; assert_eq!(bank.os_k1040_cancel_inactive(handles[1]), Err(Error::Guard)); assert_eq!(bank.snapshot(), before); bank.with_backend_observation(|b| { assert_eq!( b.table.borrow().record(id).unwrap().state, thread::State::Ready ) }); assert_eq!(hardware.borrow().pins, 2); } #[test] fn sleeping_cancel_revokes_real_wait_without_manufactured_wake() { let (mut bank, [a, b], hardware) = saved::setup_idle(); let a_id = bank.record(a).unwrap().thread; let b_id = bank.record(b).unwrap().thread; bank.backend.table.borrow_mut().exit_current(a_id).unwrap(); bank.active = None; bank.slots[a.slot].state = State::Ready; { let mut h = hardware.borrow_mut(); h.current = None; h.sp = 0x90001000; } assert!( bank.backend .sleep_wait .as_ref() .is_some_and(|wait| wait.thread() == b_id) ); assert_eq!(bank.os_k1040_cancel_inactive(b), Ok(())); assert!(bank.backend.sleep_wait.is_none()); assert_eq!( bank.backend.table.borrow().record(b_id).unwrap().state, thread::State::Dead ); assert_eq!(hardware.borrow().wake_epoch, 1); assert_eq!(hardware.borrow().pins, 2); assert_eq!(hardware.borrow().stack_pages, 8); } #[test] fn stale_and_foreign_handles_preserve_actual_table_inventory() { let (mut bank, handles, hardware) = saved::setup(); let (other, foreign, _) = saved::setup(); let before = bank.snapshot(); assert_eq!(bank.os_k1040_cancel_inactive(foreign[1]), Err(Error::Stale)); assert_eq!(bank.snapshot(), before); bank.slots[handles[1].slot].generation += 1; assert_eq!(bank.os_k1040_cancel_inactive(handles[1]), Err(Error::Stale)); assert_eq!(hardware.borrow().pins, 2); assert_eq!(bank.backend.table.borrow().snapshot().ready, 2); assert_eq!(other.snapshot().retained_bindings, 2); } #[test] fn cpu_irq_and_mask_refusals_do_not_cancel_or_release() { for field in 0..3 { let (mut bank, handles, hardware) = saved::setup(); let before = bank.snapshot(); match field { 0 => hardware.borrow_mut().cpu = 1, 1 => hardware.borrow_mut().in_irq = true, _ => hardware.borrow_mut().daif &= !128, } assert_eq!( bank.os_k1040_cancel_inactive(handles[1]), Err(Error::Context) ); assert_eq!(bank.snapshot(), before); assert_eq!(hardware.borrow().pins, 2); assert_eq!(bank.backend.table.borrow().snapshot().ready, 2); } } #[test] fn canary_and_live_source_sp_refusals_preserve_table_and_pin() { for field in 0..2 { let (mut bank, handles, hardware) = saved::setup(); let before = bank.snapshot(); let expected = if field == 0 { hardware.borrow_mut().corrupt_canary = true; Error::Canary } else { let id = bank.record(handles[1]).unwrap().thread; let record = bank.backend.table.borrow().record(id).unwrap(); hardware.borrow_mut().sp = record.layout.usable_bottom() + 16; Error::Active }; assert_eq!(bank.os_k1040_cancel_inactive(handles[1]), Err(expected)); assert_eq!(bank.snapshot(), before); assert_eq!(hardware.borrow().pins, 2); assert_eq!(bank.backend.table.borrow().snapshot().ready, 2); } } #[test] fn actual_cancel_then_changed_cpu_mask_current_or_sp_quarantines() { for bad in 100..=103 { let (mut bank, handles, hardware) = saved::setup(); let id = bank.record(handles[1]).unwrap().thread; hardware.borrow_mut().bad_registers = bad; assert_eq!( bank.os_k1040_cancel_inactive(handles[1]), Err(Error::Backend) ); assert!(bank.snapshot().faulted); assert_eq!(bank.snapshot().quarantined, 1); assert_eq!(bank.snapshot().retained_bindings, 2); assert_eq!( bank.backend.table.borrow().record(id).unwrap().state, thread::State::Dead ); assert_eq!(hardware.borrow().pins, 2); assert_eq!(hardware.borrow().stack_pages, 8); } } struct RootFlip { inner: saved::Backend, root: u64, } unsafe impl Backend for RootFlip { type Binding = saved::Binding; type Prepared = saved::Prepared; type IdlePrepared = saved::IdlePrepared; fn execution(&self) -> Execution { Execution { root: self.root, ..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, b: &Self::Binding) -> Result { self.inner.root(b) } fn executable(&self, p: u64) -> bool { self.inner.executable(p) } 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, b: Self::Binding) -> Result<(), (Error, Self::Binding)> { self.inner.release(b) } 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 InactiveBackend for RootFlip { fn cancel_inactive(&mut self, b: &Self::Binding) -> Result<(), Failure> { self.inner.cancel_inactive(b)?; self.root ^= 4096; Ok(()) } } #[test] fn actual_cancel_then_changed_hardware_root_quarantines() { let (inner, mut pins, hardware) = saved::backend(2); let mut bank = ContextBank::<_, 4>::new(RootFlip { inner, root: saved::ROOT, }) .ok() .unwrap(); bank.attach(pins.remove(0)).ok().unwrap(); let b = bank.attach(pins.remove(0)).ok().unwrap(); let id = bank.record(b).unwrap().thread; assert_eq!(bank.os_k1040_cancel_inactive(b), Err(Error::Backend)); assert!(bank.snapshot().faulted); assert_eq!(bank.snapshot().quarantined, 1); assert_eq!(bank.snapshot().retained_bindings, 2); assert_eq!( bank.backend.inner.table.borrow().record(id).unwrap().state, thread::State::Dead ); assert_eq!(bank.backend.execution().root, saved::ROOT ^ 4096); assert_eq!(hardware.borrow().pins, 2); assert_eq!(hardware.borrow().stack_pages, 8); } }