style: cargo fmt sweep under rustc 1.97.1 (toolchain reformat, no semantic change)
This commit is contained in:
+24
-6
@@ -49,8 +49,14 @@ fn looping_actor_on_check_is_stopped() {
|
||||
}
|
||||
let _ = h.join();
|
||||
});
|
||||
assert!(saw_stopped.load(Ordering::SeqCst), "expected DownReason::Stopped");
|
||||
assert!(dropped.load(Ordering::SeqCst), "Drop guard must run during the cancellation unwind");
|
||||
assert!(
|
||||
saw_stopped.load(Ordering::SeqCst),
|
||||
"expected DownReason::Stopped"
|
||||
);
|
||||
assert!(
|
||||
dropped.load(Ordering::SeqCst),
|
||||
"Drop guard must run during the cancellation unwind"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -79,8 +85,14 @@ fn parked_on_recv_actor_is_stopped() {
|
||||
}
|
||||
let _ = h.join();
|
||||
});
|
||||
assert!(saw_stopped.load(Ordering::SeqCst), "expected DownReason::Stopped");
|
||||
assert!(dropped.load(Ordering::SeqCst), "Drop guard must run on cancellation of a parked actor");
|
||||
assert!(
|
||||
saw_stopped.load(Ordering::SeqCst),
|
||||
"expected DownReason::Stopped"
|
||||
);
|
||||
assert!(
|
||||
dropped.load(Ordering::SeqCst),
|
||||
"Drop guard must run on cancellation of a parked actor"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -185,6 +197,12 @@ fn stop_flagged_while_queued_lands_at_first_park() {
|
||||
.recv_timeout(Duration::from_secs(10))
|
||||
.expect("runtime deadlocked: stop against a QUEUED actor was lost at its first park");
|
||||
|
||||
assert!(saw_stopped.load(Ordering::SeqCst), "expected DownReason::Stopped");
|
||||
assert!(dropped.load(Ordering::SeqCst), "Drop guard must run during the cancellation unwind");
|
||||
assert!(
|
||||
saw_stopped.load(Ordering::SeqCst),
|
||||
"expected DownReason::Stopped"
|
||||
);
|
||||
assert!(
|
||||
dropped.load(Ordering::SeqCst),
|
||||
"Drop guard must run during the cancellation unwind"
|
||||
);
|
||||
}
|
||||
|
||||
+25
-26
@@ -24,7 +24,11 @@ fn progress_point_counts() {
|
||||
h.join().unwrap();
|
||||
let after = smarm::causal::progress_snapshot();
|
||||
let delta = |name: &str| {
|
||||
after.iter().find(|(n, _)| n == name).map(|(_, c)| *c).unwrap()
|
||||
after
|
||||
.iter()
|
||||
.find(|(n, _)| n == name)
|
||||
.map(|(_, c)| *c)
|
||||
.unwrap()
|
||||
- before
|
||||
.iter()
|
||||
.find(|(n, _)| n == name)
|
||||
@@ -45,21 +49,12 @@ fn site_guard_nesting_restores() {
|
||||
assert_eq!(smarm::causal::current_site_name(), None);
|
||||
{
|
||||
let _outer = smarm::causal_site!("outer");
|
||||
assert_eq!(
|
||||
smarm::causal::current_site_name().as_deref(),
|
||||
Some("outer")
|
||||
);
|
||||
assert_eq!(smarm::causal::current_site_name().as_deref(), Some("outer"));
|
||||
{
|
||||
let _inner = smarm::causal_site!("inner");
|
||||
assert_eq!(
|
||||
smarm::causal::current_site_name().as_deref(),
|
||||
Some("inner")
|
||||
);
|
||||
assert_eq!(smarm::causal::current_site_name().as_deref(), Some("inner"));
|
||||
}
|
||||
assert_eq!(
|
||||
smarm::causal::current_site_name().as_deref(),
|
||||
Some("outer")
|
||||
);
|
||||
assert_eq!(smarm::causal::current_site_name().as_deref(), Some("outer"));
|
||||
}
|
||||
assert_eq!(smarm::causal::current_site_name(), None);
|
||||
});
|
||||
@@ -88,10 +83,7 @@ fn virtual_speedup_ledger() {
|
||||
let bystander = smarm::spawn(move || {
|
||||
while !stop2.load(Ordering::Relaxed) {
|
||||
smarm::check!();
|
||||
out2.store(
|
||||
smarm::causal::my_absorbed_delay_cycles(),
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
out2.store(smarm::causal::my_absorbed_delay_cycles(), Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -166,10 +158,7 @@ fn runnable_bystander_pays_delay() {
|
||||
while !stop_b.load(Ordering::Relaxed) {
|
||||
iters2.fetch_add(1, Ordering::Relaxed);
|
||||
smarm::check!();
|
||||
absorbed2.store(
|
||||
smarm::causal::my_absorbed_delay_cycles(),
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
absorbed2.store(smarm::causal::my_absorbed_delay_cycles(), Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -177,8 +166,7 @@ fn runnable_bystander_pays_delay() {
|
||||
let i0 = iters.load(Ordering::Relaxed);
|
||||
let t = std::time::Instant::now();
|
||||
smarm::sleep(Duration::from_millis(150));
|
||||
let rate =
|
||||
(iters.load(Ordering::Relaxed) - i0) as f64 / t.elapsed().as_secs_f64();
|
||||
let rate = (iters.load(Ordering::Relaxed) - i0) as f64 / t.elapsed().as_secs_f64();
|
||||
out.store(rate as u64, Ordering::Relaxed);
|
||||
};
|
||||
|
||||
@@ -448,7 +436,10 @@ fn timer_deadline_shifts_with_injected_delay() {
|
||||
|
||||
// Raw deadline passed, effective deadline not: nothing fires, entry kept.
|
||||
assert!(t.pop_due(now + Duration::from_millis(60)).is_empty());
|
||||
assert!(!t.is_empty(), "shifted entry must be re-queued, not dropped");
|
||||
assert!(
|
||||
!t.is_empty(),
|
||||
"shifted entry must be re-queued, not dropped"
|
||||
);
|
||||
|
||||
// Past raw + injected (with margin) it must fire. Chase in case a
|
||||
// parallel test injected more debt meanwhile.
|
||||
@@ -503,7 +494,11 @@ fn wall_timer_ignores_injected_delay() {
|
||||
// Just past the raw deadline: the wall entry fires, the virtual one is
|
||||
// re-queued at its shifted deadline.
|
||||
let due = t.pop_due(now + Duration::from_millis(60));
|
||||
assert_eq!(due.len(), 1, "exactly the wall entry must fire at raw deadline");
|
||||
assert_eq!(
|
||||
due.len(),
|
||||
1,
|
||||
"exactly the wall entry must fire at raw deadline"
|
||||
);
|
||||
assert_eq!(due[0].pid, Pid::new(0, 0));
|
||||
assert!(!t.is_empty(), "virtual sibling must remain queued, shifted");
|
||||
}
|
||||
@@ -623,7 +618,11 @@ fn wall_send_after_ignores_injected_delay() {
|
||||
|
||||
// Just past the raw deadline: only the wall send pops; run its thunk.
|
||||
let due = t.pop_due(now + Duration::from_millis(60));
|
||||
assert_eq!(due.len(), 1, "exactly the wall send must fire at raw deadline");
|
||||
assert_eq!(
|
||||
due.len(),
|
||||
1,
|
||||
"exactly the wall send must fire at raw deadline"
|
||||
);
|
||||
for e in due {
|
||||
if let smarm::timer::Reason::Send { fire } = e.reason {
|
||||
fire();
|
||||
|
||||
+18
-10
@@ -154,7 +154,10 @@ fn channel_ops_interleaved_with_monitor_churn_multi_thread() {
|
||||
}
|
||||
consumer.join().unwrap();
|
||||
});
|
||||
assert_eq!(total.load(std::sync::atomic::Ordering::Relaxed), (0..32).sum::<i64>());
|
||||
assert_eq!(
|
||||
total.load(std::sync::atomic::Ordering::Relaxed),
|
||||
(0..32).sum::<i64>()
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -220,7 +223,10 @@ fn recv_timeout_reports_disconnected_on_close() {
|
||||
fn recv_timeout_zero_duration_is_a_bounded_poll() {
|
||||
run(|| {
|
||||
let (_tx, rx) = channel::<i64>();
|
||||
assert_eq!(rx.recv_timeout(Duration::ZERO), Err(RecvTimeoutError::Timeout));
|
||||
assert_eq!(
|
||||
rx.recv_timeout(Duration::ZERO),
|
||||
Err(RecvTimeoutError::Timeout)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -262,15 +268,17 @@ fn recv_timeout_many_waiters_multi_thread() {
|
||||
let (tx, rx) = channel::<i64>();
|
||||
let got = got2.clone();
|
||||
let timed_out = timed_out2.clone();
|
||||
handles.push(spawn(move || match rx.recv_timeout(Duration::from_millis(100)) {
|
||||
Ok(v) => {
|
||||
assert_eq!(v, i);
|
||||
got.fetch_add(1, Ordering::Relaxed);
|
||||
handles.push(spawn(move || {
|
||||
match rx.recv_timeout(Duration::from_millis(100)) {
|
||||
Ok(v) => {
|
||||
assert_eq!(v, i);
|
||||
got.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => {
|
||||
timed_out.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
Err(e) => panic!("unexpected: {e}"),
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => {
|
||||
timed_out.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
Err(e) => panic!("unexpected: {e}"),
|
||||
}));
|
||||
if i % 2 == 0 {
|
||||
handles.push(spawn(move || {
|
||||
|
||||
+30
-11
@@ -11,9 +11,15 @@ thread_local! {
|
||||
static LOG: Cell<u64> = const { Cell::new(0) };
|
||||
}
|
||||
|
||||
fn log(v: u64) { LOG.with(|c| c.set(c.get() | v)); }
|
||||
fn get_log() -> u64 { LOG.with(|c| c.get()) }
|
||||
fn reset_log() { LOG.with(|c| c.set(0)); }
|
||||
fn log(v: u64) {
|
||||
LOG.with(|c| c.set(c.get() | v));
|
||||
}
|
||||
fn get_log() -> u64 {
|
||||
LOG.with(|c| c.get())
|
||||
}
|
||||
fn reset_log() {
|
||||
LOG.with(|c| c.set(0));
|
||||
}
|
||||
|
||||
extern "C-unwind" fn actor_simple() {
|
||||
log(0x1);
|
||||
@@ -56,7 +62,7 @@ fn actor_yields_and_resumes() {
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static REG_BEFORE: OnceLock<[u64; 4]> = OnceLock::new();
|
||||
static REG_AFTER: OnceLock<[u64; 4]> = OnceLock::new();
|
||||
static REG_AFTER: OnceLock<[u64; 4]> = OnceLock::new();
|
||||
|
||||
extern "C-unwind" fn actor_reg_check() {
|
||||
unsafe {
|
||||
@@ -73,7 +79,10 @@ extern "C-unwind" fn actor_reg_check() {
|
||||
REG_BEFORE.set([s0, s1, s2, s3]).ok();
|
||||
switch_to_scheduler();
|
||||
|
||||
let a0: u64; let a1: u64; let a2: u64; let a3: u64;
|
||||
let a0: u64;
|
||||
let a1: u64;
|
||||
let a2: u64;
|
||||
let a3: u64;
|
||||
core::arch::asm!(
|
||||
"mov {a0}, r12", "mov {a1}, r13", "mov {a2}, r14", "mov {a3}, r15",
|
||||
a0 = out(reg) a0, a1 = out(reg) a1, a2 = out(reg) a2, a3 = out(reg) a3,
|
||||
@@ -88,8 +97,14 @@ fn callee_saved_registers_survive_yield() {
|
||||
let stack = Stack::new(64 * 1024, 4096).unwrap();
|
||||
let sp = init_actor_stack(stack.top(), actor_reg_check);
|
||||
set_actor_sp(sp);
|
||||
unsafe { switch_to_actor(); switch_to_actor(); }
|
||||
assert_eq!(REG_BEFORE.get().copied().unwrap(), REG_AFTER.get().copied().unwrap());
|
||||
unsafe {
|
||||
switch_to_actor();
|
||||
switch_to_actor();
|
||||
}
|
||||
assert_eq!(
|
||||
REG_BEFORE.get().copied().unwrap(),
|
||||
REG_AFTER.get().copied().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
// Two actors, independent stacks.
|
||||
@@ -123,14 +138,18 @@ fn two_actors_dont_corrupt_each_other() {
|
||||
let sp_a = init_actor_stack(stack_a.top(), actor_a);
|
||||
let sp_b = init_actor_stack(stack_b.top(), actor_b);
|
||||
|
||||
set_actor_sp(sp_a); unsafe { switch_to_actor() };
|
||||
set_actor_sp(sp_a);
|
||||
unsafe { switch_to_actor() };
|
||||
let sp_a = get_actor_sp();
|
||||
|
||||
set_actor_sp(sp_b); unsafe { switch_to_actor() };
|
||||
set_actor_sp(sp_b);
|
||||
unsafe { switch_to_actor() };
|
||||
let sp_b = get_actor_sp();
|
||||
|
||||
set_actor_sp(sp_a); unsafe { switch_to_actor() };
|
||||
set_actor_sp(sp_b); unsafe { switch_to_actor() };
|
||||
set_actor_sp(sp_a);
|
||||
unsafe { switch_to_actor() };
|
||||
set_actor_sp(sp_b);
|
||||
unsafe { switch_to_actor() };
|
||||
|
||||
assert_eq!(A_VAL.with(|c| c.get()), 0xA00D);
|
||||
assert_eq!(B_VAL.with(|c| c.get()), 0xB00D);
|
||||
|
||||
+22
-7
@@ -11,8 +11,8 @@
|
||||
//! OUTSIDE `run` — an in-actor assertion alone passes vacuously.
|
||||
|
||||
use smarm::{
|
||||
channel, run, select, select_timeout, spawn, try_select, wait_readable,
|
||||
wait_readable_timeout, wait_writable_timeout, yield_now, FdArm,
|
||||
channel, run, select, select_timeout, spawn, try_select, wait_readable, wait_readable_timeout,
|
||||
wait_writable_timeout, yield_now, FdArm,
|
||||
};
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
@@ -33,7 +33,10 @@ impl Pipe {
|
||||
let mut fds: [libc::c_int; 2] = [0; 2];
|
||||
let r = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC | libc::O_NONBLOCK) };
|
||||
assert_eq!(r, 0, "pipe2 failed");
|
||||
Pipe { read: fds[0], write: fds[1] }
|
||||
Pipe {
|
||||
read: fds[0],
|
||||
write: fds[1],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,12 +256,18 @@ fn wait_readable_timeout_times_out_then_succeeds_with_data() {
|
||||
let (rfd, wfd) = (p.read, p.write);
|
||||
|
||||
let start = Instant::now();
|
||||
assert_eq!(wait_readable_timeout(rfd, Duration::from_millis(30)).unwrap(), false);
|
||||
assert_eq!(
|
||||
wait_readable_timeout(rfd, Duration::from_millis(30)).unwrap(),
|
||||
false
|
||||
);
|
||||
assert!(start.elapsed() >= Duration::from_millis(30));
|
||||
|
||||
// Timed-out wait must leave the fd clean; ready path returns true.
|
||||
assert_eq!(raw_write(wfd, b"d"), 1);
|
||||
assert_eq!(wait_readable_timeout(rfd, Duration::from_secs(5)).unwrap(), true);
|
||||
assert_eq!(
|
||||
wait_readable_timeout(rfd, Duration::from_secs(5)).unwrap(),
|
||||
true
|
||||
);
|
||||
let mut buf = [0u8; 1];
|
||||
assert_eq!(raw_read(rfd, &mut buf), 1);
|
||||
ok2.store(true, Ordering::SeqCst);
|
||||
@@ -274,7 +283,10 @@ fn wait_readable_timeout_wakes_on_late_data() {
|
||||
let p = Pipe::new();
|
||||
let (rfd, wfd) = (p.read, p.write);
|
||||
let h = spawn(move || {
|
||||
assert_eq!(wait_readable_timeout(rfd, Duration::from_secs(5)).unwrap(), true);
|
||||
assert_eq!(
|
||||
wait_readable_timeout(rfd, Duration::from_secs(5)).unwrap(),
|
||||
true
|
||||
);
|
||||
let mut buf = [0u8; 1];
|
||||
assert_eq!(raw_read(rfd, &mut buf), 1);
|
||||
got2.store(buf[0] as u32, Ordering::SeqCst);
|
||||
@@ -292,7 +304,10 @@ fn wait_writable_timeout_ready_now_on_empty_pipe() {
|
||||
run(move || {
|
||||
let p = Pipe::new();
|
||||
// An empty pipe's write end is writable: ready-now path, no park.
|
||||
assert_eq!(wait_writable_timeout(p.write, Duration::from_secs(5)).unwrap(), true);
|
||||
assert_eq!(
|
||||
wait_writable_timeout(p.write, Duration::from_secs(5)).unwrap(),
|
||||
true
|
||||
);
|
||||
ok2.store(true, Ordering::SeqCst);
|
||||
});
|
||||
assert!(ok.load(Ordering::SeqCst));
|
||||
|
||||
+59
-15
@@ -403,7 +403,10 @@ fn worker_pool_down_reaches_handle_down() {
|
||||
let got = Arc::new(Mutex::new(Vec::new()));
|
||||
let got2 = got.clone();
|
||||
run(move || {
|
||||
let server = start(Pool { watcher: None, log: Vec::new() });
|
||||
let server = start(Pool {
|
||||
watcher: None,
|
||||
log: Vec::new(),
|
||||
});
|
||||
server.cast(PoolCast::SpawnDoomedWorker).unwrap();
|
||||
let _ = server.call(()).unwrap(); // sync point: cast handled, worker live
|
||||
*got2.lock().unwrap() = server.call(()).unwrap();
|
||||
@@ -421,7 +424,10 @@ fn watch_dead_pid_is_noproc_down() {
|
||||
let h = spawn(|| {});
|
||||
let dead = h.pid();
|
||||
h.join().unwrap();
|
||||
let server = start(Pool { watcher: None, log: Vec::new() });
|
||||
let server = start(Pool {
|
||||
watcher: None,
|
||||
log: Vec::new(),
|
||||
});
|
||||
server.cast(PoolCast::Watch(dead)).unwrap();
|
||||
*got2.lock().unwrap() = server.call(()).unwrap();
|
||||
});
|
||||
@@ -497,7 +503,12 @@ impl GenServer for Timed {
|
||||
}
|
||||
|
||||
fn timed(fired: Arc<Mutex<Vec<u32>>>, cancel_won: Arc<Mutex<Option<bool>>>) -> Timed {
|
||||
Timed { timer: None, fired, cancel_won, last: None }
|
||||
Timed {
|
||||
timer: None,
|
||||
fired,
|
||||
cancel_won,
|
||||
last: None,
|
||||
}
|
||||
}
|
||||
|
||||
// A one-shot armed from a handler fires into handle_timer with its payload.
|
||||
@@ -534,7 +545,11 @@ fn cancel_before_fire_suppresses_it() {
|
||||
let count = server.call(()).unwrap();
|
||||
assert_eq!(count, 0, "cancelled timer must not fire");
|
||||
});
|
||||
assert_eq!(*cancel_won.lock().unwrap(), Some(true), "cancel beat the fire");
|
||||
assert_eq!(
|
||||
*cancel_won.lock().unwrap(),
|
||||
Some(true),
|
||||
"cancel beat the fire"
|
||||
);
|
||||
assert!(fired.lock().unwrap().is_empty());
|
||||
}
|
||||
|
||||
@@ -549,11 +564,16 @@ fn tick_every_rearms_repeatedly() {
|
||||
run(move || {
|
||||
let cw = Arc::new(Mutex::new(None));
|
||||
let server = start(timed(f2, cw));
|
||||
server.cast(TkCast::Tick(Duration::from_millis(20))).unwrap();
|
||||
server
|
||||
.cast(TkCast::Tick(Duration::from_millis(20)))
|
||||
.unwrap();
|
||||
let _ = server.call(()).unwrap(); // sync: periodic armed
|
||||
smarm::sleep(Duration::from_millis(130)); // ~6 periods
|
||||
let count = server.call(()).unwrap();
|
||||
assert!(count >= 3, "periodic should have re-armed several times, got {count}");
|
||||
assert!(
|
||||
count >= 3,
|
||||
"periodic should have re-armed several times, got {count}"
|
||||
);
|
||||
});
|
||||
// Every tick delivered the same payload.
|
||||
assert!(fired.lock().unwrap().iter().all(|&v| v == 9));
|
||||
@@ -568,7 +588,9 @@ fn cancel_stops_a_periodic() {
|
||||
let c2 = cancel_won.clone();
|
||||
run(move || {
|
||||
let server = start(timed(f2, c2));
|
||||
server.cast(TkCast::Tick(Duration::from_millis(20))).unwrap();
|
||||
server
|
||||
.cast(TkCast::Tick(Duration::from_millis(20)))
|
||||
.unwrap();
|
||||
let _ = server.call(()).unwrap();
|
||||
smarm::sleep(Duration::from_millis(70)); // a few ticks
|
||||
server.cast(TkCast::CancelLast).unwrap();
|
||||
@@ -616,11 +638,17 @@ fn idle_fires_repeatedly_on_quiet() {
|
||||
let idles = Arc::new(Mutex::new(0));
|
||||
let i2 = idles.clone();
|
||||
run(move || {
|
||||
let server = start(Idler { window: Duration::from_millis(25), idles: i2 });
|
||||
let server = start(Idler {
|
||||
window: Duration::from_millis(25),
|
||||
idles: i2,
|
||||
});
|
||||
smarm::sleep(Duration::from_millis(130)); // quiet ⇒ ~5 windows
|
||||
drop(server); // keep the server alive across the quiet span
|
||||
});
|
||||
assert!(*idles.lock().unwrap() >= 2, "idle should re-arm and fire several times");
|
||||
assert!(
|
||||
*idles.lock().unwrap() >= 2,
|
||||
"idle should re-arm and fire several times"
|
||||
);
|
||||
}
|
||||
|
||||
// Traffic within the window keeps idle from firing; only once the inbox goes
|
||||
@@ -632,7 +660,10 @@ fn traffic_resets_the_idle_window() {
|
||||
let before_quiet = Arc::new(Mutex::new(u32::MAX));
|
||||
let bq = before_quiet.clone();
|
||||
run(move || {
|
||||
let server = start(Idler { window: Duration::from_millis(60), idles: i2 });
|
||||
let server = start(Idler {
|
||||
window: Duration::from_millis(60),
|
||||
idles: i2,
|
||||
});
|
||||
// Poke every 25ms (< 60ms window) for ~100ms: each cast resets the
|
||||
// window before it can elapse.
|
||||
for _ in 0..4 {
|
||||
@@ -643,8 +674,15 @@ fn traffic_resets_the_idle_window() {
|
||||
smarm::sleep(Duration::from_millis(140)); // now genuinely quiet
|
||||
drop(server);
|
||||
});
|
||||
assert_eq!(*before_quiet.lock().unwrap(), 0, "steady traffic must suppress idle");
|
||||
assert!(*idles.lock().unwrap() >= 1, "idle fires once the inbox falls quiet");
|
||||
assert_eq!(
|
||||
*before_quiet.lock().unwrap(),
|
||||
0,
|
||||
"steady traffic must suppress idle"
|
||||
);
|
||||
assert!(
|
||||
*idles.lock().unwrap() >= 1,
|
||||
"idle fires once the inbox falls quiet"
|
||||
);
|
||||
}
|
||||
|
||||
// RFC 015 §4.7 — no armed timer survives loop exit. A server with a live
|
||||
@@ -658,15 +696,21 @@ fn no_timer_survives_exit() {
|
||||
let f_read = fired.clone();
|
||||
run(move || {
|
||||
let server = start(timed(f_server, Arc::new(Mutex::new(None))));
|
||||
server.cast(TkCast::Tick(Duration::from_millis(15))).unwrap();
|
||||
server
|
||||
.cast(TkCast::Tick(Duration::from_millis(15)))
|
||||
.unwrap();
|
||||
let _ = server.call(()).unwrap(); // sync: periodic armed
|
||||
smarm::sleep(Duration::from_millis(45)); // a couple of ticks
|
||||
let mon = smarm::monitor(server.pid());
|
||||
drop(server); // inbox closes → loop exits → guard drains timers
|
||||
// Clean Down ⇒ the loop returned without the no-leak assert aborting.
|
||||
// Clean Down ⇒ the loop returned without the no-leak assert aborting.
|
||||
assert!(mon.rx.recv().is_ok());
|
||||
let at_exit = f_read.lock().unwrap().len();
|
||||
smarm::sleep(Duration::from_millis(90)); // would be several more ticks
|
||||
assert_eq!(f_read.lock().unwrap().len(), at_exit, "no tick may fire after exit");
|
||||
assert_eq!(
|
||||
f_read.lock().unwrap().len(),
|
||||
at_exit,
|
||||
"no tick may fire after exit"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
+91
-15
@@ -100,7 +100,15 @@ fn state_timeout_fires() {
|
||||
let got = Arc::new(Mutex::new(0u32));
|
||||
let got2 = got.clone();
|
||||
run(move || {
|
||||
let m = TimerSm::start(T::Idle, TData { enters: 0, st_fires: 0, named_fires: 0, st_window: 5 });
|
||||
let m = TimerSm::start(
|
||||
T::Idle,
|
||||
TData {
|
||||
enters: 0,
|
||||
st_fires: 0,
|
||||
named_fires: 0,
|
||||
st_window: 5,
|
||||
},
|
||||
);
|
||||
m.send(Ev2::Cast(TCast::Arm)).unwrap(); // -> Armed, arms 5ms state-timeout
|
||||
smarm::sleep(Duration::from_millis(40)); // let it fire
|
||||
*got2.lock().unwrap() = m.call(|r| Ev2::Call(TCall::StFires(r))).unwrap();
|
||||
@@ -116,13 +124,25 @@ fn state_timeout_auto_resets_on_transition() {
|
||||
let got2 = got.clone();
|
||||
run(move || {
|
||||
// Long window so the explicit Disarm beats it comfortably.
|
||||
let m = TimerSm::start(T::Idle, TData { enters: 0, st_fires: 0, named_fires: 0, st_window: 50 });
|
||||
let m = TimerSm::start(
|
||||
T::Idle,
|
||||
TData {
|
||||
enters: 0,
|
||||
st_fires: 0,
|
||||
named_fires: 0,
|
||||
st_window: 50,
|
||||
},
|
||||
);
|
||||
m.send(Ev2::Cast(TCast::Arm)).unwrap(); // -> Armed, arms 50ms state-timeout
|
||||
m.send(Ev2::Cast(TCast::Disarm)).unwrap(); // -> Idle, auto-resets it
|
||||
smarm::sleep(Duration::from_millis(80)); // past the original window
|
||||
*got2.lock().unwrap() = m.call(|r| Ev2::Call(TCall::StFires(r))).unwrap();
|
||||
});
|
||||
assert_eq!(*got.lock().unwrap(), 0, "auto-reset cancelled the pending state-timeout");
|
||||
assert_eq!(
|
||||
*got.lock().unwrap(),
|
||||
0,
|
||||
"auto-reset cancelled the pending state-timeout"
|
||||
);
|
||||
}
|
||||
|
||||
// A named timeout survives a state change: armed in Idle, it still fires after
|
||||
@@ -133,14 +153,26 @@ fn named_timeout_survives_transition() {
|
||||
let got2 = got.clone();
|
||||
run(move || {
|
||||
// Armed's own state-timeout is long so it doesn't interfere.
|
||||
let m = TimerSm::start(T::Idle, TData { enters: 0, st_fires: 0, named_fires: 0, st_window: 200 });
|
||||
let m = TimerSm::start(
|
||||
T::Idle,
|
||||
TData {
|
||||
enters: 0,
|
||||
st_fires: 0,
|
||||
named_fires: 0,
|
||||
st_window: 200,
|
||||
},
|
||||
);
|
||||
m.send(Ev2::Cast(TCast::Ping(20))).unwrap(); // arm "ping" for 20ms (in Idle)
|
||||
m.send(Ev2::Cast(TCast::Arm)).unwrap(); // -> Armed (ping must survive this)
|
||||
m.send(Ev2::Cast(TCast::Disarm)).unwrap(); // -> Idle (and this)
|
||||
smarm::sleep(Duration::from_millis(60)); // let "ping" fire
|
||||
*got2.lock().unwrap() = m.call(|r| Ev2::Call(TCall::NamedFires(r))).unwrap();
|
||||
});
|
||||
assert_eq!(*got.lock().unwrap(), 1, "named timeout fired across the transitions");
|
||||
assert_eq!(
|
||||
*got.lock().unwrap(),
|
||||
1,
|
||||
"named timeout fired across the transitions"
|
||||
);
|
||||
}
|
||||
|
||||
// Cancelling a named timeout before its window prevents the fire.
|
||||
@@ -149,13 +181,25 @@ fn named_timeout_cancel() {
|
||||
let got = Arc::new(Mutex::new(99u32));
|
||||
let got2 = got.clone();
|
||||
run(move || {
|
||||
let m = TimerSm::start(T::Idle, TData { enters: 0, st_fires: 0, named_fires: 0, st_window: 200 });
|
||||
let m = TimerSm::start(
|
||||
T::Idle,
|
||||
TData {
|
||||
enters: 0,
|
||||
st_fires: 0,
|
||||
named_fires: 0,
|
||||
st_window: 200,
|
||||
},
|
||||
);
|
||||
m.send(Ev2::Cast(TCast::Ping(30))).unwrap(); // arm "ping" for 30ms
|
||||
m.send(Ev2::Cast(TCast::CancelPing)).unwrap(); // cancel before it fires
|
||||
smarm::sleep(Duration::from_millis(60)); // past the original window
|
||||
*got2.lock().unwrap() = m.call(|r| Ev2::Call(TCall::NamedFires(r))).unwrap();
|
||||
});
|
||||
assert_eq!(*got.lock().unwrap(), 0, "cancel prevented the named-timeout fire");
|
||||
assert_eq!(
|
||||
*got.lock().unwrap(),
|
||||
0,
|
||||
"cancel prevented the named-timeout fire"
|
||||
);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
@@ -170,15 +214,27 @@ fn cast_then_call_roundtrip() {
|
||||
let got2 = got.clone();
|
||||
run(move || {
|
||||
// Long state-timeout window so it never fires during the test.
|
||||
let m = TimerSm::start(T::Idle, TData { enters: 0, st_fires: 0, named_fires: 0, st_window: 10_000 });
|
||||
m.send(Ev2::Cast(TCast::Arm)).unwrap(); // Idle -> Armed (enter)
|
||||
let m = TimerSm::start(
|
||||
T::Idle,
|
||||
TData {
|
||||
enters: 0,
|
||||
st_fires: 0,
|
||||
named_fires: 0,
|
||||
st_window: 10_000,
|
||||
},
|
||||
);
|
||||
m.send(Ev2::Cast(TCast::Arm)).unwrap(); // Idle -> Armed (enter)
|
||||
m.send(Ev2::Cast(TCast::Disarm)).unwrap(); // Armed -> Idle (enter)
|
||||
m.send(Ev2::Cast(TCast::Arm)).unwrap(); // Idle -> Armed (enter)
|
||||
m.send(Ev2::Cast(TCast::Arm)).unwrap(); // Idle -> Armed (enter)
|
||||
m.send(Ev2::Cast(TCast::Disarm)).unwrap(); // Armed -> Idle (enter)
|
||||
// enters = 1 (start) + 4 transitions = 5.
|
||||
// enters = 1 (start) + 4 transitions = 5.
|
||||
*got2.lock().unwrap() = m.call(|r| Ev2::Call(TCall::Enters(r))).unwrap();
|
||||
});
|
||||
assert_eq!(*got.lock().unwrap(), 5, "one enter on start, one per real transition");
|
||||
assert_eq!(
|
||||
*got.lock().unwrap(),
|
||||
5,
|
||||
"one enter on start, one per real transition"
|
||||
);
|
||||
}
|
||||
|
||||
// `enter` fires once on start and once per *real* transition; a stay (a call
|
||||
@@ -188,7 +244,15 @@ fn enter_on_start_and_each_transition_but_not_stay() {
|
||||
let got = Arc::new(Mutex::new((0u32, 0u32, 0u32)));
|
||||
let got2 = got.clone();
|
||||
run(move || {
|
||||
let m = TimerSm::start(T::Idle, TData { enters: 0, st_fires: 0, named_fires: 0, st_window: 10_000 }); // enter -> 1
|
||||
let m = TimerSm::start(
|
||||
T::Idle,
|
||||
TData {
|
||||
enters: 0,
|
||||
st_fires: 0,
|
||||
named_fires: 0,
|
||||
st_window: 10_000,
|
||||
},
|
||||
); // enter -> 1
|
||||
let after_start = m.call(|r| Ev2::Call(TCall::Enters(r))).unwrap();
|
||||
// A stay (a counter read returns `prev`) must not bump enters.
|
||||
let _ = m.call(|r| Ev2::Call(TCall::StFires(r))).unwrap();
|
||||
@@ -208,7 +272,15 @@ fn call_to_panicking_handler_is_down() {
|
||||
let got = Arc::new(Mutex::new(None::<Result<u32, CallError>>));
|
||||
let got2 = got.clone();
|
||||
run(move || {
|
||||
let m = TimerSm::start(T::Idle, TData { enters: 0, st_fires: 0, named_fires: 0, st_window: 10_000 });
|
||||
let m = TimerSm::start(
|
||||
T::Idle,
|
||||
TData {
|
||||
enters: 0,
|
||||
st_fires: 0,
|
||||
named_fires: 0,
|
||||
st_window: 10_000,
|
||||
},
|
||||
);
|
||||
let r = m.call(|rep| Ev2::Call(TCall::Boom(rep)));
|
||||
*got2.lock().unwrap() = Some(r);
|
||||
});
|
||||
@@ -299,7 +371,11 @@ fn postponed_call_answered_after_transition() {
|
||||
smarm::sleep(Duration::from_millis(20)); // let the child wake with its reply
|
||||
*g2.lock().unwrap() = *taken.lock().unwrap();
|
||||
});
|
||||
assert_eq!(*got.lock().unwrap(), Some(42), "postponed call answered by the Filled state");
|
||||
assert_eq!(
|
||||
*got.lock().unwrap(),
|
||||
Some(42),
|
||||
"postponed call answered by the Filled state"
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
|
||||
+40
-9
@@ -56,7 +56,11 @@ fn snapshot_lists_actors_with_parent_edge() {
|
||||
|
||||
// The root itself is on-CPU (it's running this code) and rooted under
|
||||
// the forest sentinel.
|
||||
let root = snap.actors.iter().find(|a| a.pid == me).expect("root present");
|
||||
let root = snap
|
||||
.actors
|
||||
.iter()
|
||||
.find(|a| a.pid == me)
|
||||
.expect("root present");
|
||||
assert_eq!(root.state, ActorState::Running);
|
||||
assert_eq!(root.supervisor, smarm::Pid::new(u32::MAX, u32::MAX));
|
||||
|
||||
@@ -200,7 +204,11 @@ fn tree_places_child_under_its_spawner() {
|
||||
|
||||
// The root is parented at the forest sentinel, so it's a genuine root,
|
||||
// and the worker it spawned hangs beneath it.
|
||||
let root = t.roots.iter().find(|n| n.info.pid == me).expect("root in forest");
|
||||
let root = t
|
||||
.roots
|
||||
.iter()
|
||||
.find(|n| n.info.pid == me)
|
||||
.expect("root in forest");
|
||||
assert!(!root.orphaned);
|
||||
assert!(
|
||||
root.children.iter().any(|c| c.info.pid == h.pid()),
|
||||
@@ -258,14 +266,25 @@ fn tree_from_nests_children_and_reroots_orphans() {
|
||||
let t = tree_from(snap);
|
||||
assert_eq!(t.roots.len(), 2);
|
||||
|
||||
let root = t.roots.iter().find(|n| n.info.pid == root_pid).expect("root present");
|
||||
let root = t
|
||||
.roots
|
||||
.iter()
|
||||
.find(|n| n.info.pid == root_pid)
|
||||
.expect("root present");
|
||||
assert!(!root.orphaned);
|
||||
assert_eq!(root.children.len(), 1);
|
||||
assert_eq!(root.children[0].info.pid, child);
|
||||
assert!(!root.children[0].orphaned);
|
||||
|
||||
let o = t.roots.iter().find(|n| n.info.pid == orphan).expect("orphan re-rooted");
|
||||
assert!(o.orphaned, "an actor whose parent is absent must be flagged orphaned");
|
||||
let o = t
|
||||
.roots
|
||||
.iter()
|
||||
.find(|n| n.info.pid == orphan)
|
||||
.expect("orphan re-rooted");
|
||||
assert!(
|
||||
o.orphaned,
|
||||
"an actor whose parent is absent must be flagged orphaned"
|
||||
);
|
||||
assert!(o.children.is_empty());
|
||||
}
|
||||
|
||||
@@ -398,7 +417,11 @@ fn stack_info_reports_defaults_and_sampled_depth() {
|
||||
let info = spin_until(h.pid(), |a| a.state == ActorState::Parked);
|
||||
let s = info.stack;
|
||||
assert_eq!(s.reserve, 64 * 1024, "default reserve");
|
||||
assert_eq!(s.guard, 1024 * 1024, "default guard (kernel stack_guard_gap convention)");
|
||||
assert_eq!(
|
||||
s.guard,
|
||||
1024 * 1024,
|
||||
"default guard (kernel stack_guard_gap convention)"
|
||||
);
|
||||
assert!(
|
||||
s.depth_high_water >= 8 * 4096,
|
||||
"hwm sampled at the deep yield: expected ≥ 32 KiB, got {}",
|
||||
@@ -432,7 +455,10 @@ fn stack_info_shrink_counters_are_live() {
|
||||
let spike = 768 * 4096;
|
||||
assert!(spike > SHRINK_THRESHOLD);
|
||||
let worker = spawn_with(
|
||||
SpawnOpts { stack_reserve: Some(8 * 1024 * 1024), ..SpawnOpts::default() },
|
||||
SpawnOpts {
|
||||
stack_reserve: Some(8 * 1024 * 1024),
|
||||
..SpawnOpts::default()
|
||||
},
|
||||
move || {
|
||||
std::hint::black_box(burn_stack_yielding(768));
|
||||
for _ in 0..(SHRINK_COOLDOWN + 8) {
|
||||
@@ -457,9 +483,14 @@ fn stack_info_shrink_counters_are_live() {
|
||||
spin_until(wpid, |a| a.state == ActorState::Parked);
|
||||
park_tx.send(()).unwrap();
|
||||
}
|
||||
let info = spin_until(wpid, |a| a.state == ActorState::Parked && a.stack.shrinks >= 1);
|
||||
let info = spin_until(wpid, |a| {
|
||||
a.state == ActorState::Parked && a.stack.shrinks >= 1
|
||||
});
|
||||
let s = info.stack;
|
||||
assert!(s.shrinks >= 1, "cooldown was crossed with a spike above threshold");
|
||||
assert!(
|
||||
s.shrinks >= 1,
|
||||
"cooldown was crossed with a spike above threshold"
|
||||
);
|
||||
assert!(
|
||||
s.parks_since_shrink < SHRINK_COOLDOWN,
|
||||
"counter must reset at shrink: {}",
|
||||
|
||||
+13
-3
@@ -56,8 +56,16 @@ fn other_actors_run_while_block_on_io_is_in_flight() {
|
||||
let pos_2 = v.iter().position(|&x| x == 2).unwrap();
|
||||
let pos_3 = v.iter().position(|&x| x == 3).unwrap();
|
||||
let pos_4 = v.iter().position(|&x| x == 4).unwrap();
|
||||
assert!(pos_2 < pos_4, "B's first step ran after A resumed: {:?}", *v);
|
||||
assert!(pos_3 < pos_4, "B's second step ran after A resumed: {:?}", *v);
|
||||
assert!(
|
||||
pos_2 < pos_4,
|
||||
"B's first step ran after A resumed: {:?}",
|
||||
*v
|
||||
);
|
||||
assert!(
|
||||
pos_3 < pos_4,
|
||||
"B's second step ran after A resumed: {:?}",
|
||||
*v
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -76,7 +84,9 @@ fn many_concurrent_block_on_io_calls_all_complete() {
|
||||
cc.fetch_add(n, Ordering::SeqCst);
|
||||
}));
|
||||
}
|
||||
for h in handles { h.join().unwrap(); }
|
||||
for h in handles {
|
||||
h.join().unwrap();
|
||||
}
|
||||
});
|
||||
assert_eq!(counter.load(Ordering::SeqCst), 10);
|
||||
}
|
||||
|
||||
+11
-4
@@ -144,8 +144,7 @@ fn write_sugar_sends_bytes_to_pipe() {
|
||||
// Pipe is empty + has buffer space, so this returns immediately
|
||||
// after wait_writable wakes (which happens fast because the
|
||||
// kernel marks an empty pipe as immediately writable).
|
||||
let n = smarm::scheduler::write(p_writer.write, b"smarm")
|
||||
.expect("write failed");
|
||||
let n = smarm::scheduler::write(p_writer.write, b"smarm").expect("write failed");
|
||||
assert_eq!(n, 5);
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
@@ -209,10 +208,18 @@ fn other_actors_run_while_one_is_parked_on_wait_readable() {
|
||||
let pos_lit_a = v.iter().position(|&c| c == b'a').unwrap();
|
||||
let big_b_count = v.iter().filter(|&&c| c == b'B').count();
|
||||
assert_eq!(big_b_count, 3, "B should have made 3 steps: {:?}", *v);
|
||||
assert!(pos_big_a < pos_lit_a, "A pre-park before A post-park: {:?}", *v);
|
||||
assert!(
|
||||
pos_big_a < pos_lit_a,
|
||||
"A pre-park before A post-park: {:?}",
|
||||
*v
|
||||
);
|
||||
// At least the last B step should be before A resumes.
|
||||
let last_big_b = v.iter().rposition(|&c| c == b'B').unwrap();
|
||||
assert!(last_big_b < pos_lit_a, "B should finish before A resumes: {:?}", *v);
|
||||
assert!(
|
||||
last_big_b < pos_lit_a,
|
||||
"B should finish before A resumes: {:?}",
|
||||
*v
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
+10
-4
@@ -57,7 +57,10 @@ fn linked_pair_one_panics_other_is_stopped() {
|
||||
panic!("boom");
|
||||
});
|
||||
|
||||
let dn = down_b.rx.recv().expect("monitor channel closed before Down");
|
||||
let dn = down_b
|
||||
.rx
|
||||
.recv()
|
||||
.expect("monitor channel closed before Down");
|
||||
assert_eq!(dn.pid, b, "Down reported the wrong pid");
|
||||
if matches!(dn.reason, DownReason::Stopped) {
|
||||
s.store(true, Ordering::SeqCst);
|
||||
@@ -117,7 +120,7 @@ fn normal_exit_does_not_propagate() {
|
||||
let a = ha.pid();
|
||||
link(a);
|
||||
yield_now(); // let A run to completion and finalize
|
||||
// A exited normally: nothing should have landed on the inbox.
|
||||
// A exited normally: nothing should have landed on the inbox.
|
||||
if let Ok(None) = inbox.try_recv() {
|
||||
e.store(true, Ordering::SeqCst);
|
||||
}
|
||||
@@ -152,7 +155,10 @@ fn link_to_dead_pid_stops_a_nontrapping_caller() {
|
||||
});
|
||||
let b = hb.pid();
|
||||
let down_b = monitor(b);
|
||||
let dn = down_b.rx.recv().expect("monitor channel closed before Down");
|
||||
let dn = down_b
|
||||
.rx
|
||||
.recv()
|
||||
.expect("monitor channel closed before Down");
|
||||
if matches!(dn.reason, DownReason::Stopped) {
|
||||
s.store(true, Ordering::SeqCst);
|
||||
}
|
||||
@@ -208,7 +214,7 @@ fn unlink_prevents_propagation() {
|
||||
panic!("boom"); // abnormal, but the link is gone
|
||||
});
|
||||
yield_now(); // let A link, unlink, and panic
|
||||
// Unlinked before death → no ExitSignal should have arrived.
|
||||
// Unlinked before death → no ExitSignal should have arrived.
|
||||
if let Ok(None) = inbox.try_recv() {
|
||||
sv.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
+27
-6
@@ -67,7 +67,10 @@ fn monitor_already_dead_target_is_noproc() {
|
||||
// and its generation bumped, so `pid` is now stale.
|
||||
h.join().unwrap();
|
||||
let down = monitor(pid);
|
||||
let d = down.rx.recv().expect("NoProc Down should be delivered immediately");
|
||||
let d = down
|
||||
.rx
|
||||
.recv()
|
||||
.expect("NoProc Down should be delivered immediately");
|
||||
assert_eq!(d.pid, pid);
|
||||
if matches!(d.reason, DownReason::NoProc) {
|
||||
o.store(true, Ordering::SeqCst);
|
||||
@@ -91,7 +94,11 @@ fn multiple_monitors_all_notified() {
|
||||
}
|
||||
}
|
||||
});
|
||||
assert_eq!(count.load(Ordering::SeqCst), 3, "every monitor should see the Down");
|
||||
assert_eq!(
|
||||
count.load(Ordering::SeqCst),
|
||||
3,
|
||||
"every monitor should see the Down"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -103,8 +110,15 @@ fn demonitor_stops_delivery() {
|
||||
let h = spawn(|| {});
|
||||
let pid = h.pid();
|
||||
let m = monitor(pid);
|
||||
assert_eq!(demonitor(&m), Some(m.id), "live registration should be removed");
|
||||
assert!(m.rx.recv().is_err(), "no Down should arrive after demonitor");
|
||||
assert_eq!(
|
||||
demonitor(&m),
|
||||
Some(m.id),
|
||||
"live registration should be removed"
|
||||
);
|
||||
assert!(
|
||||
m.rx.recv().is_err(),
|
||||
"no Down should arrive after demonitor"
|
||||
);
|
||||
let _ = h.join();
|
||||
});
|
||||
}
|
||||
@@ -122,7 +136,10 @@ fn demonitor_one_of_many() {
|
||||
let _ = h.join();
|
||||
assert!(matches!(ms[0].rx.recv().unwrap().reason, DownReason::Exit));
|
||||
assert!(matches!(ms[2].rx.recv().unwrap().reason, DownReason::Exit));
|
||||
assert!(ms[1].rx.recv().is_err(), "demonitored channel should be closed");
|
||||
assert!(
|
||||
ms[1].rx.recv().is_err(),
|
||||
"demonitored channel should be closed"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -136,7 +153,11 @@ fn demonitor_after_fire_is_none() {
|
||||
let m = monitor(pid);
|
||||
let d = m.rx.recv().expect("Down before close");
|
||||
assert!(matches!(d.reason, DownReason::Exit));
|
||||
assert_eq!(demonitor(&m), None, "already-fired monitor has nothing to remove");
|
||||
assert_eq!(
|
||||
demonitor(&m),
|
||||
None,
|
||||
"already-fired monitor has nothing to remove"
|
||||
);
|
||||
let _ = h.join();
|
||||
});
|
||||
}
|
||||
|
||||
+16
-4
@@ -3,9 +3,9 @@
|
||||
//! needs to be able to park.
|
||||
|
||||
use smarm::{run, spawn, yield_now, LockTimeout, Mutex};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -111,8 +111,16 @@ fn contended_lock_parks_until_holder_releases() {
|
||||
let pos_b_locked = v.iter().position(|s| *s == "B_locked").unwrap();
|
||||
|
||||
assert!(pos_a_locked < pos_b_try, "log: {:?}", *v);
|
||||
assert!(pos_b_try < pos_a_dropped, "B should attempt before A drops: {:?}", *v);
|
||||
assert!(pos_a_dropped < pos_b_locked, "B should lock only after A drops: {:?}", *v);
|
||||
assert!(
|
||||
pos_b_try < pos_a_dropped,
|
||||
"B should attempt before A drops: {:?}",
|
||||
*v
|
||||
);
|
||||
assert!(
|
||||
pos_a_dropped < pos_b_locked,
|
||||
"B should lock only after A drops: {:?}",
|
||||
*v
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -209,7 +217,11 @@ fn waiters_are_granted_the_lock_in_fifo_order() {
|
||||
});
|
||||
|
||||
let v = order.lock().unwrap().clone();
|
||||
assert_eq!(v, vec![1, 2, 3, 4], "waiters should acquire in arrival order");
|
||||
assert_eq!(
|
||||
v,
|
||||
vec![1, 2, 3, 4],
|
||||
"waiters should acquire in arrival order"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
+5
-3
@@ -77,8 +77,7 @@ fn observer_reports_none_for_a_forged_pid() {
|
||||
// An index that is not in the slab at all — the verb relays the
|
||||
// primitive's `None` faithfully.
|
||||
let forged = smarm::Pid::new(u32::MAX - 1, 0);
|
||||
let ObserverReply::ActorInfo(none) =
|
||||
obs.call(ObserverRequest::ActorInfo(forged)).unwrap()
|
||||
let ObserverReply::ActorInfo(none) = obs.call(ObserverRequest::ActorInfo(forged)).unwrap()
|
||||
else {
|
||||
panic!("ActorInfo verb must reply ActorInfo");
|
||||
};
|
||||
@@ -113,7 +112,10 @@ fn observer_sees_a_parked_actor_as_parked() {
|
||||
}
|
||||
smarm::yield_now();
|
||||
}
|
||||
assert!(parked, "observer should eventually report the worker as Parked");
|
||||
assert!(
|
||||
parked,
|
||||
"observer should eventually report the worker as Parked"
|
||||
);
|
||||
|
||||
gate_tx.send(()).unwrap();
|
||||
worker.join().unwrap();
|
||||
|
||||
+13
-3
@@ -44,7 +44,10 @@ fn a_dead_actor_vanishes_from_every_group_it_joined() {
|
||||
// Drain-on-contact: touching g1 detects the death and sweeps the pid
|
||||
// out of every group (g2 included), not just g1.
|
||||
assert!(members("g1").is_empty(), "evicted from the touched group");
|
||||
assert!(members("g2").is_empty(), "and swept from the untouched group");
|
||||
assert!(
|
||||
members("g2").is_empty(),
|
||||
"and swept from the untouched group"
|
||||
);
|
||||
assert_eq!(pick("g1"), None);
|
||||
});
|
||||
}
|
||||
@@ -83,7 +86,11 @@ fn live_members_survive_a_peers_death() {
|
||||
tx_a.send(()).unwrap();
|
||||
a.join().unwrap();
|
||||
|
||||
assert_eq!(members("svc"), vec![b.pid()], "only the dead peer is reaped");
|
||||
assert_eq!(
|
||||
members("svc"),
|
||||
vec![b.pid()],
|
||||
"only the dead peer is reaped"
|
||||
);
|
||||
assert_eq!(pick("svc"), Some(b.pid()));
|
||||
|
||||
tx_b.send(()).unwrap();
|
||||
@@ -125,7 +132,10 @@ fn joining_an_already_dead_pid_is_evicted_on_next_contact() {
|
||||
// monitor() on a gone pid queues a NoProc Down immediately, so the
|
||||
// membership is reaped the next time the group is touched.
|
||||
join("late", pid);
|
||||
assert!(members("late").is_empty(), "dead-at-join member is reaped on read");
|
||||
assert!(
|
||||
members("late").is_empty(),
|
||||
"dead-at-join member is reaped on read"
|
||||
);
|
||||
assert_eq!(pick("late"), None);
|
||||
});
|
||||
}
|
||||
|
||||
+10
-2
@@ -41,7 +41,11 @@ fn stop_storm_does_not_poison_runtime() {
|
||||
}
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
assert_eq!(completed.load(Ordering::SeqCst), 1, "root completed cleanly");
|
||||
assert_eq!(
|
||||
completed.load(Ordering::SeqCst),
|
||||
1,
|
||||
"root completed cleanly"
|
||||
);
|
||||
}
|
||||
|
||||
/// The sharper repro: a stop-flagged actor whose *next allocation* is the
|
||||
@@ -85,5 +89,9 @@ fn self_stop_during_spawn_does_not_poison_shared_mutex() {
|
||||
}
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
assert_eq!(completed.load(Ordering::SeqCst), 1, "root completed cleanly");
|
||||
assert_eq!(
|
||||
completed.load(Ordering::SeqCst),
|
||||
1,
|
||||
"root completed cleanly"
|
||||
);
|
||||
}
|
||||
|
||||
+15
-4
@@ -43,10 +43,21 @@ fn check_yields_when_timeslice_expired() {
|
||||
let pos_big_b = v.iter().position(|&c| c == b'B').unwrap();
|
||||
let pos_lit_a = v.iter().position(|&c| c == b'a').unwrap();
|
||||
let pos_lit_b = v.iter().position(|&c| c == b'b').unwrap();
|
||||
assert!(pos_big_a < pos_lit_a, "A's tail ran before B's head: {:?}", *v);
|
||||
assert!(pos_big_b < pos_lit_b, "B's tail ran before A's head: {:?}", *v);
|
||||
assert!(pos_big_a.max(pos_big_b) < pos_lit_a.min(pos_lit_b),
|
||||
"preemption didn't interleave: {:?}", *v);
|
||||
assert!(
|
||||
pos_big_a < pos_lit_a,
|
||||
"A's tail ran before B's head: {:?}",
|
||||
*v
|
||||
);
|
||||
assert!(
|
||||
pos_big_b < pos_lit_b,
|
||||
"B's tail ran before A's head: {:?}",
|
||||
*v
|
||||
);
|
||||
assert!(
|
||||
pos_big_a.max(pos_big_b) < pos_lit_a.min(pos_lit_b),
|
||||
"preemption didn't interleave: {:?}",
|
||||
*v
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+13
-4
@@ -65,7 +65,10 @@ fn name_held_by_live_actor_is_taken() {
|
||||
ready_rx.recv().unwrap();
|
||||
// Root tries to claim a live actor's name for itself -> NameTaken.
|
||||
let (tx_b, _rx_b) = channel::<u64>();
|
||||
assert_eq!(register(SVC, tx_b), Err(RegisterError::NameTaken { holder: a.pid() }));
|
||||
assert_eq!(
|
||||
register(SVC, tx_b),
|
||||
Err(RegisterError::NameTaken { holder: a.pid() })
|
||||
);
|
||||
send(SVC, 0).unwrap(); // release a (delivers to the holder, a)
|
||||
a.join().unwrap();
|
||||
});
|
||||
@@ -105,7 +108,10 @@ fn dead_holder_is_pruned_and_name_taken_over() {
|
||||
fn send_errors_unresolved_and_no_channel() {
|
||||
run(|| {
|
||||
// No actor at all.
|
||||
assert!(matches!(send(Name::<u64>::new("ghost"), 1u64), Err(SendError::Unresolved(_))));
|
||||
assert!(matches!(
|
||||
send(Name::<u64>::new("ghost"), 1u64),
|
||||
Err(SendError::Unresolved(_))
|
||||
));
|
||||
|
||||
let (ready_tx, ready_rx) = channel::<()>();
|
||||
let (tx, rx) = channel::<u64>();
|
||||
@@ -227,8 +233,11 @@ fn send_dyn_delivers_and_reports_wrong_type() {
|
||||
ready_rx.recv().unwrap();
|
||||
let p = h.pid(); // a bare Pid<Erased>, as if recovered off a Down
|
||||
send_dyn::<u64>(p, 3u64).unwrap(); // right type: delivered
|
||||
// Live actor, but it has no channel for &str — the genuinely-fallible case.
|
||||
assert!(matches!(send_dyn::<&'static str>(p, "nope"), Err(SendError::NoChannel(_))));
|
||||
// Live actor, but it has no channel for &str — the genuinely-fallible case.
|
||||
assert!(matches!(
|
||||
send_dyn::<&'static str>(p, "nope"),
|
||||
Err(SendError::NoChannel(_))
|
||||
));
|
||||
done_tx.send(()).unwrap();
|
||||
h.join().unwrap();
|
||||
});
|
||||
|
||||
+77
-26
@@ -14,10 +14,17 @@
|
||||
//! - No slot leaks under high spawn/join churn
|
||||
//! - Panic on one scheduler thread doesn't kill others
|
||||
|
||||
use smarm::{channel, runtime::{Config, Runtime}, spawn, yield_now, JoinHandle};
|
||||
use std::sync::{atomic::{AtomicBool, AtomicU64, Ordering}, Arc};
|
||||
use std::time::Duration;
|
||||
use smarm::{
|
||||
channel,
|
||||
runtime::{Config, Runtime},
|
||||
spawn, yield_now, JoinHandle,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
Arc,
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -29,7 +36,9 @@ fn rt(n: usize) -> Runtime {
|
||||
}
|
||||
|
||||
/// Convenient single-threaded runtime (regression guard).
|
||||
fn rt1() -> Runtime { rt(1) }
|
||||
fn rt1() -> Runtime {
|
||||
rt(1)
|
||||
}
|
||||
|
||||
/// Multi-threaded runtime using all available parallelism.
|
||||
fn rt_par() -> Runtime {
|
||||
@@ -79,7 +88,9 @@ fn config_min_1_max_1_is_single_threaded() {
|
||||
fn runtime_run_executes_closure() {
|
||||
let flag = Arc::new(AtomicBool::new(false));
|
||||
let f = flag.clone();
|
||||
rt(1).run(move || { f.store(true, Ordering::SeqCst); });
|
||||
rt(1).run(move || {
|
||||
f.store(true, Ordering::SeqCst);
|
||||
});
|
||||
assert!(flag.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
@@ -111,8 +122,12 @@ fn runtime_can_be_used_multiple_times_sequentially() {
|
||||
let b = Arc::new(AtomicU64::new(0));
|
||||
let ac = a.clone();
|
||||
let bc = b.clone();
|
||||
r.run(move || { ac.fetch_add(1, Ordering::SeqCst); });
|
||||
r.run(move || { bc.fetch_add(1, Ordering::SeqCst); });
|
||||
r.run(move || {
|
||||
ac.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
r.run(move || {
|
||||
bc.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
assert_eq!(a.load(Ordering::SeqCst), 1);
|
||||
assert_eq!(b.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
@@ -126,7 +141,9 @@ fn exact_1_spawn_join_works() {
|
||||
let v = Arc::new(AtomicU64::new(0));
|
||||
let vc = v.clone();
|
||||
rt1().run(move || {
|
||||
let h = spawn(move || { vc.store(42, Ordering::SeqCst); });
|
||||
let h = spawn(move || {
|
||||
vc.store(42, Ordering::SeqCst);
|
||||
});
|
||||
h.join().unwrap();
|
||||
});
|
||||
assert_eq!(v.load(Ordering::SeqCst), 42);
|
||||
@@ -155,7 +172,9 @@ fn exact_1_panic_captured() {
|
||||
let s = saw_err.clone();
|
||||
rt1().run(move || {
|
||||
let h = spawn(|| panic!("oops"));
|
||||
if h.join().is_err() { s.store(true, Ordering::SeqCst); }
|
||||
if h.join().is_err() {
|
||||
s.store(true, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
assert!(saw_err.load(Ordering::SeqCst));
|
||||
}
|
||||
@@ -176,7 +195,9 @@ fn multi_thread_all_actors_complete() {
|
||||
cc.fetch_add(1, Ordering::SeqCst);
|
||||
}));
|
||||
}
|
||||
for h in handles { h.join().unwrap(); }
|
||||
for h in handles {
|
||||
h.join().unwrap();
|
||||
}
|
||||
});
|
||||
assert_eq!(counter.load(Ordering::SeqCst), 100);
|
||||
}
|
||||
@@ -221,7 +242,9 @@ fn multi_thread_many_channels_no_lost_wakeups() {
|
||||
tx.send(1).unwrap();
|
||||
}));
|
||||
}
|
||||
for h in handles { h.join().unwrap(); }
|
||||
for h in handles {
|
||||
h.join().unwrap();
|
||||
}
|
||||
});
|
||||
assert_eq!(count.load(Ordering::SeqCst), PAIRS as u64);
|
||||
}
|
||||
@@ -247,7 +270,9 @@ fn multi_thread_mutex_contention_no_deadlock() {
|
||||
}
|
||||
}));
|
||||
}
|
||||
for h in handles { h.join().unwrap(); }
|
||||
for h in handles {
|
||||
h.join().unwrap();
|
||||
}
|
||||
let g = m.lock_timeout(Duration::from_secs(1)).unwrap();
|
||||
t.store(*g, Ordering::SeqCst);
|
||||
});
|
||||
@@ -262,7 +287,9 @@ fn multi_thread_join_across_threads() {
|
||||
rt_par().run(move || {
|
||||
let h = spawn(move || {
|
||||
// Do some work to make scheduling interesting.
|
||||
for _ in 0..10 { yield_now(); }
|
||||
for _ in 0..10 {
|
||||
yield_now();
|
||||
}
|
||||
vc.store(1, Ordering::SeqCst);
|
||||
});
|
||||
h.join().unwrap();
|
||||
@@ -279,8 +306,7 @@ fn multi_thread_join_across_threads() {
|
||||
|
||||
#[test]
|
||||
fn actors_run_on_multiple_os_threads() {
|
||||
let thread_ids: Arc<smarm::Mutex<HashSet<u64>>> =
|
||||
Arc::new(smarm::Mutex::new(HashSet::new()));
|
||||
let thread_ids: Arc<smarm::Mutex<HashSet<u64>>> = Arc::new(smarm::Mutex::new(HashSet::new()));
|
||||
|
||||
rt_par().run({
|
||||
let ids = thread_ids.clone();
|
||||
@@ -294,11 +320,15 @@ fn actors_run_on_multiple_os_threads() {
|
||||
g.insert(tid);
|
||||
}));
|
||||
}
|
||||
for h in handles { h.join().unwrap(); }
|
||||
for h in handles {
|
||||
h.join().unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let n = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
|
||||
let n = std::thread::available_parallelism()
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(1);
|
||||
|
||||
let ids = thread_ids.lock_timeout(Duration::from_secs(1)).unwrap();
|
||||
// If we have >1 scheduler threads, we expect >1 OS thread IDs.
|
||||
@@ -326,11 +356,17 @@ fn scheduler_stats_run_queue_len_is_observable() {
|
||||
// run() completes (queue len == 0 at quiescence).
|
||||
let r = rt_par();
|
||||
r.run(|| {
|
||||
for _ in 0..10 { spawn(|| {}); }
|
||||
for _ in 0..10 {
|
||||
spawn(|| {});
|
||||
}
|
||||
// Don't join — let them drain naturally.
|
||||
});
|
||||
let stats = r.stats();
|
||||
assert_eq!(stats.total_run_queue_len(), 0, "queue should be empty after run()");
|
||||
assert_eq!(
|
||||
stats.total_run_queue_len(),
|
||||
0,
|
||||
"queue should be empty after run()"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -359,7 +395,9 @@ fn panic_in_actor_does_not_kill_runtime() {
|
||||
}));
|
||||
}
|
||||
let _ = bad.join(); // expect Err
|
||||
for h in good_handles { h.join().unwrap(); }
|
||||
for h in good_handles {
|
||||
h.join().unwrap();
|
||||
}
|
||||
});
|
||||
assert_eq!(completed.load(Ordering::SeqCst), 10);
|
||||
}
|
||||
@@ -379,9 +417,11 @@ fn no_slot_leak_under_churn() {
|
||||
rt_par().run(move || {
|
||||
for _ in 0..500 {
|
||||
let cc = c.clone();
|
||||
spawn(move || { cc.fetch_add(1, Ordering::SeqCst); })
|
||||
.join()
|
||||
.unwrap();
|
||||
spawn(move || {
|
||||
cc.fetch_add(1, Ordering::SeqCst);
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
assert_eq!(counter.load(Ordering::SeqCst), 500);
|
||||
@@ -474,7 +514,11 @@ fn multi_thread_timer_only_no_pipe_contention() {
|
||||
}
|
||||
});
|
||||
|
||||
assert_eq!(count.load(Ordering::SeqCst), ACTORS as u64, "not all actors completed");
|
||||
assert_eq!(
|
||||
count.load(Ordering::SeqCst),
|
||||
ACTORS as u64,
|
||||
"not all actors completed"
|
||||
);
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
assert!(
|
||||
@@ -515,7 +559,10 @@ fn runtime_reusable_after_root_panic() {
|
||||
let ran = Arc::new(AtomicBool::new(false));
|
||||
let ran_t = ran.clone();
|
||||
r.run(move || ran_t.store(true, Ordering::Relaxed));
|
||||
assert!(ran.load(Ordering::Relaxed), "runtime unusable after root panic");
|
||||
assert!(
|
||||
ran.load(Ordering::Relaxed),
|
||||
"runtime unusable after root panic"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -528,7 +575,11 @@ fn runtime_reusable_after_root_panic() {
|
||||
fn burn_stack(frames: usize) -> u64 {
|
||||
let mut local = [0u8; 4096];
|
||||
local[0] = frames as u8;
|
||||
let below = if frames == 0 { 0 } else { burn_stack(frames - 1) };
|
||||
let below = if frames == 0 {
|
||||
0
|
||||
} else {
|
||||
burn_stack(frames - 1)
|
||||
};
|
||||
std::hint::black_box(&mut local);
|
||||
below.wrapping_add(local[0] as u64)
|
||||
}
|
||||
|
||||
+7
-4
@@ -14,7 +14,9 @@ use std::sync::Arc;
|
||||
fn root_actor_runs() {
|
||||
let captured = Arc::new(AtomicI64::new(0));
|
||||
let c = captured.clone();
|
||||
run(move || { c.store(99, Ordering::SeqCst); });
|
||||
run(move || {
|
||||
c.store(99, Ordering::SeqCst);
|
||||
});
|
||||
assert_eq!(captured.load(Ordering::SeqCst), 99);
|
||||
}
|
||||
|
||||
@@ -27,7 +29,9 @@ fn spawn_and_join_returns_exit() {
|
||||
let captured = Arc::new(AtomicI64::new(0));
|
||||
let c = captured.clone();
|
||||
run(move || {
|
||||
let h = spawn(move || { c.store(7, Ordering::SeqCst); });
|
||||
let h = spawn(move || {
|
||||
c.store(7, Ordering::SeqCst);
|
||||
});
|
||||
let res = h.join();
|
||||
assert!(res.is_ok(), "join returned {:?}", res);
|
||||
});
|
||||
@@ -68,8 +72,7 @@ fn yield_now_interleaves_actors() {
|
||||
|
||||
#[test]
|
||||
fn self_pid_is_stable_within_an_actor() {
|
||||
let pid_cell: Arc<std::sync::Mutex<Option<smarm::Pid>>> =
|
||||
Arc::new(std::sync::Mutex::new(None));
|
||||
let pid_cell: Arc<std::sync::Mutex<Option<smarm::Pid>>> = Arc::new(std::sync::Mutex::new(None));
|
||||
let p2 = pid_cell.clone();
|
||||
run(move || {
|
||||
let h = spawn(move || {
|
||||
|
||||
+14
-3
@@ -19,7 +19,12 @@ fn ready_arm_returns_immediately_without_parking() {
|
||||
txa.send(42).unwrap();
|
||||
let i = select(&[&rxb, &rxa]);
|
||||
assert_eq!(i, 1);
|
||||
out2.store(rxa.try_recv().unwrap().expect("ready arm must hold a message"), Ordering::SeqCst);
|
||||
out2.store(
|
||||
rxa.try_recv()
|
||||
.unwrap()
|
||||
.expect("ready arm must hold a message"),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
});
|
||||
assert_eq!(out.load(Ordering::SeqCst), 42);
|
||||
}
|
||||
@@ -276,7 +281,10 @@ fn select_timeout_ready_arm_wins_without_arming_a_timer() {
|
||||
let (txa, rxa) = channel::<i64>();
|
||||
let (_keep_b, rxb) = channel::<i64>();
|
||||
txa.send(5).unwrap();
|
||||
assert_eq!(select_timeout(&[&rxb, &rxa], Duration::from_millis(500)), Some(1));
|
||||
assert_eq!(
|
||||
select_timeout(&[&rxb, &rxa], Duration::from_millis(500)),
|
||||
Some(1)
|
||||
);
|
||||
assert_eq!(rxa.try_recv().unwrap(), Some(5));
|
||||
});
|
||||
}
|
||||
@@ -342,7 +350,10 @@ fn select_timeout_closed_arm_is_ready_not_a_timeout() {
|
||||
let (_keep_a, rxa) = channel::<i64>();
|
||||
let (txb, rxb) = channel::<i64>();
|
||||
drop(txb);
|
||||
assert_eq!(select_timeout(&[&rxa, &rxb], Duration::from_millis(200)), Some(1));
|
||||
assert_eq!(
|
||||
select_timeout(&[&rxa, &rxb], Duration::from_millis(200)),
|
||||
Some(1)
|
||||
);
|
||||
assert!(rxb.try_recv().is_err());
|
||||
});
|
||||
}
|
||||
|
||||
+34
-11
@@ -6,9 +6,7 @@
|
||||
//! big reserve behaviorally takes effect (deep recursion completes).
|
||||
|
||||
use smarm::runtime::{Config, DEFAULT_STACK_GUARD, DEFAULT_STACK_RESERVE};
|
||||
use smarm::{
|
||||
self_pid, spawn, spawn_under_with, spawn_with, GenServerBuilder, SpawnOpts,
|
||||
};
|
||||
use smarm::{self_pid, spawn, spawn_under_with, spawn_with, GenServerBuilder, SpawnOpts};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -45,7 +43,10 @@ fn spawn_with_overrides_reserve_and_guard() {
|
||||
#[test]
|
||||
fn spawn_with_partial_override_keeps_config_default_for_the_rest() {
|
||||
rt1().run(|| {
|
||||
let opts = SpawnOpts { stack_reserve: Some(1024 * 1024), ..SpawnOpts::default() };
|
||||
let opts = SpawnOpts {
|
||||
stack_reserve: Some(1024 * 1024),
|
||||
..SpawnOpts::default()
|
||||
};
|
||||
let h = spawn_with(opts, || {
|
||||
let shape = smarm::introspect::stack_shape(self_pid()).unwrap();
|
||||
assert_eq!(shape, (1024 * 1024, DEFAULT_STACK_GUARD));
|
||||
@@ -57,7 +58,10 @@ fn spawn_with_partial_override_keeps_config_default_for_the_rest() {
|
||||
#[test]
|
||||
fn spawn_with_rounds_to_pages() {
|
||||
rt1().run(|| {
|
||||
let opts = SpawnOpts { stack_reserve: Some(64 * 1024 + 1), guard_size: Some(4097) };
|
||||
let opts = SpawnOpts {
|
||||
stack_reserve: Some(64 * 1024 + 1),
|
||||
guard_size: Some(4097),
|
||||
};
|
||||
let h = spawn_with(opts, || {
|
||||
let (reserve, guard) = smarm::introspect::stack_shape(self_pid()).unwrap();
|
||||
assert_eq!(reserve % 4096, 0);
|
||||
@@ -73,7 +77,10 @@ fn spawn_with_rounds_to_pages() {
|
||||
fn spawn_under_with_takes_opts() {
|
||||
rt1().run(|| {
|
||||
let me = self_pid();
|
||||
let opts = SpawnOpts { stack_reserve: Some(128 * 1024), ..SpawnOpts::default() };
|
||||
let opts = SpawnOpts {
|
||||
stack_reserve: Some(128 * 1024),
|
||||
..SpawnOpts::default()
|
||||
};
|
||||
let h = spawn_under_with(me, opts, || {
|
||||
let (reserve, _) = smarm::introspect::stack_shape(self_pid()).unwrap();
|
||||
assert_eq!(reserve, 128 * 1024);
|
||||
@@ -90,7 +97,10 @@ fn spawn_under_with_takes_opts() {
|
||||
fn custom_stack_never_enters_the_pool() {
|
||||
rt1().run(|| {
|
||||
spawn_with(
|
||||
SpawnOpts { stack_reserve: Some(512 * 1024), guard_size: Some(128 * 1024) },
|
||||
SpawnOpts {
|
||||
stack_reserve: Some(512 * 1024),
|
||||
guard_size: Some(128 * 1024),
|
||||
},
|
||||
|| {},
|
||||
)
|
||||
.join()
|
||||
@@ -122,7 +132,11 @@ fn default_stack_is_recycled() {
|
||||
fn burn_stack(frames: usize) -> u64 {
|
||||
let mut local = [0u8; 4096];
|
||||
local[0] = frames as u8;
|
||||
let below = if frames == 0 { 0 } else { burn_stack(frames - 1) };
|
||||
let below = if frames == 0 {
|
||||
0
|
||||
} else {
|
||||
burn_stack(frames - 1)
|
||||
};
|
||||
std::hint::black_box(&mut local);
|
||||
below.wrapping_add(local[0] as u64)
|
||||
}
|
||||
@@ -134,7 +148,10 @@ fn big_reserve_behaviorally_takes_effect() {
|
||||
let done = Arc::new(AtomicBool::new(false));
|
||||
let done2 = done.clone();
|
||||
spawn_with(
|
||||
SpawnOpts { stack_reserve: Some(8 * 1024 * 1024), ..SpawnOpts::default() },
|
||||
SpawnOpts {
|
||||
stack_reserve: Some(8 * 1024 * 1024),
|
||||
..SpawnOpts::default()
|
||||
},
|
||||
move || {
|
||||
std::hint::black_box(burn_stack(256));
|
||||
done2.store(true, Ordering::SeqCst);
|
||||
@@ -167,7 +184,10 @@ impl smarm::GenServer for Echo {
|
||||
fn gen_server_builder_stack_opts() {
|
||||
rt1().run(|| {
|
||||
let server = GenServerBuilder::new(Echo)
|
||||
.stack_opts(SpawnOpts { stack_reserve: Some(256 * 1024), ..SpawnOpts::default() })
|
||||
.stack_opts(SpawnOpts {
|
||||
stack_reserve: Some(256 * 1024),
|
||||
..SpawnOpts::default()
|
||||
})
|
||||
.start();
|
||||
let (reserve, guard) = server.call(()).unwrap();
|
||||
assert_eq!(reserve, 256 * 1024);
|
||||
@@ -200,7 +220,10 @@ impl smarm::Machine for Probe {
|
||||
fn gen_statem_spawn_with_stack_opts() {
|
||||
rt1().run(|| {
|
||||
let m = smarm::gen_statem::spawn_with(
|
||||
SpawnOpts { stack_reserve: Some(256 * 1024), ..SpawnOpts::default() },
|
||||
SpawnOpts {
|
||||
stack_reserve: Some(256 * 1024),
|
||||
..SpawnOpts::default()
|
||||
},
|
||||
Probe,
|
||||
);
|
||||
let (tx, rx) = smarm::channel::channel();
|
||||
|
||||
+30
-6
@@ -78,14 +78,18 @@ fn run_as_child_if_requested() {
|
||||
Ok("wide_guard_top") => {
|
||||
// One byte below the usable region, 64 KiB guard: must fault.
|
||||
let s = Stack::new(64 * 1024, 64 * 1024).unwrap();
|
||||
unsafe { s.usable_base().sub(1).write_volatile(0xAB); }
|
||||
unsafe {
|
||||
s.usable_base().sub(1).write_volatile(0xAB);
|
||||
}
|
||||
std::process::exit(0);
|
||||
}
|
||||
Ok("wide_guard_bottom") => {
|
||||
// The very bottom page of a 64 KiB guard: an unprobed C-style
|
||||
// leap over a small guard lands here — must still fault.
|
||||
let s = Stack::new(64 * 1024, 64 * 1024).unwrap();
|
||||
unsafe { s.usable_base().sub(64 * 1024).write_volatile(0xAB); }
|
||||
unsafe {
|
||||
s.usable_base().sub(64 * 1024).write_volatile(0xAB);
|
||||
}
|
||||
std::process::exit(0);
|
||||
}
|
||||
Ok("stack_overflow") => {
|
||||
@@ -120,7 +124,12 @@ fn guard_page_causes_sigsegv() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
assert_eq!(status.signal(), Some(11), "expected SIGSEGV, got: {:?}", status);
|
||||
assert_eq!(
|
||||
status.signal(),
|
||||
Some(11),
|
||||
"expected SIGSEGV, got: {:?}",
|
||||
status
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +140,12 @@ fn stack_overflow_causes_sigsegv() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
assert_eq!(status.signal(), Some(11), "expected SIGSEGV, got: {:?}", status);
|
||||
assert_eq!(
|
||||
status.signal(),
|
||||
Some(11),
|
||||
"expected SIGSEGV, got: {:?}",
|
||||
status
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +189,12 @@ fn wide_guard_faults_at_top() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
assert_eq!(status.signal(), Some(11), "expected SIGSEGV, got: {:?}", status);
|
||||
assert_eq!(
|
||||
status.signal(),
|
||||
Some(11),
|
||||
"expected SIGSEGV, got: {:?}",
|
||||
status
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +205,11 @@ fn wide_guard_faults_at_bottom() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
assert_eq!(status.signal(), Some(11), "expected SIGSEGV, got: {:?}", status);
|
||||
assert_eq!(
|
||||
status.signal(),
|
||||
Some(11),
|
||||
"expected SIGSEGV, got: {:?}",
|
||||
status
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-4
@@ -42,9 +42,15 @@ fn run_as_child_if_requested() {
|
||||
let opts = match mode.as_str() {
|
||||
"rust_overflow" | "ffi_tier1" => SpawnOpts::default(),
|
||||
// Small guard: the canary's 96 KiB displacement clears it.
|
||||
"ffi_tier2" => SpawnOpts { guard_size: Some(4096), ..SpawnOpts::default() },
|
||||
"ffi_tier2" => SpawnOpts {
|
||||
guard_size: Some(4096),
|
||||
..SpawnOpts::default()
|
||||
},
|
||||
// Enough reserve: the same frame simply fits.
|
||||
"ffi_clean" => SpawnOpts { stack_reserve: Some(256 * 1024), ..SpawnOpts::default() },
|
||||
"ffi_clean" => SpawnOpts {
|
||||
stack_reserve: Some(256 * 1024),
|
||||
..SpawnOpts::default()
|
||||
},
|
||||
other => panic!("unknown subtest {other}"),
|
||||
};
|
||||
let is_rust = mode == "rust_overflow";
|
||||
@@ -92,8 +98,14 @@ fn rust_overflow_dies_with_tier1_message() {
|
||||
err.contains("overflowed its stack") && err.contains("in the guard region"),
|
||||
"missing tier-1 diagnostic; stderr:\n{err}"
|
||||
);
|
||||
assert!(err.contains("reserve=65536"), "wrong reserve in message:\n{err}");
|
||||
assert!(err.contains("guard=1048576"), "wrong guard in message:\n{err}");
|
||||
assert!(
|
||||
err.contains("reserve=65536"),
|
||||
"wrong reserve in message:\n{err}"
|
||||
);
|
||||
assert!(
|
||||
err.contains("guard=1048576"),
|
||||
"wrong guard in message:\n{err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+16
-6
@@ -24,7 +24,11 @@ const RESERVE: usize = 4 * 1024 * 1024;
|
||||
fn burn_stack(frames: usize) -> u64 {
|
||||
let mut local = [0u8; 4096];
|
||||
local[0] = frames as u8;
|
||||
let below = if frames == 0 { 0 } else { burn_stack(frames - 1) };
|
||||
let below = if frames == 0 {
|
||||
0
|
||||
} else {
|
||||
burn_stack(frames - 1)
|
||||
};
|
||||
std::hint::black_box(&mut local);
|
||||
below.wrapping_add(local[0] as u64)
|
||||
}
|
||||
@@ -33,10 +37,13 @@ fn burn_stack(frames: usize) -> u64 {
|
||||
fn resident_pages(lo: usize, len: usize) -> usize {
|
||||
let page = 4096;
|
||||
let mut vec = vec![0u8; len / page];
|
||||
let ret = unsafe {
|
||||
libc::mincore(lo as *mut libc::c_void, len, vec.as_mut_ptr())
|
||||
};
|
||||
assert_eq!(ret, 0, "mincore failed: {}", std::io::Error::last_os_error());
|
||||
let ret = unsafe { libc::mincore(lo as *mut libc::c_void, len, vec.as_mut_ptr()) };
|
||||
assert_eq!(
|
||||
ret,
|
||||
0,
|
||||
"mincore failed: {}",
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
vec.iter().filter(|&&b| b & 1 != 0).count()
|
||||
}
|
||||
|
||||
@@ -118,6 +125,9 @@ fn recycle_zaps_dead_stack_down_to_retain() {
|
||||
[{usable_base:#x}, +{zap_len:#x})"
|
||||
);
|
||||
// Pooled, not munmapped: the mapping must still be there.
|
||||
assert!(vma_exists(usable_base), "default-shaped stack was unmapped instead of pooled");
|
||||
assert!(
|
||||
vma_exists(usable_base),
|
||||
"default-shaped stack was unmapped instead of pooled"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
+12
-3
@@ -66,7 +66,10 @@ fn spike_then_parks_marks_lazyfree_and_keeps_live_data() {
|
||||
assert!(spike > SHRINK_THRESHOLD);
|
||||
|
||||
let worker = spawn_with(
|
||||
SpawnOpts { stack_reserve: Some(8 * 1024 * 1024), ..SpawnOpts::default() },
|
||||
SpawnOpts {
|
||||
stack_reserve: Some(8 * 1024 * 1024),
|
||||
..SpawnOpts::default()
|
||||
},
|
||||
move || {
|
||||
// Live data that must survive the shrink, and an anchor
|
||||
// address inside the stack for the smaps scan.
|
||||
@@ -111,7 +114,11 @@ fn spike_then_parks_marks_lazyfree_and_keeps_live_data() {
|
||||
"expected ≥ 2 MiB LazyFree in the stack range, got {} bytes",
|
||||
lazy
|
||||
);
|
||||
assert_eq!(checksum, 64 * 0xA5u64, "live stack data corrupted by shrink");
|
||||
assert_eq!(
|
||||
checksum,
|
||||
64 * 0xA5u64,
|
||||
"live stack data corrupted by shrink"
|
||||
);
|
||||
worker.join().unwrap();
|
||||
});
|
||||
}
|
||||
@@ -131,7 +138,9 @@ fn shallow_actor_never_shrinks() {
|
||||
for _ in 0..(SHRINK_COOLDOWN + 8) {
|
||||
park_rx.recv().unwrap();
|
||||
}
|
||||
done_tx.send(lazy_free_bytes_in(anchor - 64 * 1024, anchor + 4096)).unwrap();
|
||||
done_tx
|
||||
.send(lazy_free_bytes_in(anchor - 64 * 1024, anchor + 4096))
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let wpid = worker.pid();
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
//! registry entry guarantees for every named server.
|
||||
|
||||
use smarm::{
|
||||
call, channel, init, request_stop, spawn, Config, GenServer, GenServerBuilder, GenServerName,
|
||||
CallError, Receiver, RecvTimeoutError,
|
||||
call, channel, init, request_stop, spawn, CallError, Config, GenServer, GenServerBuilder,
|
||||
GenServerName, Receiver, RecvTimeoutError,
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
@@ -73,10 +73,12 @@ fn named_server_request_stop_releases_queued_caller_with_server_down() {
|
||||
let (res_tx, res_rx) = channel::<Result<(), CallError>>();
|
||||
|
||||
// 1. Start the named server and keep its ref alive.
|
||||
let server = GenServerBuilder::new(Blocker { gate: Some(gate_rx) })
|
||||
.named(BLOCKER)
|
||||
.start()
|
||||
.expect("name should be free");
|
||||
let server = GenServerBuilder::new(Blocker {
|
||||
gate: Some(gate_rx),
|
||||
})
|
||||
.named(BLOCKER)
|
||||
.start()
|
||||
.expect("name should be free");
|
||||
let spid = server.pid();
|
||||
|
||||
// 2. Send the cast and let the server dequeue it and park on the gate.
|
||||
|
||||
+16
-8
@@ -10,7 +10,11 @@
|
||||
//! out rather than produce a false pass — run with `cargo test -- --timeout`
|
||||
//! or under a CI timeout.
|
||||
|
||||
use smarm::{channel, runtime::{Config, Runtime}, spawn, yield_now, JoinHandle};
|
||||
use smarm::{
|
||||
channel,
|
||||
runtime::{Config, Runtime},
|
||||
spawn, yield_now, JoinHandle,
|
||||
};
|
||||
use std::sync::{
|
||||
atomic::{AtomicU64, AtomicUsize, Ordering},
|
||||
Arc,
|
||||
@@ -199,7 +203,9 @@ fn thundering_herd_all_wake() {
|
||||
}
|
||||
|
||||
// Let all receivers park before we send.
|
||||
for _ in 0..4 { yield_now(); }
|
||||
for _ in 0..4 {
|
||||
yield_now();
|
||||
}
|
||||
|
||||
// Coordinator blasts all channels.
|
||||
handles.push(spawn(move || {
|
||||
@@ -240,8 +246,7 @@ fn concurrent_spawn_join_churn() {
|
||||
for _ in 0..PARENTS {
|
||||
let tc = t.clone();
|
||||
parent_handles.push(spawn(move || {
|
||||
let mut child_handles: Vec<JoinHandle> =
|
||||
Vec::with_capacity(CHILDREN_PER_PARENT);
|
||||
let mut child_handles: Vec<JoinHandle> = Vec::with_capacity(CHILDREN_PER_PARENT);
|
||||
|
||||
for _ in 0..CHILDREN_PER_PARENT {
|
||||
let tcc = tc.clone();
|
||||
@@ -292,7 +297,9 @@ fn join_race_child_finishes_first() {
|
||||
}
|
||||
|
||||
// Yield enough to let children run to completion before we join.
|
||||
for _ in 0..8 { yield_now(); }
|
||||
for _ in 0..8 {
|
||||
yield_now();
|
||||
}
|
||||
|
||||
for h in handles {
|
||||
// If child already finished, join must return immediately with Ok.
|
||||
@@ -374,8 +381,7 @@ fn panic_storm_does_not_corrupt_scheduler() {
|
||||
fn pid_generation_increments_on_reuse() {
|
||||
use smarm::self_pid;
|
||||
|
||||
let pids: Arc<smarm::Mutex<Vec<smarm::Pid>>> =
|
||||
Arc::new(smarm::Mutex::new(Vec::new()));
|
||||
let pids: Arc<smarm::Mutex<Vec<smarm::Pid>>> = Arc::new(smarm::Mutex::new(Vec::new()));
|
||||
|
||||
let p = pids.clone();
|
||||
rt(1).run(move || {
|
||||
@@ -392,7 +398,9 @@ fn pid_generation_increments_on_reuse() {
|
||||
}
|
||||
});
|
||||
|
||||
let g = pids.lock_timeout(std::time::Duration::from_secs(1)).unwrap();
|
||||
let g = pids
|
||||
.lock_timeout(std::time::Duration::from_secs(1))
|
||||
.unwrap();
|
||||
// Any two PIDs that share an index must have different generations.
|
||||
for i in 0..g.len() {
|
||||
for j in (i + 1)..g.len() {
|
||||
|
||||
+10
-2
@@ -51,7 +51,11 @@ fn transient_child_is_restarted_on_panic_then_settles() {
|
||||
});
|
||||
sup.join().unwrap();
|
||||
});
|
||||
assert_eq!(runs.load(Ordering::SeqCst), 3, "two restarts then a clean exit");
|
||||
assert_eq!(
|
||||
runs.load(Ordering::SeqCst),
|
||||
3,
|
||||
"two restarts then a clean exit"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -167,7 +171,11 @@ fn one_for_all_restarts_a_normally_exited_sibling() {
|
||||
sup.join().unwrap();
|
||||
});
|
||||
assert_eq!(a.load(Ordering::SeqCst), 2, "A: crash then clean run");
|
||||
assert_eq!(b.load(Ordering::SeqCst), 2, "B cycled with the group despite a clean exit");
|
||||
assert_eq!(
|
||||
b.load(Ordering::SeqCst),
|
||||
2,
|
||||
"B cycled with the group despite a clean exit"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
//! caller's deliberate act, not a semantics change.
|
||||
|
||||
use smarm::{
|
||||
init, mark_watchable, request_stop, resolve_name, terminal_reason, CallError, Config, DownReason, GenServer,
|
||||
GenServerBuilder, GenServerName, NameResolution,
|
||||
init, mark_watchable, request_stop, resolve_name, terminal_reason, CallError, Config,
|
||||
DownReason, GenServer, GenServerBuilder, GenServerName, NameResolution,
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -35,7 +35,10 @@ impl PipePair {
|
||||
let mut fds: [libc::c_int; 2] = [0; 2];
|
||||
let r = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC | libc::O_NONBLOCK) };
|
||||
assert_eq!(r, 0, "pipe2 failed");
|
||||
PipePair { read: fds[0], write: fds[1] }
|
||||
PipePair {
|
||||
read: fds[0],
|
||||
write: fds[1],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +70,9 @@ fn run_with_watchdog(limit: Duration, body: impl FnOnce() + Send + 'static) {
|
||||
rt.run(body);
|
||||
let _ = done_tx.send(());
|
||||
});
|
||||
done_rx
|
||||
.recv_timeout(limit)
|
||||
.expect("Runtime::run did not return: idle scheduler thread was never woken at termination");
|
||||
done_rx.recv_timeout(limit).expect(
|
||||
"Runtime::run did not return: idle scheduler thread was never woken at termination",
|
||||
);
|
||||
}
|
||||
|
||||
/// Permanent-hang variant: sibling blocked in `poll_wake(wake_fd, None)`
|
||||
|
||||
+30
-10
@@ -166,14 +166,19 @@ fn timers_only_pop_entries_whose_deadline_has_passed() {
|
||||
#[test]
|
||||
fn timers_mix_sleep_and_wait_timeout_reasons() {
|
||||
let mut t = Timers::new();
|
||||
let target = Arc::new(RecordingTarget { calls: Mutex::new(Vec::new()) });
|
||||
let target = Arc::new(RecordingTarget {
|
||||
calls: Mutex::new(Vec::new()),
|
||||
});
|
||||
let now = Instant::now();
|
||||
|
||||
t.insert_sleep(now + Duration::from_millis(5), Pid::new(0, 0), 1);
|
||||
t.insert(
|
||||
now + Duration::from_millis(10),
|
||||
Pid::new(1, 0),
|
||||
Reason::WaitTimeout { target: target.clone(), epoch: 42 },
|
||||
Reason::WaitTimeout {
|
||||
target: target.clone(),
|
||||
epoch: 42,
|
||||
},
|
||||
);
|
||||
|
||||
let due = t.pop_due(now + Duration::from_millis(20));
|
||||
@@ -238,7 +243,10 @@ fn armed_send_timer_is_returned_and_fires() {
|
||||
|
||||
let mut due = t.pop_due(now + Duration::from_millis(20));
|
||||
assert_eq!(due.len(), 1, "an armed send timer should pop when due");
|
||||
assert!(!fired.load(Ordering::SeqCst), "pop must not fire on its own");
|
||||
assert!(
|
||||
!fired.load(Ordering::SeqCst),
|
||||
"pop must not fire on its own"
|
||||
);
|
||||
run_fire(due.pop().unwrap());
|
||||
assert!(fired.load(Ordering::SeqCst), "running the thunk delivers");
|
||||
assert!(t.is_empty());
|
||||
@@ -282,7 +290,11 @@ fn cancel_after_fire_returns_false() {
|
||||
fn cancel_unknown_id_returns_false() {
|
||||
let mut t = Timers::new();
|
||||
let now = Instant::now();
|
||||
let id = t.insert_send(now + Duration::from_millis(5), Pid::new(0, 0), Box::new(|| {}));
|
||||
let id = t.insert_send(
|
||||
now + Duration::from_millis(5),
|
||||
Pid::new(0, 0),
|
||||
Box::new(|| {}),
|
||||
);
|
||||
assert!(t.cancel(id));
|
||||
// Second cancel of the same id: already gone.
|
||||
assert!(!t.cancel(id));
|
||||
@@ -293,7 +305,11 @@ fn send_timers_interleave_with_sleep_in_deadline_order() {
|
||||
let mut t = Timers::new();
|
||||
let now = Instant::now();
|
||||
t.insert_sleep(now + Duration::from_millis(30), Pid::new(0, 0), 1);
|
||||
let _id = t.insert_send(now + Duration::from_millis(10), Pid::new(1, 0), Box::new(|| {}));
|
||||
let _id = t.insert_send(
|
||||
now + Duration::from_millis(10),
|
||||
Pid::new(1, 0),
|
||||
Box::new(|| {}),
|
||||
);
|
||||
t.insert_sleep(now + Duration::from_millis(20), Pid::new(2, 0), 1);
|
||||
|
||||
let due = t.pop_due(now + Duration::from_millis(50));
|
||||
@@ -308,7 +324,11 @@ fn send_timers_interleave_with_sleep_in_deadline_order() {
|
||||
fn clear_drops_armed_send_timers() {
|
||||
let mut t = Timers::new();
|
||||
let now = Instant::now();
|
||||
let id = t.insert_send(now + Duration::from_millis(10), Pid::new(0, 0), Box::new(|| {}));
|
||||
let id = t.insert_send(
|
||||
now + Duration::from_millis(10),
|
||||
Pid::new(0, 0),
|
||||
Box::new(|| {}),
|
||||
);
|
||||
t.clear();
|
||||
assert!(t.is_empty());
|
||||
// The arm record is gone too: cancelling reports nothing to cancel.
|
||||
@@ -356,7 +376,7 @@ fn send_after_to_unresolved_name_is_silent() {
|
||||
// Nobody registered NOPE; firing resolves to nothing and is dropped.
|
||||
let _id = send_after_named(Duration::from_millis(10), NOPE, 1);
|
||||
sleep(Duration::from_millis(40)); // let it fire and no-op
|
||||
// Reaching here without a panic is the assertion.
|
||||
// Reaching here without a panic is the assertion.
|
||||
});
|
||||
}
|
||||
|
||||
@@ -401,9 +421,9 @@ fn send_after_to_dead_typed_pid_is_silent() {
|
||||
assert_eq!(report_rx.recv().unwrap(), 1); // sink has now exited
|
||||
let _id = send_after(Duration::from_millis(15), sink, 2);
|
||||
sleep(Duration::from_millis(45)); // let it fire against the dead pid
|
||||
// No panic; the sink is gone, so its report sender dropped with it —
|
||||
// closed+empty is Err (documented), which also proves nothing
|
||||
// further was delivered.
|
||||
// No panic; the sink is gone, so its report sender dropped with it —
|
||||
// closed+empty is Err (documented), which also proves nothing
|
||||
// further was delivered.
|
||||
assert!(report_rx.try_recv().is_err(), "nothing further delivered");
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user