Hand-rolled context switching on mmap'd stacks with guard pages, allocator-driven RDTSC preemption, unbounded MPSC channels, supervision via per-slot Signal mailboxes, root supervisor as sentinel PID. Lib + tests + benches clean check/clippy. All 29 tests pass. Bench: smarm 3.4% over serial baseline, within 160us of tokio current-thread on prime-counting fan-out.
111 lines
3.0 KiB
Rust
111 lines
3.0 KiB
Rust
//! 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));
|
|
}
|