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:
+89
-26
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user