diff --git a/src/channel.rs b/src/channel.rs index d586e04..f14fc34 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -176,8 +176,10 @@ impl Receiver { if g.senders == 0 { return Err(RecvError); } - let me = crate::actor::current_pid() - .expect("recv() called outside an actor"); + let me = match crate::actor::current_pid() { + Some(me) => me, + None => panic!("smarm: recv() called outside an actor"), + }; debug_assert!( g.parked_receiver.is_none_or(|(p, _)| p == me), "channel has more than one receiver" @@ -191,7 +193,10 @@ impl Receiver { // Release the lock before parking — the unparker will need it. crate::scheduler::park_current(); // Woken up — record it before looping to check the queue. - crate::te!(crate::trace::Event::RecvWake(crate::actor::current_pid().unwrap())); + crate::te!(crate::trace::Event::RecvWake(match crate::actor::current_pid() { + Some(p) => p, + None => panic!("smarm: RecvWake outside an actor (core corrupt)"), + })); } } @@ -222,8 +227,10 @@ impl Receiver { where T: Send + 'static, { - let me = crate::actor::current_pid() - .expect("recv_timeout() called outside an actor"); + let me = match crate::actor::current_pid() { + Some(me) => me, + None => panic!("smarm: recv_timeout() called outside an actor"), + }; // Fast path + wait registration, one critical section. let epoch; @@ -254,7 +261,10 @@ impl Receiver { crate::scheduler::insert_wait_timer(deadline, me, target, epoch); crate::scheduler::park_current(); - crate::te!(crate::trace::Event::RecvWake(crate::actor::current_pid().unwrap())); + crate::te!(crate::trace::Event::RecvWake(match crate::actor::current_pid() { + Some(p) => p, + None => panic!("smarm: RecvWake outside an actor (core corrupt)"), + })); let mut g = self.inner.lock(); if let Some(v) = g.queue.pop_front() { crate::preempt::note_message_received(); @@ -283,17 +293,23 @@ impl Receiver { loop { { let mut g = self.inner.lock(); - if let Some(i) = g.queue.iter().position(|v| pred(v)) { + if let Some(i) = g.queue.iter().position(&pred) { // position() found it, so remove() returns Some. crate::preempt::note_message_received(); - return Ok(g.queue.remove(i).unwrap()); + let v = match g.queue.remove(i) { + Some(v) => v, + None => panic!("smarm: channel queue.remove after position (logic bug)"), + }; + return Ok(v); } if g.senders == 0 { // Closed and nothing queued can ever match. return Err(RecvError); } - let me = crate::actor::current_pid() - .expect("recv_match() called outside an actor"); + let me = match crate::actor::current_pid() { + Some(me) => me, + None => panic!("smarm: recv_match() called outside an actor"), + }; debug_assert!( g.parked_receiver.is_none_or(|(p, _)| p == me), "channel has more than one receiver" @@ -303,7 +319,10 @@ impl Receiver { } // Release the lock before parking — the unparker will need it. crate::scheduler::park_current(); - crate::te!(crate::trace::Event::RecvWake(crate::actor::current_pid().unwrap())); + crate::te!(crate::trace::Event::RecvWake(match crate::actor::current_pid() { + Some(p) => p, + None => panic!("smarm: RecvWake outside an actor (core corrupt)"), + })); } } @@ -316,9 +335,13 @@ impl Receiver { F: Fn(&T) -> bool, { let mut g = self.inner.lock(); - if let Some(i) = g.queue.iter().position(|v| pred(v)) { + if let Some(i) = g.queue.iter().position(&pred) { crate::preempt::note_message_received(); - return Ok(Some(g.queue.remove(i).unwrap())); + let v = match g.queue.remove(i) { + Some(v) => v, + None => panic!("smarm: channel queue.remove after position (logic bug)"), + }; + return Ok(Some(v)); } if g.senders == 0 { return Err(RecvError); @@ -466,7 +489,10 @@ impl Selectable for Receiver { /// see [`try_select`] for the fallible form; channel-only selects cannot /// fail). pub fn select(arms: &[&dyn Selectable]) -> usize { - try_select(arms).expect("select(): fd arm failed to register (use try_select)") + match try_select(arms) { + Ok(i) => i, + Err(e) => panic!("smarm: select() fd arm failed to register (use try_select): {e}"), + } } /// [`select`], fallible: `Err` when an arm fails to register (only fd @@ -476,7 +502,10 @@ pub fn select(arms: &[&dyn Selectable]) -> usize { /// one has been unregistered. pub fn try_select(arms: &[&dyn Selectable]) -> std::io::Result { assert!(!arms.is_empty(), "select() on an empty arm list"); - let me = crate::actor::current_pid().expect("select() called outside an actor"); + let me = match crate::actor::current_pid() { + Some(me) => me, + None => panic!("smarm: select() called outside an actor"), + }; loop { let epoch = crate::scheduler::begin_wait(); if let Some(i) = register_arms(me, epoch, arms)? { @@ -626,8 +655,12 @@ pub fn select_timeout( arms: &[&dyn Selectable], timeout: std::time::Duration, ) -> Option { - try_select_timeout(arms, timeout) - .expect("select_timeout(): fd arm failed to register (use try_select_timeout)") + match try_select_timeout(arms, timeout) { + Ok(r) => r, + Err(e) => panic!( + "smarm: select_timeout() fd arm failed to register (use try_select_timeout): {e}" + ), + } } /// [`select_timeout`], fallible: `Err` when an arm fails to register @@ -638,7 +671,10 @@ pub fn try_select_timeout( timeout: std::time::Duration, ) -> std::io::Result> { assert!(!arms.is_empty(), "select_timeout() on an empty arm list"); - let me = crate::actor::current_pid().expect("select_timeout() called outside an actor"); + let me = match crate::actor::current_pid() { + Some(me) => me, + None => panic!("smarm: select_timeout() called outside an actor"), + }; let epoch = crate::scheduler::begin_wait(); if let Some(i) = register_arms(me, epoch, arms)? { return Ok(Some(i)); // ready now: the timer was never armed diff --git a/src/gen_server.rs b/src/gen_server.rs index 22954d1..44d61a8 100644 --- a/src/gen_server.rs +++ b/src/gen_server.rs @@ -528,7 +528,10 @@ impl TimerHandle { /// [`TimerId`] you can pass to `cancel`. Timer fires arrive before info and /// inbox messages. pub fn arm_after(&self, after: Duration, msg: G::Timer) -> TimerId { - let mut reg = self.reg.lock().unwrap(); + let mut reg = match self.reg.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: gen_server reg lock poisoned (core corrupt): {e}"), + }; let local = reg.mint(); // The fire thunk carries the local id so the loop can retire the entry // on dispatch; `msg` is moved in (no `Clone` needed for one-shots). @@ -552,7 +555,10 @@ impl TimerHandle { where G::Timer: Clone, { - let mut reg = self.reg.lock().unwrap(); + let mut reg = match self.reg.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: gen_server reg lock poisoned (core corrupt): {e}"), + }; let local = reg.mint(); // A live periodic must keep the system arm open so the next tick can be // re-armed; the first periodic installs the loop's re-arm sender. @@ -577,7 +583,10 @@ impl TimerHandle { /// stops re-arming: the entry is removed so the loop will not schedule /// another tick even if the pending one already escaped onto the channel. pub fn cancel(&self, id: TimerId) -> bool { - let mut reg = self.reg.lock().unwrap(); + let mut reg = match self.reg.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: gen_server reg lock poisoned (core corrupt): {e}"), + }; if let Some(sub) = reg.oneshots.remove(&id) { return cancel_timer(sub); } @@ -587,7 +596,7 @@ impl TimerHandle { reg.rearm_tx = None; } debug_assert!( - reg.rearm_tx.is_some() == !reg.periodics.is_empty(), + reg.rearm_tx.is_some() != reg.periodics.is_empty(), "rearm_tx must be Some iff periodics is non-empty" ); return beat; @@ -846,7 +855,10 @@ fn server_loop( impl Drop for Terminate { fn drop(&mut self) { { - let mut reg = self.1.lock().unwrap(); + let mut reg = match self.1.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: gen_server reg lock poisoned (core corrupt): {e}"), + }; for (_, sub) in reg.oneshots.drain() { cancel_timer(sub); } @@ -995,7 +1007,10 @@ fn server_loop( // The one-shot fired: retire its registry entry so the // live set tracks only still-pending timers, then // dispatch. - reg.lock().unwrap().oneshots.remove(&id); + match reg.lock() { + Ok(mut g) => { g.oneshots.remove(&id); } + Err(e) => panic!("smarm: gen_server reg lock poisoned (core corrupt): {e}"), + } guard.0.handle_timer(msg); reset_idle(&mut idle_deadline); } @@ -1006,16 +1021,20 @@ fn server_loop( // so the period is measured from fire-handling and a // handler that cancels stops the just-armed instance. let msg = { - let mut g = reg.lock().unwrap(); + let mut g = match reg.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: gen_server reg lock poisoned (core corrupt): {e}"), + }; let r = &mut *g; if let Some(p) = r.periodics.get_mut(&id) { let every = p.every; let msg = (p.make)(); - let tx = r - .rearm_tx - .as_ref() - .expect("a live periodic keeps rearm_tx Some") - .clone(); + let tx = match r.rearm_tx.as_ref() { + Some(tx) => tx.clone(), + None => panic!( + "smarm: live periodic without rearm_tx (logic bug)" + ), + }; p.live = send_after_to(every, tx, Sys::Tick(id)); Some(msg) } else { diff --git a/src/gen_statem.rs b/src/gen_statem.rs index 1fa44fe..745bc72 100644 --- a/src/gen_statem.rs +++ b/src/gen_statem.rs @@ -257,7 +257,10 @@ impl Cx { /// state. Only one is ever pending — arming again cancels and replaces the /// previous one. pub fn state_timeout(&mut self, after: Duration) { - let mut reg = self.reg.lock().unwrap(); + let mut reg = match self.reg.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: gen_statem reg lock poisoned (core corrupt): {e}"), + }; if let Some((_, old_sub)) = reg.state.take() { cancel_timer(old_sub); } @@ -271,7 +274,10 @@ impl Cx { /// `name`. Arming the same `name` again cancels and replaces the pending one /// for that name. pub fn timeout(&mut self, name: &'static str, after: Duration) { - let mut reg = self.reg.lock().unwrap(); + let mut reg = match self.reg.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: gen_statem reg lock poisoned (core corrupt): {e}"), + }; if let Some((_, old_sub)) = reg.named.remove(name) { cancel_timer(old_sub); } @@ -283,7 +289,10 @@ impl Cx { /// Cancel the named timeout `name` if pending. Returns `true` if the cancel /// beat the fire, `false` if it had already fired / was never armed. pub fn cancel_timeout(&mut self, name: &'static str) -> bool { - let mut reg = self.reg.lock().unwrap(); + let mut reg = match self.reg.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: gen_statem reg lock poisoned (core corrupt): {e}"), + }; match reg.named.remove(name) { Some((_, sub)) => cancel_timer(sub), None => false, @@ -294,7 +303,10 @@ impl Cx { /// beat the fire. Rarely needed by hand (the loop auto-resets on /// transition); exposed for a handler that wants to disarm within a state. pub fn cancel_state_timeout(&mut self) -> bool { - let mut reg = self.reg.lock().unwrap(); + let mut reg = match self.reg.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: gen_statem reg lock poisoned (core corrupt): {e}"), + }; match reg.state.take() { Some((_, sub)) => cancel_timer(sub), None => false, @@ -311,7 +323,10 @@ impl Cx { /// `enter` if it wants one. #[doc(hidden)] pub fn __reset_state_timeout(&mut self) { - let mut reg = self.reg.lock().unwrap(); + let mut reg = match self.reg.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: gen_statem reg lock poisoned (core corrupt): {e}"), + }; if let Some((_, sub)) = reg.state.take() { cancel_timer(sub); } @@ -458,7 +473,10 @@ fn statem_loop(rx: Receiver, mut machine: M) { // escaped onto the channel. A stale fire is dropped. let ev = match fire { Sys::StateTimeout(local) => { - let mut t = reg.lock().unwrap(); + let mut t = match reg.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: gen_statem reg lock poisoned (core corrupt): {e}"), + }; match t.state { Some((live, _)) if live == local => { t.state = None; // retire: it has now fired @@ -468,7 +486,10 @@ fn statem_loop(rx: Receiver, mut machine: M) { } } Sys::Timeout(name, local) => { - let mut t = reg.lock().unwrap(); + let mut t = match reg.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: gen_statem reg lock poisoned (core corrupt): {e}"), + }; match t.named.get(name) { Some(&(live, _)) if live == local => { t.named.remove(name); // retire on fire diff --git a/src/trace.rs b/src/trace.rs index 4545daa..e39c1a3 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -101,7 +101,7 @@ mod inner { thread_local! { static LOCAL_STATE: std::cell::RefCell> = - std::cell::RefCell::new(None); + const { std::cell::RefCell::new(None) }; } // ----------------------------------------------------------------------- @@ -115,14 +115,20 @@ mod inner { let (tx, rx) = mpsc::channel::(); let start = Instant::now(); - *GLOBAL.lock().unwrap() = Some(Global { sender: tx, start }); + match GLOBAL.lock() { + Ok(mut g) => *g = Some(Global { sender: tx, start }), + Err(e) => panic!("smarm: trace lock poisoned (core corrupt): {e}"), + } // Drain thread: owns the Receiver, writes to disk. let path_for_thread = path.clone(); - std::thread::Builder::new() + match std::thread::Builder::new() .name("smarm-trace-drain".into()) .spawn(move || drain_thread(rx, &path_for_thread)) - .expect("failed to spawn trace drain thread"); + { + Ok(_) => {} + Err(e) => panic!("smarm: failed to spawn trace drain thread: {e}"), + } eprintln!("[smarm-trace] writing to {}", path); } @@ -133,7 +139,10 @@ mod inner { // Drop the global sender so the drain thread's recv() returns Err // after the Flush sentinel, signalling clean shutdown. let sender = { - let mut g = GLOBAL.lock().unwrap(); + let mut g = match GLOBAL.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: trace lock poisoned (core corrupt): {e}"), + }; g.take().map(|g| g.sender) }; if let Some(tx) = sender { @@ -162,7 +171,11 @@ mod inner { let mut opt = cell.borrow_mut(); // Lazily initialise: one mutex hit per thread, ever. if opt.is_none() { - if let Some(g) = GLOBAL.lock().unwrap().as_ref() { + let guard = match GLOBAL.lock() { + Ok(g) => g, + Err(e) => panic!("smarm: trace lock poisoned (core corrupt): {e}"), + }; + if let Some(g) = guard.as_ref() { let tx = g.sender.clone(); *opt = Some(LocalState { tx, start: g.start }); }