feat: full runtime redesign (v0.6)

Complete rewrite with improved architecture & correctness:
- src/runtime.rs: Simplified task scheduling with proper state transitions
- src/scheduler.rs: Decoupled from runtime, pure task queue logic
- src/io.rs, src/mutex.rs: Refactored for clarity & performance
- New actor model framework (src/actor.rs, src/context.rs)
- Channel primitives (src/channel.rs) & process IDs (src/pid.rs)
- Preemption framework (src/preempt.rs) for fair timeslicing
- Expanded benchmarks & tests (multi_scheduler, primes, runtime)
This commit is contained in:
Claude
2026-05-23 16:09:35 +00:00
parent 078447539c
commit 978678a46e
31 changed files with 5751 additions and 0 deletions

110
tests/channel.rs Normal file
View File

@@ -0,0 +1,110 @@
//! Channel tests. These run under the scheduler because `recv()` needs to
//! be able to park, which requires a live runtime.
use smarm::{channel, run, spawn};
use std::cell::Cell;
thread_local! {
static OUT: Cell<i64> = const { Cell::new(0) };
}
#[test]
fn send_then_recv_same_actor() {
OUT.with(|c| c.set(0));
run(|| {
let (tx, rx) = channel::<i64>();
tx.send(42).unwrap();
let v = rx.recv().unwrap();
OUT.with(|c| c.set(v));
});
assert_eq!(OUT.with(|c| c.get()), 42);
}
#[test]
fn recv_parks_until_send_from_other_actor() {
OUT.with(|c| c.set(0));
run(|| {
let (tx, rx) = channel::<i64>();
let h = spawn(move || {
// This actor blocks on an empty channel.
let v = rx.recv().unwrap();
OUT.with(|c| c.set(v));
});
// Parent runs, then yields to let the child block,
// then sends, then joins.
smarm::yield_now();
tx.send(7).unwrap();
h.join().unwrap();
});
assert_eq!(OUT.with(|c| c.get()), 7);
}
#[test]
fn multiple_messages_arrive_in_order() {
let captured: std::sync::Arc<std::sync::Mutex<Vec<i64>>> =
std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let cap2 = captured.clone();
run(move || {
let (tx, rx) = channel::<i64>();
let h = spawn(move || {
for _ in 0..3 {
let v = rx.recv().unwrap();
cap2.lock().unwrap().push(v);
}
});
for v in 1..=3i64 {
tx.send(v).unwrap();
}
h.join().unwrap();
});
assert_eq!(*captured.lock().unwrap(), vec![1, 2, 3]);
}
#[test]
fn cloned_senders_both_deliver() {
let captured: std::sync::Arc<std::sync::Mutex<Vec<i64>>> =
std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let cap2 = captured.clone();
run(move || {
let (tx, rx) = channel::<i64>();
let tx2 = tx.clone();
let h = spawn(move || {
for _ in 0..2 {
let v = rx.recv().unwrap();
cap2.lock().unwrap().push(v);
}
});
tx.send(10).unwrap();
tx2.send(20).unwrap();
h.join().unwrap();
});
let mut got = captured.lock().unwrap().clone();
got.sort();
assert_eq!(got, vec![10, 20]);
}
#[test]
fn recv_returns_err_when_all_senders_dropped() {
let saw_err: std::sync::Arc<std::sync::atomic::AtomicBool> =
std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let saw_err2 = saw_err.clone();
run(move || {
let (tx, rx) = channel::<i64>();
let h = spawn(move || {
// Receiver waits; no message will ever come.
if rx.recv().is_err() {
saw_err2.store(true, std::sync::atomic::Ordering::SeqCst);
}
});
smarm::yield_now();
drop(tx); // last sender gone; rx.recv must return Err.
h.join().unwrap();
});
assert!(saw_err.load(std::sync::atomic::Ordering::SeqCst));
}