core: rewrite panic sites as explicit match+panic

Replace implicit unwrap()/expect() in the lock-ordered core with explicit
match arms. Lock-poison sites use one uniform message
("smarm: <lock> lock poisoned (core corrupt): {e}"); invariant sites panic
with a descriptive message naming the violated invariant. No behaviour
change: each rewrite preserves the prior panic-on-bad-arm semantics. Also
clears the accompanying clippy hygiene in these files (redundant_closure,
len_without_is_empty, too_many_arguments, unnecessary_sort_by,
missing_safety_doc, nonminimal_bool/unnecessary_unwrap).
This commit is contained in:
smarm-agent
2026-06-20 17:47:33 +00:00
parent 531571bfa5
commit a875fa8285
11 changed files with 387 additions and 114 deletions
+4 -2
View File
@@ -93,8 +93,10 @@ pub fn take_last_outcome() -> Option<Outcome> {
/// unwinding to cross the boundary, but `catch_unwind` here means unwinding /// unwinding to cross the boundary, but `catch_unwind` here means unwinding
/// never actually does. /// never actually does.
pub extern "C-unwind" fn trampoline() { pub extern "C-unwind" fn trampoline() {
let b = CURRENT_ACTOR_BOX.with(|c| c.borrow_mut().take()) let b = match CURRENT_ACTOR_BOX.with(|c| c.borrow_mut().take()) {
.expect("trampoline entered without a closure set"); Some(b) => b,
None => panic!("smarm: trampoline entered without a closure set (core corrupt)"),
};
let outcome = match panic::catch_unwind(panic::AssertUnwindSafe(b)) { let outcome = match panic::catch_unwind(panic::AssertUnwindSafe(b)) {
Ok(()) => Outcome::Exit, Ok(()) => Outcome::Exit,
+18
View File
@@ -94,10 +94,28 @@ unsafe extern "C" fn switch_to_actor_asm() {
} }
/// Resume the actor whose sp is in `ACTOR_SP`. Returns when the actor yields. /// Resume the actor whose sp is in `ACTOR_SP`. Returns when the actor yields.
///
/// # Safety
///
/// The caller must be running on a scheduler thread with a valid actor stack
/// pointer installed in `ACTOR_SP` — either by `init_actor_stack` (first
/// resume) or by a prior `switch_to_scheduler` (subsequent resumes). Resuming
/// with an unset or stale `ACTOR_SP` transfers control to an arbitrary address.
/// Must not be called from within an actor (only the scheduler side may resume).
pub unsafe fn switch_to_actor() { pub unsafe fn switch_to_actor() {
unsafe { switch_to_actor_asm() }; unsafe { switch_to_actor_asm() };
} }
/// Yield from the running actor back to its scheduler thread. Returns when the
/// actor is next resumed via [`switch_to_actor`].
///
/// # Safety
///
/// The caller must be running on an actor stack that was entered through
/// [`switch_to_actor`], so that `SCHEDULER_SP` holds the live saved stack
/// pointer of the scheduler side. Calling this from the scheduler thread, or
/// before any actor has been resumed, transfers control to an arbitrary
/// address.
#[unsafe(naked)] #[unsafe(naked)]
pub unsafe extern "C" fn switch_to_scheduler() { pub unsafe extern "C" fn switch_to_scheduler() {
core::arch::naked_asm!( core::arch::naked_asm!(
+15 -9
View File
@@ -240,15 +240,18 @@ impl IoThread {
self.outstanding += 1; self.outstanding += 1;
// Send can only fail if the pool has hung up, which only happens // Send can only fail if the pool has hung up, which only happens
// on shutdown. submit during shutdown is a bug. // on shutdown. submit during shutdown is a bug.
self.tx if self.tx.send(Request { pid, epoch, work }).is_err() {
.send(Request { pid, epoch, work }) panic!("smarm: io pool hung up unexpectedly (submit during shutdown)");
.expect("io pool hung up unexpectedly"); }
} }
/// Drain every available completion. Caller (the scheduler) routes the /// Drain every available completion. Caller (the scheduler) routes the
/// results and updates `outstanding` / `waiters` accordingly. /// results and updates `outstanding` / `waiters` accordingly.
pub fn drain_completions(&mut self) -> Vec<Completion> { pub fn drain_completions(&mut self) -> Vec<Completion> {
let mut q = self.completions.lock().unwrap(); let mut q = match self.completions.lock() {
Ok(g) => g,
Err(e) => panic!("smarm: io completions lock poisoned (core corrupt): {e}"),
};
let mut out = Vec::with_capacity(q.len()); let mut out = Vec::with_capacity(q.len());
while let Some(c) = q.pop_front() { while let Some(c) = q.pop_front() {
out.push(c); out.push(c);
@@ -389,10 +392,10 @@ fn pool_loop(
Ok(r) => r, Ok(r) => r,
Err(payload) => Err(payload), Err(payload) => Err(payload),
}; };
completions match completions.lock() {
.lock() Ok(mut g) => g.push_back(Completion::Blocking { pid, epoch, result }),
.unwrap() Err(e) => panic!("smarm: io completions lock poisoned (core corrupt): {e}"),
.push_back(Completion::Blocking { pid, epoch, result }); }
wake_scheduler(wake_write); wake_scheduler(wake_write);
} }
} }
@@ -435,7 +438,10 @@ fn epoll_loop(
let mut shutdown_requested = false; let mut shutdown_requested = false;
let mut pushed_any = false; let mut pushed_any = false;
{ {
let mut q = completions.lock().unwrap(); let mut q = match completions.lock() {
Ok(g) => g,
Err(e) => panic!("smarm: io completions lock poisoned (core corrupt): {e}"),
};
for ev in events.iter().take(n as usize) { for ev in events.iter().take(n as usize) {
if ev.u64 == SHUTDOWN_EPOLL_TOKEN { if ev.u64 == SHUTDOWN_EPOLL_TOKEN {
shutdown_requested = true; shutdown_requested = true;
+4 -1
View File
@@ -135,7 +135,10 @@ pub fn link<A>(target: Pid<A>) {
if registered_on_target { if registered_on_target {
with_runtime(|inner| { with_runtime(|inner| {
let slot = inner.slot_at(me).expect("link: own slot vanished"); let slot = match inner.slot_at(me) {
Some(s) => s,
None => panic!("smarm: link own slot vanished (core corrupt)"),
};
let mut cold = slot.cold.lock(); let mut cold = slot.cold.lock();
if !cold.links.contains(&target) { if !cold.links.contains(&target) {
cold.links.push(target); cold.links.push(target);
+89 -26
View File
@@ -64,7 +64,10 @@ impl MutexCore {
impl TimerTarget for MutexCore { impl TimerTarget for MutexCore {
fn on_timeout(&self, pid: Pid, epoch: u32) { fn on_timeout(&self, pid: Pid, epoch: u32) {
let unpark = { let unpark = {
let mut st = self.state.lock().unwrap(); let mut st = match self.state.lock() {
Ok(g) => g,
Err(e) => panic!("smarm: mutex state lock poisoned (core corrupt): {e}"),
};
// Remove from waiters only if still there with matching epoch. // Remove from waiters only if still there with matching epoch.
// If the lock was already granted (holder == Some(pid)), the // If the lock was already granted (holder == Some(pid)), the
// timer fired after the grant — treat as no-op; the actor // timer fired after the grant — treat as no-op; the actor
@@ -72,12 +75,12 @@ impl TimerTarget for MutexCore {
if st.holder == Some(pid) { if st.holder == Some(pid) {
return; return;
} }
let pos = st.waiters.iter().position(|w| w.pid == pid && w.epoch == epoch); match st.waiters.iter().position(|w| w.pid == pid && w.epoch == epoch) {
if pos.is_some() { Some(pos) => {
st.waiters.remove(pos.unwrap()); st.waiters.remove(pos);
true true
} else { }
false None => false,
} }
}; };
if unpark { if unpark {
@@ -105,11 +108,17 @@ impl<T> Mutex<T> {
} }
pub fn set_default_timeout(&self, timeout: Duration) { pub fn set_default_timeout(&self, timeout: Duration) {
self.core.state.lock().unwrap().default_timeout = timeout; match self.core.state.lock() {
Ok(mut st) => st.default_timeout = timeout,
Err(e) => panic!("smarm: mutex state lock poisoned (core corrupt): {e}"),
}
} }
pub fn lock(&self) -> Result<MutexGuard<'_, T>, LockTimeout> { pub fn lock(&self) -> Result<MutexGuard<'_, T>, LockTimeout> {
let timeout = self.core.state.lock().unwrap().default_timeout; let timeout = match self.core.state.lock() {
Ok(st) => st.default_timeout,
Err(e) => panic!("smarm: mutex state lock poisoned (core corrupt): {e}"),
};
self.lock_timeout(timeout) self.lock_timeout(timeout)
} }
@@ -122,12 +131,21 @@ impl<T> Mutex<T> {
// Fast path: nobody holds it. // Fast path: nobody holds it.
{ {
let mut st = self.core.state.lock().unwrap(); let mut st = match self.core.state.lock() {
Ok(g) => g,
Err(e) => panic!("smarm: mutex state lock poisoned (core corrupt): {e}"),
};
if st.holder.is_none() { if st.holder.is_none() {
st.holder = Some(me); st.holder = Some(me);
drop(st); drop(st);
let value = self.value.lock().unwrap().take() let taken = match self.value.lock() {
.expect("Mutex: value missing on free fast path"); Ok(mut g) => g.take(),
Err(e) => panic!("smarm: mutex value lock poisoned (core corrupt): {e}"),
};
let value = match taken {
Some(v) => v,
None => panic!("smarm: Mutex value missing on free fast path (core corrupt)"),
};
return Ok(MutexGuard { mutex: self, value: Some(value) }); return Ok(MutexGuard { mutex: self, value: Some(value) });
} }
} }
@@ -135,7 +153,10 @@ impl<T> Mutex<T> {
// Slow path: register as a waiter, set timeout, park. // Slow path: register as a waiter, set timeout, park.
let _np = scheduler::NoPreempt::enter(); let _np = scheduler::NoPreempt::enter();
let epoch = { let epoch = {
let mut st = self.core.state.lock().unwrap(); let mut st = match self.core.state.lock() {
Ok(g) => g,
Err(e) => panic!("smarm: mutex state lock poisoned (core corrupt): {e}"),
};
// begin_wait is lock-free — legal under the state lock; this // begin_wait is lock-free — legal under the state lock; this
// makes the epoch atomic with the registration's visibility to // makes the epoch atomic with the registration's visibility to
// grants and timeouts. // grants and timeouts.
@@ -153,10 +174,19 @@ impl<T> Mutex<T> {
// wait (both epoch-stamped; a stop wake unwinds out of // wait (both epoch-stamped; a stop wake unwinds out of
// park_current). The one-shot interpretation below is therefore // park_current). The one-shot interpretation below is therefore
// exhaustive. Are we the holder? // exhaustive. Are we the holder?
let is_holder = self.core.state.lock().unwrap().holder == Some(me); let is_holder = match self.core.state.lock() {
Ok(st) => st.holder == Some(me),
Err(e) => panic!("smarm: mutex state lock poisoned (core corrupt): {e}"),
};
if is_holder { if is_holder {
let value = self.value.lock().unwrap().take() let taken = match self.value.lock() {
.expect("Mutex: value missing after grant"); Ok(mut g) => g.take(),
Err(e) => panic!("smarm: mutex value lock poisoned (core corrupt): {e}"),
};
let value = match taken {
Some(v) => v,
None => panic!("smarm: Mutex value missing after grant (core corrupt)"),
};
Ok(MutexGuard { mutex: self, value: Some(value) }) Ok(MutexGuard { mutex: self, value: Some(value) })
} else { } else {
Err(LockTimeout) Err(LockTimeout)
@@ -165,14 +195,23 @@ impl<T> Mutex<T> {
pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> { pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
let me = crate::actor::current_pid()?; let me = crate::actor::current_pid()?;
let mut st = self.core.state.lock().unwrap(); let mut st = match self.core.state.lock() {
Ok(g) => g,
Err(e) => panic!("smarm: mutex state lock poisoned (core corrupt): {e}"),
};
if st.holder.is_some() { if st.holder.is_some() {
return None; return None;
} }
st.holder = Some(me); st.holder = Some(me);
drop(st); drop(st);
let value = self.value.lock().unwrap().take() let taken = match self.value.lock() {
.expect("Mutex: value missing on try_lock free path"); Ok(mut g) => g.take(),
Err(e) => panic!("smarm: mutex value lock poisoned (core corrupt): {e}"),
};
let value = match taken {
Some(v) => v,
None => panic!("smarm: Mutex value missing on try_lock free path (core corrupt)"),
};
Some(MutexGuard { mutex: self, value: Some(value) }) Some(MutexGuard { mutex: self, value: Some(value) })
} }
@@ -183,7 +222,10 @@ impl<T> Mutex<T> {
// tracking and just grab the value mutex directly. This is safe because // tracking and just grab the value mutex directly. This is safe because
// outside the runtime there are no green threads competing. // outside the runtime there are no green threads competing.
let value = loop { let value = loop {
let v = self.value.lock().unwrap().take(); let v = match self.value.lock() {
Ok(mut g) => g.take(),
Err(e) => panic!("smarm: mutex value lock poisoned (core corrupt): {e}"),
};
if let Some(v) = v { break v; } if let Some(v) = v { break v; }
std::thread::yield_now(); std::thread::yield_now();
}; };
@@ -212,30 +254,51 @@ pub struct MutexGuard<'a, T> {
impl<T> std::ops::Deref for MutexGuard<'_, T> { impl<T> std::ops::Deref for MutexGuard<'_, T> {
type Target = T; type Target = T;
fn deref(&self) -> &T { self.value.as_ref().expect("MutexGuard: value missing") } fn deref(&self) -> &T {
match self.value.as_ref() {
Some(v) => v,
None => panic!("smarm: MutexGuard value missing (core corrupt)"),
}
}
} }
impl<T> std::ops::DerefMut for MutexGuard<'_, T> { impl<T> std::ops::DerefMut for MutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T { fn deref_mut(&mut self) -> &mut T {
self.value.as_mut().expect("MutexGuard: value missing") match self.value.as_mut() {
Some(v) => v,
None => panic!("smarm: MutexGuard value missing (core corrupt)"),
}
} }
} }
impl<T: std::fmt::Debug> std::fmt::Debug for MutexGuard<'_, T> { impl<T: std::fmt::Debug> std::fmt::Debug for MutexGuard<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = match self.value.as_ref() {
Some(v) => v,
None => panic!("smarm: MutexGuard value missing (core corrupt)"),
};
f.debug_tuple("MutexGuard") f.debug_tuple("MutexGuard")
.field(self.value.as_ref().expect("MutexGuard: value missing")) .field(value)
.finish() .finish()
} }
} }
impl<T> Drop for MutexGuard<'_, T> { impl<T> Drop for MutexGuard<'_, T> {
fn drop(&mut self) { fn drop(&mut self) {
let v = self.value.take().expect("MutexGuard: double drop"); let v = match self.value.take() {
*self.mutex.value.lock().unwrap() = Some(v); Some(v) => v,
None => panic!("smarm: MutexGuard double drop (core corrupt)"),
};
match self.mutex.value.lock() {
Ok(mut g) => *g = Some(v),
Err(e) => panic!("smarm: mutex value lock poisoned (core corrupt): {e}"),
}
let next = { let next = {
let mut st = self.mutex.core.state.lock().unwrap(); let mut st = match self.mutex.core.state.lock() {
Ok(g) => g,
Err(e) => panic!("smarm: mutex state lock poisoned (core corrupt): {e}"),
};
match st.waiters.pop_front() { match st.waiters.pop_front() {
Some(w) => { Some(w) => {
st.holder = Some(w.pid); st.holder = Some(w.pid);
+6 -5
View File
@@ -193,11 +193,12 @@ impl Mailbox {
/// legal under a Leaf (Leaf -> Channel). /// legal under a Leaf (Leaf -> Channel).
fn clone_sender<M: Send + 'static>(&self) -> Option<Sender<M>> { fn clone_sender<M: Send + 'static>(&self) -> Option<Sender<M>> {
let ch = self.channels.get(&TypeId::of::<M>())?; let ch = self.channels.get(&TypeId::of::<M>())?;
let tx = ch let tx = match ch.sender.as_any().downcast_ref::<Sender<M>>() {
.sender Some(tx) => tx,
.as_any() None => panic!(
.downcast_ref::<Sender<M>>() "smarm: channel keyed by TypeId but downcast to its own type failed (core corrupt)"
.expect("channel keyed by TypeId but downcast to its own type failed — smarm bug"); ),
};
debug_assert_eq!(ch.msg_type, type_name::<M>(), "msg_type / TypeId disagree"); debug_assert_eq!(ch.msg_type, type_name::<M>(), "msg_type / TypeId disagree");
Some(tx.clone()) Some(tx.clone())
} }
+27 -3
View File
@@ -113,16 +113,32 @@ impl MutexQueue {
pub fn push(&self, pid: Pid) { pub fn push(&self, pid: Pid) {
assert_no_preempt(); assert_no_preempt();
self.q.lock().unwrap().push_back(pid); match self.q.lock() {
Ok(mut g) => g.push_back(pid),
Err(e) => panic!("smarm: run-queue q lock poisoned (core corrupt): {e}"),
}
} }
pub fn pop(&self) -> Option<Pid> { pub fn pop(&self) -> Option<Pid> {
assert_no_preempt(); assert_no_preempt();
self.q.lock().unwrap().pop_front() match self.q.lock() {
Ok(mut g) => g.pop_front(),
Err(e) => panic!("smarm: run-queue q lock poisoned (core corrupt): {e}"),
}
} }
pub fn len(&self) -> u64 { pub fn len(&self) -> u64 {
self.q.lock().unwrap().len() as u64 match self.q.lock() {
Ok(g) => g.len() as u64,
Err(e) => panic!("smarm: run-queue q lock poisoned (core corrupt): {e}"),
}
}
pub fn is_empty(&self) -> bool {
match self.q.lock() {
Ok(g) => g.is_empty(),
Err(e) => panic!("smarm: run-queue q lock poisoned (core corrupt): {e}"),
}
} }
} }
@@ -262,6 +278,10 @@ impl MpmcRing {
let d = self.dequeue_pos.0.load(Ordering::Relaxed); let d = self.dequeue_pos.0.load(Ordering::Relaxed);
e.saturating_sub(d) as u64 e.saturating_sub(d) as u64
} }
pub fn is_empty(&self) -> bool {
self.len() == 0
}
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -341,6 +361,10 @@ impl StripedRing {
pub fn len(&self) -> u64 { pub fn len(&self) -> u64 {
self.stripes.iter().map(|s| s.len()).sum() self.stripes.iter().map(|s| s.len()).sum()
} }
pub fn is_empty(&self) -> bool {
self.stripes.iter().all(|s| s.is_empty())
}
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
+89 -26
View File
@@ -655,6 +655,9 @@ pub(crate) struct RuntimeInner {
} }
impl RuntimeInner { impl RuntimeInner {
// Private constructor taking the parsed Config fields one-for-one; a params
// struct would only move the same 8 values across the call boundary.
#[allow(clippy::too_many_arguments)]
fn new( fn new(
thread_count: usize, thread_count: usize,
alloc_interval: u32, alloc_interval: u32,
@@ -762,7 +765,10 @@ impl RuntimeInner {
/// park-epoch and return it. See slot_state.rs for the rules. /// park-epoch and return it. See slot_state.rs for the rules.
#[must_use] #[must_use]
pub(crate) fn begin_wait(&self, pid: Pid) -> u32 { pub(crate) fn begin_wait(&self, pid: Pid) -> u32 {
let slot = self.slot_at(pid).expect("begin_wait: own slot vanished"); let slot = match self.slot_at(pid) {
Some(slot) => slot,
None => panic!("begin_wait: own slot vanished: {:?}", pid),
};
slot.word.begin_wait(pid.generation()) slot.word.begin_wait(pid.generation())
} }
@@ -771,7 +777,10 @@ impl RuntimeInner {
/// then eat a notification that already landed. The caller MUST /// then eat a notification that already landed. The caller MUST
/// re-check its stop flag afterwards — see `StateWord::clear_notify`. /// re-check its stop flag afterwards — see `StateWord::clear_notify`.
pub(crate) fn retire_wait(&self, pid: Pid) { pub(crate) fn retire_wait(&self, pid: Pid) {
let slot = self.slot_at(pid).expect("retire_wait: own slot vanished"); let slot = match self.slot_at(pid) {
Some(slot) => slot,
None => panic!("retire_wait: own slot vanished: {:?}", pid),
};
let _ = slot.word.begin_wait(pid.generation()); let _ = slot.word.begin_wait(pid.generation());
slot.word.clear_notify(pid.generation()); slot.word.clear_notify(pid.generation());
} }
@@ -932,7 +941,14 @@ impl Runtime {
self.inner.live_actors.load(Ordering::Acquire), 0, self.inner.live_actors.load(Ordering::Acquire), 0,
"run() called while previous run still active" "run() called while previous run still active"
); );
*self.inner.io.lock().unwrap() = Some(IoThread::start().expect("failed to start IO thread")); let io_thread = match IoThread::start() {
Ok(io) => io,
Err(e) => panic!("failed to start IO thread: {e}"),
};
match self.inner.io.lock() {
Ok(mut io) => *io = Some(io_thread),
Err(e) => panic!("smarm: io lock poisoned (core corrupt): {e}"),
}
// RFC 005: slot counters reset at the START of a run (not the end), // RFC 005: slot counters reset at the START of a run (not the end),
// so `stats()` read after `run()` returns reports that run's totals. // so `stats()` read after `run()` returns reports that run's totals.
@@ -977,8 +993,14 @@ impl Runtime {
drop(initial_handle); drop(initial_handle);
// Tear down IO and clean up for the next run() call. // Tear down IO and clean up for the next run() call.
drop(self.inner.io.lock().unwrap().take()); // joins IO threads match self.inner.io.lock() {
self.inner.timers.lock().unwrap().clear(); Ok(mut io) => drop(io.take()), // joins IO threads
Err(e) => panic!("smarm: io lock poisoned (core corrupt): {e}"),
}
match self.inner.timers.lock() {
Ok(mut timers) => timers.clear(),
Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"),
}
self.inner.next_monitor_id.store(0, Ordering::Relaxed); self.inner.next_monitor_id.store(0, Ordering::Relaxed);
// Every slot must have come back: any leak here is a runtime bug // Every slot must have come back: any leak here is a runtime bug
// (a JoinHandle held across run() is decremented just above). // (a JoinHandle held across run() is decremented just above).
@@ -1156,10 +1178,16 @@ fn finalize_actor(inner: &Arc<RuntimeInner>, pid: Pid, outcome: Outcome) {
Outcome::Stopped => (Outcome::Stopped, Signal::Stopped(pid), DownReason::Stopped), Outcome::Stopped => (Outcome::Stopped, Signal::Stopped(pid), DownReason::Stopped),
}; };
let slot = inner.slot_at(pid).expect("finalize_actor: pid out of range"); let slot = match inner.slot_at(pid) {
Some(slot) => slot,
None => panic!("finalize_actor: pid out of range: {:?}", pid),
};
let (waiters, monitors, links, actor) = { let (waiters, monitors, links, actor) = {
let mut cold = slot.cold.lock(); let mut cold = slot.cold.lock();
let actor = cold.actor.take().expect("finalize_actor: actor vanished"); let actor = match cold.actor.take() {
Some(actor) => actor,
None => panic!("finalize_actor: actor vanished"),
};
cold.outcome = Some(joiner_outcome); cold.outcome = Some(joiner_outcome);
slot.stop_ptr.store(std::ptr::null_mut(), Ordering::Release); slot.stop_ptr.store(std::ptr::null_mut(), Ordering::Release);
// Done is published under the cold lock, so join's // Done is published under the cold lock, so join's
@@ -1301,17 +1329,23 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
// just to discover there is nothing to drain. The clock is read // just to discover there is nothing to drain. The clock is read
// only when the timer heap is non-empty. // only when the timer heap is non-empty.
let due = { let due = {
let mut t = inner.timers.lock().unwrap(); let mut t = match inner.timers.lock() {
Ok(t) => t,
Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"),
};
if t.is_empty() { if t.is_empty() {
Vec::new() Vec::new()
} else { } else {
t.pop_due(std::time::Instant::now()) t.pop_due(std::time::Instant::now())
} }
}; };
let completions = inner.io.lock().unwrap() let completions = match inner.io.lock() {
.as_mut() Ok(mut io) => io
.map(|io| io.drain_completions()) .as_mut()
.unwrap_or_default(); .map(|io| io.drain_completions())
.unwrap_or_default(),
Err(e) => panic!("smarm: io lock poisoned (core corrupt): {e}"),
};
for entry in due { for entry in due {
match entry.reason { match entry.reason {
// A sleep expiry is just an unpark: the protocol handles // A sleep expiry is just an unpark: the protocol handles
@@ -1340,8 +1374,15 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
for completion in completions { for completion in completions {
match completion { match completion {
crate::io::Completion::Blocking { pid, epoch, result } => { crate::io::Completion::Blocking { pid, epoch, result } => {
if let Some(io) = inner.io.lock().unwrap().as_mut() { match inner.io.lock() {
io.outstanding = io.outstanding.saturating_sub(1); Ok(mut io) => {
if let Some(io) = io.as_mut() {
io.outstanding = io.outstanding.saturating_sub(1);
}
}
Err(e) => {
panic!("smarm: io lock poisoned (core corrupt): {e}")
}
} }
// Stash the result under the cold lock, then unpark. // Stash the result under the cold lock, then unpark.
// The protocol also covers the submit→park window // The protocol also covers the submit→park window
@@ -1363,11 +1404,16 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
crate::io::Completion::FdReady { fd, events: _ } => { crate::io::Completion::FdReady { fd, events: _ } => {
// Resolve the parked pid under the io lock, then wake // Resolve the parked pid under the io lock, then wake
// through the protocol. Lock order: io before all. // through the protocol. Lock order: io before all.
let parked = inner.io.lock().unwrap().as_mut().and_then(|io| { let parked = match inner.io.lock() {
let entry = io.waiters.remove(&fd); Ok(mut io) => io.as_mut().and_then(|io| {
io.epoll_deregister(fd); let entry = io.waiters.remove(&fd);
entry io.epoll_deregister(fd);
}); entry
}),
Err(e) => {
panic!("smarm: io lock poisoned (core corrupt): {e}")
}
};
if let Some((pid, epoch)) = parked { if let Some((pid, epoch)) = parked {
inner.unpark_at(pid, epoch); inner.unpark_at(pid, epoch);
} }
@@ -1411,9 +1457,15 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
// Read IO liveness BEFORE the queue lock (phase-1 ordering: a // Read IO liveness BEFORE the queue lock (phase-1 ordering: a
// completion resurrects an actor only via the drain path, whose // completion resurrects an actor only via the drain path, whose
// enqueue would be visible under the queue lock we take next). // enqueue would be visible under the queue lock we take next).
let (io_out, io_fd) = match inner.io.lock().unwrap().as_ref() { let (io_out, io_fd) = {
Some(io) => (io.outstanding + io.waiters.len() as u32, Some(io.wake_fd())), let io = match inner.io.lock() {
None => (0, None), Ok(io) => io,
Err(e) => panic!("smarm: io lock poisoned (core corrupt): {e}"),
};
match io.as_ref() {
Some(io) => (io.outstanding + io.waiters.len() as u32, Some(io.wake_fd())),
None => (0, None),
}
}; };
stats.run_queue_len.store(inner.run_queue.len(), Ordering::Relaxed); stats.run_queue_len.store(inner.run_queue.len(), Ordering::Relaxed);
@@ -1457,7 +1509,10 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
// Remaining timer entries are orphaned (no live actor can be // Remaining timer entries are orphaned (no live actor can be
// woken by them — e.g. a sleeper cancelled out of its sleep); // woken by them — e.g. a sleeper cancelled out of its sleep);
// they must not keep the runtime alive. Drop them on the way out. // they must not keep the runtime alive. Drop them on the way out.
inner.timers.lock().unwrap().clear(); match inner.timers.lock() {
Ok(mut timers) => timers.clear(),
Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"),
}
// Terminal wake: a sibling scheduler may be blocked in its // Terminal wake: a sibling scheduler may be blocked in its
// idle wait on a snapshot that is now terminally stale — an // idle wait on a snapshot that is now terminally stale — an
// orphaned long deadline (it would sleep it out in full) or // orphaned long deadline (it would sleep it out in full) or
@@ -1466,8 +1521,13 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
// no completion, so nothing else writes the wake pipe). // no completion, so nothing else writes the wake pipe).
// One byte wakes every poller; each re-runs the verdict, // One byte wakes every poller; each re-runs the verdict,
// reaches AllDone itself, and re-wakes — idempotent. // reaches AllDone itself, and re-wakes — idempotent.
if let Some(io) = inner.io.lock().unwrap().as_ref() { match inner.io.lock() {
io.wake(); Ok(io) => {
if let Some(io) = io.as_ref() {
io.wake();
}
}
Err(e) => panic!("smarm: io lock poisoned (core corrupt): {e}"),
} }
return; return;
} }
@@ -1481,7 +1541,10 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
Pop::Idle { io_outstanding, wake_fd } => { Pop::Idle { io_outstanding, wake_fd } => {
// Something is still in flight. Sleep on the appropriate // Something is still in flight. Sleep on the appropriate
// source to avoid hammering the queue mutex; retry on wake. // source to avoid hammering the queue mutex; retry on wake.
let next_deadline = inner.timers.lock().unwrap().peek_deadline(); let next_deadline = match inner.timers.lock() {
Ok(timers) => timers.peek_deadline(),
Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"),
};
match (next_deadline, wake_fd) { match (next_deadline, wake_fd) {
(Some(deadline), fd_opt) => { (Some(deadline), fd_opt) => {
let now = std::time::Instant::now(); let now = std::time::Instant::now();
+129 -39
View File
@@ -39,7 +39,10 @@ pub(crate) fn with_runtime<R>(f: impl FnOnce(&Arc<RuntimeInner>) -> R) -> R {
let prev = crate::preempt::PREEMPTION_ENABLED.with(|c| c.replace(false)); let prev = crate::preempt::PREEMPTION_ENABLED.with(|c| c.replace(false));
let result = RUNTIME.with(|r| { let result = RUNTIME.with(|r| {
let b = r.borrow(); let b = r.borrow();
let inner = b.as_ref().expect("smarm: not inside Runtime::run()"); let inner = match b.as_ref() {
Some(inner) => inner,
None => panic!("smarm: not inside Runtime::run()"),
};
f(inner) f(inner)
}); });
crate::preempt::PREEMPTION_ENABLED.with(|c| c.set(prev)); crate::preempt::PREEMPTION_ENABLED.with(|c| c.set(prev));
@@ -51,7 +54,7 @@ pub(crate) fn with_runtime<R>(f: impl FnOnce(&Arc<RuntimeInner>) -> R) -> R {
/// Same preemption gate as [`with_runtime`]; same reasons. /// Same preemption gate as [`with_runtime`]; same reasons.
pub(crate) fn try_with_runtime<R>(f: impl FnOnce(&Arc<RuntimeInner>) -> R) -> Option<R> { pub(crate) fn try_with_runtime<R>(f: impl FnOnce(&Arc<RuntimeInner>) -> R) -> Option<R> {
let prev = crate::preempt::PREEMPTION_ENABLED.with(|c| c.replace(false)); let prev = crate::preempt::PREEMPTION_ENABLED.with(|c| c.replace(false));
let result = RUNTIME.with(|r| r.borrow().as_ref().map(|inner| f(inner))); let result = RUNTIME.with(|r| r.borrow().as_ref().map(f));
crate::preempt::PREEMPTION_ENABLED.with(|c| c.set(prev)); crate::preempt::PREEMPTION_ENABLED.with(|c| c.set(prev));
result result
} }
@@ -76,15 +79,20 @@ impl JoinHandle {
pub fn join(mut self) -> Result<(), JoinError> { pub fn join(mut self) -> Result<(), JoinError> {
use crate::actor::Outcome; use crate::actor::Outcome;
let me = current_pid().expect("join() called outside an actor"); let me = match current_pid() {
Some(pid) => pid,
None => panic!("join() called outside an actor"),
};
loop { loop {
// Check-Done-or-register-waiter is atomic under the target's cold // Check-Done-or-register-waiter is atomic under the target's cold
// lock; finalize publishes Done and takes the waiter list under // lock; finalize publishes Done and takes the waiter list under
// the same lock, so we either see the outcome or are woken. // the same lock, so we either see the outcome or are woken.
let outcome = with_runtime(|inner| { let outcome = with_runtime(|inner| {
let slot = inner.slot_at(self.pid) let slot = match inner.slot_at(self.pid) {
.expect("join: pid index out of range"); Some(slot) => slot,
None => panic!("join: pid index out of range: {:?}", self.pid),
};
let mut cold = slot.cold.lock(); let mut cold = slot.cold.lock();
match slot.status_for(self.pid) { match slot.status_for(self.pid) {
// Our outstanding handle pins the slot: it cannot be // Our outstanding handle pins the slot: it cannot be
@@ -93,7 +101,10 @@ impl JoinHandle {
panic!("join: target slot has been reused") panic!("join: target slot has been reused")
} }
crate::slot_state::Status::Done => { crate::slot_state::Status::Done => {
Some(cold.outcome.take().expect("Done slot must have outcome")) Some(match cold.outcome.take() {
Some(outcome) => outcome,
None => panic!("Done slot must have outcome"),
})
} }
crate::slot_state::Status::Live => { crate::slot_state::Status::Live => {
// begin_wait is lock-free, legal under the cold lock; // begin_wait is lock-free, legal under the cold lock;
@@ -181,8 +192,10 @@ pub fn spawn_under<A>(supervisor: Pid<A>, f: impl FnOnce() + Send + 'static) ->
// syscall and no allocation ever stalls another scheduler thread. // syscall and no allocation ever stalls another scheduler thread.
let stack = with_runtime(|inner| inner.stack_pool.lock().pop()) let stack = with_runtime(|inner| inner.stack_pool.lock().pop())
.unwrap_or_else(|| { .unwrap_or_else(|| {
crate::stack::Stack::new(crate::runtime::ACTOR_STACK_SIZE) match crate::stack::Stack::new(crate::runtime::ACTOR_STACK_SIZE) {
.expect("stack allocation failed") Ok(stack) => stack,
Err(e) => panic!("stack allocation failed: {e}"),
}
}); });
let sp = init_actor_stack(stack.top(), crate::actor::trampoline); let sp = init_actor_stack(stack.top(), crate::actor::trampoline);
let closure: crate::runtime::Closure = Box::new(f); let closure: crate::runtime::Closure = Box::new(f);
@@ -225,7 +238,10 @@ pub fn spawn_addr<A: crate::pid::Addressable>(
use crate::context::init_actor_stack; use crate::context::init_actor_stack;
pub fn self_pid() -> Pid { pub fn self_pid() -> Pid {
current_pid().expect("self_pid() called outside an actor") match current_pid() {
Some(pid) => pid,
None => panic!("self_pid() called outside an actor"),
}
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -282,7 +298,10 @@ pub(crate) fn unpark_at(pid: Pid, epoch: u32) {
/// waker. Lock-free (one CAS on the own slot word), so it is legal under /// waker. Lock-free (one CAS on the own slot word), so it is legal under
/// any lock, including a Channel-class lock. /// any lock, including a Channel-class lock.
pub(crate) fn begin_wait() -> u32 { pub(crate) fn begin_wait() -> u32 {
let me = current_pid().expect("begin_wait() called outside an actor"); let me = match current_pid() {
Some(pid) => pid,
None => panic!("begin_wait() called outside an actor"),
};
with_runtime(|inner| inner.begin_wait(me)) with_runtime(|inner| inner.begin_wait(me))
} }
@@ -294,7 +313,10 @@ pub(crate) fn begin_wait() -> u32 {
/// self-clean at their wakers' failed CAS; nothing can fault the actor's /// self-clean at their wakers' failed CAS; nothing can fault the actor's
/// next one-shot park. /// next one-shot park.
pub(crate) fn retire_wait() { pub(crate) fn retire_wait() {
let me = current_pid().expect("retire_wait() called outside an actor"); let me = match current_pid() {
Some(pid) => pid,
None => panic!("retire_wait() called outside an actor"),
};
with_runtime(|inner| inner.retire_wait(me)); with_runtime(|inner| inner.retire_wait(me));
// A request_stop that fired before the clear had its notification // A request_stop that fired before the clear had its notification
// eaten, but it set the stop flag first — observe it here and unwind. // eaten, but it set the stop flag first — observe it here and unwind.
@@ -365,11 +387,19 @@ impl Drop for NoPreempt {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
pub fn sleep(duration: std::time::Duration) { pub fn sleep(duration: std::time::Duration) {
let me = current_pid().expect("sleep() called outside an actor"); let me = match current_pid() {
Some(pid) => pid,
None => panic!("sleep() called outside an actor"),
};
let _np = NoPreempt::enter(); let _np = NoPreempt::enter();
let epoch = begin_wait(); let epoch = begin_wait();
let deadline = crate::timer::deadline_from_now(duration); let deadline = crate::timer::deadline_from_now(duration);
with_runtime(|inner| inner.timers.lock().unwrap().insert_sleep(deadline, me, epoch)); with_runtime(|inner| {
match inner.timers.lock() {
Ok(mut timers) => timers.insert_sleep(deadline, me, epoch),
Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"),
}
});
park_current(); park_current();
} }
@@ -380,11 +410,14 @@ pub fn insert_wait_timer(
epoch: u32, epoch: u32,
) { ) {
with_runtime(|inner| { with_runtime(|inner| {
inner.timers.lock().unwrap().insert( match inner.timers.lock() {
deadline, Ok(mut timers) => timers.insert(
pid, deadline,
crate::timer::Reason::WaitTimeout { target, epoch }, pid,
); crate::timer::Reason::WaitTimeout { target, epoch },
),
Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"),
}
}); });
} }
@@ -412,7 +445,12 @@ pub fn send_after<A: crate::pid::Addressable>(
let fire = Box::new(move || { let fire = Box::new(move || {
let _ = crate::registry::send_to(dest, msg); let _ = crate::registry::send_to(dest, msg);
}); });
with_runtime(|inner| inner.timers.lock().unwrap().insert_send(deadline, dest.erase(), fire)) with_runtime(|inner| {
match inner.timers.lock() {
Ok(mut timers) => timers.insert_send(deadline, dest.erase(), fire),
Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"),
}
})
} }
/// Deliver `msg` to whichever actor holds the name `dest` at fire time /// Deliver `msg` to whichever actor holds the name `dest` at fire time
@@ -429,7 +467,12 @@ pub fn send_after_named<M: Send + 'static>(
let fire = Box::new(move || { let fire = Box::new(move || {
let _ = crate::registry::send(dest, msg); let _ = crate::registry::send(dest, msg);
}); });
with_runtime(|inner| inner.timers.lock().unwrap().insert_send(deadline, armed_by, fire)) with_runtime(|inner| {
match inner.timers.lock() {
Ok(mut timers) => timers.insert_send(deadline, armed_by, fire),
Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"),
}
})
} }
/// Deliver `msg` onto a channel the caller owns after `after`, rather than to a /// Deliver `msg` onto a channel the caller owns after `after`, rather than to a
@@ -453,14 +496,24 @@ pub(crate) fn send_after_to<T: Send + 'static>(
let fire = Box::new(move || { let fire = Box::new(move || {
let _ = tx.send(msg); let _ = tx.send(msg);
}); });
with_runtime(|inner| inner.timers.lock().unwrap().insert_send(deadline, armed_by, fire)) with_runtime(|inner| {
match inner.timers.lock() {
Ok(mut timers) => timers.insert_send(deadline, armed_by, fire),
Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"),
}
})
} }
/// Cancel a timer armed by [`send_after`] / [`send_after_named`]. Returns /// Cancel a timer armed by [`send_after`] / [`send_after_named`]. Returns
/// `true` if it was still pending (delivery now prevented), `false` if it had /// `true` if it was still pending (delivery now prevented), `false` if it had
/// already fired or been cancelled. /// already fired or been cancelled.
pub fn cancel_timer(id: crate::timer::TimerId) -> bool { pub fn cancel_timer(id: crate::timer::TimerId) -> bool {
with_runtime(|inner| inner.timers.lock().unwrap().cancel(id)) with_runtime(|inner| {
match inner.timers.lock() {
Ok(mut timers) => timers.cancel(id),
Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"),
}
})
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -472,7 +525,10 @@ where
F: FnOnce() -> T + Send + 'static, F: FnOnce() -> T + Send + 'static,
T: Send + 'static, T: Send + 'static,
{ {
let me = current_pid().expect("block_on_io() called outside an actor"); let me = match current_pid() {
Some(pid) => pid,
None => panic!("block_on_io() called outside an actor"),
};
let work: Box<dyn FnOnce() -> crate::io::IoResult + Send> = Box::new(move || { let work: Box<dyn FnOnce() -> crate::io::IoResult + Send> = Box::new(move || {
let v: T = f(); let v: T = f();
Ok(Box::new(v) as Box<dyn std::any::Any + Send>) Ok(Box::new(v) as Box<dyn std::any::Any + Send>)
@@ -481,24 +537,37 @@ where
let _np = NoPreempt::enter(); let _np = NoPreempt::enter();
let epoch = begin_wait(); let epoch = begin_wait();
with_runtime(|inner| { with_runtime(|inner| {
let mut io = inner.io.lock().unwrap(); let mut io = match inner.io.lock() {
io.as_mut().expect("io thread not started").submit(me, epoch, work); Ok(io) => io,
Err(e) => panic!("smarm: io lock poisoned (core corrupt): {e}"),
};
match io.as_mut() {
Some(io) => io.submit(me, epoch, work),
None => panic!("io thread not started"),
}
}); });
park_current(); park_current();
} }
let result = with_runtime(|inner| { let result = with_runtime(|inner| {
let slot = inner.slot_at(me).expect("block_on_io: own slot vanished"); let slot = match inner.slot_at(me) {
Some(slot) => slot,
None => panic!("block_on_io: own slot vanished"),
};
let mut cold = slot.cold.lock(); let mut cold = slot.cold.lock();
debug_assert_eq!( debug_assert_eq!(
slot.generation(), me.generation(), slot.generation(), me.generation(),
"block_on_io: own slot reused mid-park" "block_on_io: own slot reused mid-park"
); );
cold.pending_io_result match cold.pending_io_result.take() {
.take() Some(result) => result,
.expect("block_on_io: resumed without a result") None => panic!("block_on_io: resumed without a result"),
}
}); });
match result { match result {
Ok(any) => *any.downcast::<T>().expect("block_on_io: type mismatch"), Ok(any) => match any.downcast::<T>() {
Ok(typed) => *typed,
Err(_) => panic!("block_on_io: type mismatch"),
},
Err(payload) => std::panic::resume_unwind(payload), Err(payload) => std::panic::resume_unwind(payload),
} }
} }
@@ -512,12 +581,21 @@ pub fn wait_writable(fd: std::os::fd::RawFd) -> std::io::Result<()> {
} }
fn wait_fd(fd: std::os::fd::RawFd, readable: bool, writable: bool) -> std::io::Result<()> { fn wait_fd(fd: std::os::fd::RawFd, readable: bool, writable: bool) -> std::io::Result<()> {
let me = current_pid().expect("wait_*() called outside an actor"); let me = match current_pid() {
Some(pid) => pid,
None => panic!("wait_*() called outside an actor"),
};
let _np = NoPreempt::enter(); let _np = NoPreempt::enter();
let epoch = begin_wait(); let epoch = begin_wait();
with_runtime(|inner| { with_runtime(|inner| {
let mut io = inner.io.lock().unwrap(); let mut io = match inner.io.lock() {
io.as_mut().expect("io thread not started").epoll_register(fd, me, epoch, readable, writable) Ok(io) => io,
Err(e) => panic!("smarm: io lock poisoned (core corrupt): {e}"),
};
match io.as_mut() {
Some(io) => io.epoll_register(fd, me, epoch, readable, writable),
None => panic!("io thread not started"),
}
})?; })?;
// If a terminal stop unwinds us out of the park below, the registration // If a terminal stop unwinds us out of the park below, the registration
@@ -535,7 +613,10 @@ fn wait_fd(fd: std::os::fd::RawFd, readable: bool, writable: bool) -> std::io::R
impl Drop for Dereg { impl Drop for Dereg {
fn drop(&mut self) { fn drop(&mut self) {
with_runtime(|inner| { with_runtime(|inner| {
let mut io = inner.io.lock().unwrap(); let mut io = match inner.io.lock() {
Ok(io) => io,
Err(e) => panic!("smarm: io lock poisoned (core corrupt): {e}"),
};
if let Some(io) = io.as_mut() { if let Some(io) = io.as_mut() {
if io.waiters.get(&self.fd) == Some(&(self.me, self.epoch)) { if io.waiters.get(&self.fd) == Some(&(self.me, self.epoch)) {
io.waiters.remove(&self.fd); io.waiters.remove(&self.fd);
@@ -597,10 +678,16 @@ impl crate::channel::Selectable for FdArm {
return Ok(false); return Ok(false);
} }
with_runtime(|inner| { with_runtime(|inner| {
let mut io = inner.io.lock().unwrap(); let mut io = match inner.io.lock() {
io.as_mut() Ok(io) => io,
.expect("io thread not started") Err(e) => panic!("smarm: io lock poisoned (core corrupt): {e}"),
.epoll_register(self.fd, pid, epoch, self.readable, self.writable) };
match io.as_mut() {
Some(io) => {
io.epoll_register(self.fd, pid, epoch, self.readable, self.writable)
}
None => panic!("io thread not started"),
}
})?; })?;
Ok(true) Ok(true)
} }
@@ -621,7 +708,10 @@ impl crate::channel::Selectable for FdArm {
/// actor's fresh registration; in that case touch nothing. /// actor's fresh registration; in that case touch nothing.
fn sel_unregister(&self, pid: Pid, epoch: u32) { fn sel_unregister(&self, pid: Pid, epoch: u32) {
with_runtime(|inner| { with_runtime(|inner| {
let mut io = inner.io.lock().unwrap(); let mut io = match inner.io.lock() {
Ok(io) => io,
Err(e) => panic!("smarm: io lock poisoned (core corrupt): {e}"),
};
if let Some(io) = io.as_mut() { if let Some(io) = io.as_mut() {
if io.waiters.get(&self.fd) == Some(&(pid, epoch)) { if io.waiters.get(&self.fd) == Some(&(pid, epoch)) {
io.waiters.remove(&self.fd); io.waiters.remove(&self.fd);
+2 -2
View File
@@ -253,7 +253,7 @@ impl OneForOne {
.collect(), .collect(),
}; };
// Stop survivors in reverse start order (highest child index first). // Stop survivors in reverse start order (highest child index first).
to_stop.sort_unstable_by(|a, b| b.1.cmp(&a.1)); to_stop.sort_unstable_by_key(|x| std::cmp::Reverse(x.1));
// The set we will restart: the failed child plus every sibling we // The set we will restart: the failed child plus every sibling we
// are about to stop, restarted in start (ascending index) order. // are about to stop, restarted in start (ascending index) order.
@@ -299,7 +299,7 @@ impl OneForOne {
// and this is a no-op; on a cap-trip or mailbox-closed break it tears // and this is a no-op; on a cap-trip or mailbox-closed break it tears
// the remaining children down deterministically instead of leaking them. // the remaining children down deterministically instead of leaking them.
let mut survivors: Vec<(Pid, usize)> = by_pid.iter().map(|(p, i)| (*p, *i)).collect(); let mut survivors: Vec<(Pid, usize)> = by_pid.iter().map(|(p, i)| (*p, *i)).collect();
survivors.sort_unstable_by(|a, b| b.1.cmp(&a.1)); survivors.sort_unstable_by_key(|x| std::cmp::Reverse(x.1));
let mut awaiting: Vec<Pid> = Vec::with_capacity(survivors.len()); let mut awaiting: Vec<Pid> = Vec::with_capacity(survivors.len());
for (pid, _) in &survivors { for (pid, _) in &survivors {
crate::scheduler::request_stop(*pid); crate::scheduler::request_stop(*pid);
+4 -1
View File
@@ -214,7 +214,10 @@ impl Timers {
let mut out = Vec::new(); let mut out = Vec::new();
while let Some(r) = self.heap.peek() { while let Some(r) = self.heap.peek() {
if r.0.deadline <= now { if r.0.deadline <= now {
let entry = self.heap.pop().unwrap().0; let entry = match self.heap.pop() {
Some(e) => e.0,
None => panic!("smarm: timer heap pop after peek returned None (core corrupt)"),
};
if matches!(entry.reason, Reason::Send { .. }) && !self.armed.remove(&entry.seq) { if matches!(entry.reason, Reason::Send { .. }) && !self.armed.remove(&entry.seq) {
// Cancelled before it came due: discard, do not deliver. // Cancelled before it came due: discard, do not deliver.
continue; continue;