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
+89 -26
View File
@@ -655,6 +655,9 @@ pub(crate) struct 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(
thread_count: usize,
alloc_interval: u32,
@@ -762,7 +765,10 @@ impl RuntimeInner {
/// park-epoch and return it. See slot_state.rs for the rules.
#[must_use]
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())
}
@@ -771,7 +777,10 @@ impl RuntimeInner {
/// then eat a notification that already landed. The caller MUST
/// re-check its stop flag afterwards — see `StateWord::clear_notify`.
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());
slot.word.clear_notify(pid.generation());
}
@@ -932,7 +941,14 @@ impl Runtime {
self.inner.live_actors.load(Ordering::Acquire), 0,
"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),
// so `stats()` read after `run()` returns reports that run's totals.
@@ -977,8 +993,14 @@ impl Runtime {
drop(initial_handle);
// Tear down IO and clean up for the next run() call.
drop(self.inner.io.lock().unwrap().take()); // joins IO threads
self.inner.timers.lock().unwrap().clear();
match self.inner.io.lock() {
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);
// Every slot must have come back: any leak here is a runtime bug
// (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),
};
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 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);
slot.stop_ptr.store(std::ptr::null_mut(), Ordering::Release);
// 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
// only when the timer heap is non-empty.
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() {
Vec::new()
} else {
t.pop_due(std::time::Instant::now())
}
};
let completions = inner.io.lock().unwrap()
.as_mut()
.map(|io| io.drain_completions())
.unwrap_or_default();
let completions = match inner.io.lock() {
Ok(mut io) => io
.as_mut()
.map(|io| io.drain_completions())
.unwrap_or_default(),
Err(e) => panic!("smarm: io lock poisoned (core corrupt): {e}"),
};
for entry in due {
match entry.reason {
// 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 {
match completion {
crate::io::Completion::Blocking { pid, epoch, result } => {
if let Some(io) = inner.io.lock().unwrap().as_mut() {
io.outstanding = io.outstanding.saturating_sub(1);
match inner.io.lock() {
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.
// 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: _ } => {
// Resolve the parked pid under the io lock, then wake
// through the protocol. Lock order: io before all.
let parked = inner.io.lock().unwrap().as_mut().and_then(|io| {
let entry = io.waiters.remove(&fd);
io.epoll_deregister(fd);
entry
});
let parked = match inner.io.lock() {
Ok(mut io) => io.as_mut().and_then(|io| {
let entry = io.waiters.remove(&fd);
io.epoll_deregister(fd);
entry
}),
Err(e) => {
panic!("smarm: io lock poisoned (core corrupt): {e}")
}
};
if let Some((pid, epoch)) = parked {
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
// completion resurrects an actor only via the drain path, whose
// enqueue would be visible under the queue lock we take next).
let (io_out, io_fd) = match inner.io.lock().unwrap().as_ref() {
Some(io) => (io.outstanding + io.waiters.len() as u32, Some(io.wake_fd())),
None => (0, None),
let (io_out, io_fd) = {
let io = match inner.io.lock() {
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);
@@ -1457,7 +1509,10 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
// Remaining timer entries are orphaned (no live actor can be
// 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.
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
// idle wait on a snapshot that is now terminally stale — an
// 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).
// One byte wakes every poller; each re-runs the verdict,
// reaches AllDone itself, and re-wakes — idempotent.
if let Some(io) = inner.io.lock().unwrap().as_ref() {
io.wake();
match inner.io.lock() {
Ok(io) => {
if let Some(io) = io.as_ref() {
io.wake();
}
}
Err(e) => panic!("smarm: io lock poisoned (core corrupt): {e}"),
}
return;
}
@@ -1481,7 +1541,10 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
Pop::Idle { io_outstanding, wake_fd } => {
// Something is still in flight. Sleep on the appropriate
// 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) {
(Some(deadline), fd_opt) => {
let now = std::time::Instant::now();