feat: I/O and mutex support (v0.3)

Add epoll-based non-blocking I/O and kernel-like mutexes:
- src/io.rs: Complete epoll backend with timeout & error handling
- src/mutex.rs: Fair mutex with waiter queues & parking integration
- Enhanced scheduler to support synchronous I/O blocking
- Comprehensive test suites for I/O (epoll) and mutex behavior
- Documentation: LOOM.md concurrency model & README
This commit is contained in:
Claude
2026-05-23 16:09:29 +00:00
parent d3ab81b833
commit 8cbef1dfc1
11 changed files with 2032 additions and 146 deletions

View File

@@ -114,3 +114,96 @@ fn many_concurrent_sleepers_all_wake() {
});
assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 20);
}
// ---------------------------------------------------------------------------
// Direct tests on the Timers data structure. No scheduler involved — these
// cover the new Reason machinery without needing a Mutex implementation.
// ---------------------------------------------------------------------------
use smarm::pid::Pid;
use smarm::timer::{Reason, TimerTarget, Timers};
use std::cell::RefCell;
use std::rc::Rc;
struct RecordingTarget {
calls: RefCell<Vec<(Pid, u64)>>,
}
impl TimerTarget for RecordingTarget {
fn on_timeout(&self, pid: Pid, seq: u64) {
self.calls.borrow_mut().push((pid, seq));
}
}
#[test]
fn timers_pop_due_returns_entries_in_deadline_order() {
let mut t = Timers::new();
let now = Instant::now();
// Insert out of order; pop_due should hand them back sorted by deadline.
t.insert_sleep(now + Duration::from_millis(30), Pid::new(0, 0));
t.insert_sleep(now + Duration::from_millis(10), Pid::new(1, 0));
t.insert_sleep(now + Duration::from_millis(20), Pid::new(2, 0));
// Advance past all of them.
let due = t.pop_due(now + Duration::from_millis(50));
let pids: Vec<u32> = due.iter().map(|e| e.pid.index()).collect();
assert_eq!(pids, vec![1, 2, 0]);
assert!(t.is_empty());
}
#[test]
fn timers_only_pop_entries_whose_deadline_has_passed() {
let mut t = Timers::new();
let now = Instant::now();
t.insert_sleep(now + Duration::from_millis(5), Pid::new(0, 0));
t.insert_sleep(now + Duration::from_millis(100), Pid::new(1, 0));
let due = t.pop_due(now + Duration::from_millis(20));
assert_eq!(due.len(), 1);
assert_eq!(due[0].pid.index(), 0);
assert!(!t.is_empty());
// The unpopped entry's deadline is still visible.
assert!(t.peek_deadline().is_some());
}
#[test]
fn timers_mix_sleep_and_wait_timeout_reasons() {
let mut t = Timers::new();
let target = Rc::new(RecordingTarget { calls: RefCell::new(Vec::new()) });
let now = Instant::now();
t.insert_sleep(now + Duration::from_millis(5), Pid::new(0, 0));
t.insert(
now + Duration::from_millis(10),
Pid::new(1, 0),
Reason::WaitTimeout { target: target.clone(), wait_seq: 42 },
);
let due = t.pop_due(now + Duration::from_millis(20));
assert_eq!(due.len(), 2);
// Order: Sleep (5ms) first, WaitTimeout (10ms) second.
match &due[0].reason {
Reason::Sleep => {}
_ => panic!("first entry should be a Sleep"),
}
match &due[1].reason {
Reason::WaitTimeout { wait_seq, .. } => assert_eq!(*wait_seq, 42),
_ => panic!("second entry should be a WaitTimeout"),
}
}
#[test]
fn same_deadline_entries_pop_in_insertion_order() {
// The `seq` tiebreaker means inserting two entries with the same
// deadline preserves the order they were inserted.
let mut t = Timers::new();
let now = Instant::now();
let d = now + Duration::from_millis(10);
t.insert_sleep(d, Pid::new(0, 0));
t.insert_sleep(d, Pid::new(1, 0));
t.insert_sleep(d, Pid::new(2, 0));
let due = t.pop_due(now + Duration::from_millis(20));
let pids: Vec<u32> = due.iter().map(|e| e.pid.index()).collect();
assert_eq!(pids, vec![0, 1, 2]);
}