diff --git a/benches/general.rs b/benches/general.rs index 01af305..0da2be1 100644 --- a/benches/general.rs +++ b/benches/general.rs @@ -26,7 +26,9 @@ use std::time::Instant; const ITERS: u32 = 15; fn available_threads() -> usize { - std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1) + std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(1) } fn env_sets() -> u32 { @@ -108,17 +110,15 @@ fn bench_chained_smarm(threads: usize) -> (u64, u128) { fn bench_chained_tokio_current() -> (u64, u128) { let counter = Arc::new(AtomicU64::new(0)); let c2 = counter.clone(); - let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let start = Instant::now(); let local = tokio::task::LocalSet::new(); local.block_on(&rt, async move { // Use a oneshot done channel like tokio's own chained_spawn bench. let (done_tx, done_rx) = tokio::sync::oneshot::channel(); - fn iter( - c: Arc, - done: tokio::sync::oneshot::Sender<()>, - n: u64, - ) { + fn iter(c: Arc, done: tokio::sync::oneshot::Sender<()>, n: u64) { if n == 0 { let _ = done.send(()); } else { @@ -186,7 +186,9 @@ fn bench_yield_smarm(threads: usize) -> (u64, u128) { } fn bench_yield_tokio_current() -> (u64, u128) { - let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let start = Instant::now(); let local = tokio::task::LocalSet::new(); local.block_on(&rt, async move { @@ -235,11 +237,22 @@ const PRIME_N: u64 = 400_000; const PRIME_WORKERS: u64 = 64; fn is_prime(n: u64) -> bool { - if n < 2 { return false; } - if n < 4 { return true; } - if n % 2 == 0 { return false; } + if n < 2 { + return false; + } + if n < 4 { + return true; + } + if n % 2 == 0 { + return false; + } let mut i = 3u64; - while i * i <= n { if n % i == 0 { return false; } i += 2; } + while i * i <= n { + if n % i == 0 { + return false; + } + i += 2; + } true } @@ -250,7 +263,11 @@ fn count_primes(lo: u64, hi: u64) -> u64 { fn primes_slice(w: u64) -> (u64, u64) { let per = PRIME_N / PRIME_WORKERS; let lo = w * per; - let hi = if w + 1 == PRIME_WORKERS { PRIME_N } else { lo + per }; + let hi = if w + 1 == PRIME_WORKERS { + PRIME_N + } else { + lo + per + }; (lo, hi) } @@ -267,7 +284,9 @@ fn bench_primes_smarm(threads: usize) -> (u64, u128) { tc.fetch_add(count_primes(lo, hi), Ordering::Relaxed); })); } - for h in handles { h.join().unwrap(); } + for h in handles { + h.join().unwrap(); + } }); (total.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -275,7 +294,9 @@ fn bench_primes_smarm(threads: usize) -> (u64, u128) { fn bench_primes_tokio_current() -> (u64, u128) { let total = Arc::new(AtomicU64::new(0)); let t2 = total.clone(); - let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let start = Instant::now(); let local = tokio::task::LocalSet::new(); local.block_on(&rt, async move { @@ -287,7 +308,9 @@ fn bench_primes_tokio_current() -> (u64, u128) { tc.fetch_add(count_primes(lo, hi), Ordering::Relaxed); })); } - for h in handles { let _ = h.await; } + for h in handles { + let _ = h.await; + } }); (total.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -309,7 +332,9 @@ fn bench_primes_tokio_multi() -> (u64, u128) { tc.fetch_add(count_primes(lo, hi), Ordering::Relaxed); })); } - for h in handles { let _ = h.await; } + for h in handles { + let _ = h.await; + } }); (total.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -344,7 +369,9 @@ fn bench_pp_smarm(threads: usize) -> (u64, u128) { } fn bench_pp_tokio_current() -> (u64, u128) { - let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let start = Instant::now(); let local = tokio::task::LocalSet::new(); local.block_on(&rt, async move { @@ -395,7 +422,6 @@ fn bench_pp_tokio_multi() -> (u64, u128) { // main // --------------------------------------------------------------------------- - // --------------------------------------------------------------------------- // Knob helper — reads SMARM_ALLOC_INTERVAL / SMARM_TIMESLICE_CYCLES env vars // so the sweep script can override the preemption knobs without recompiling. @@ -404,10 +430,14 @@ fn bench_pp_tokio_multi() -> (u64, u128) { fn bench_cfg(threads: usize) -> smarm::runtime::Config { let mut cfg = smarm::runtime::Config::exact(threads); if let Ok(v) = std::env::var("SMARM_ALLOC_INTERVAL") { - if let Ok(n) = v.parse::() { cfg = cfg.alloc_interval(n); } + if let Ok(n) = v.parse::() { + cfg = cfg.alloc_interval(n); + } } if let Ok(v) = std::env::var("SMARM_TIMESLICE_CYCLES") { - if let Ok(n) = v.parse::() { cfg = cfg.timeslice_cycles(n); } + if let Ok(n) = v.parse::() { + cfg = cfg.timeslice_cycles(n); + } } cfg } @@ -417,7 +447,10 @@ fn main() { println!("smarm general benchmarks"); println!("available parallelism: {n} threads"); let sets = env_sets(); - println!("ITERS={ITERS}×{sets} sets = {} samples (+1 warmup, discarded)", ITERS * sets); + println!( + "ITERS={ITERS}×{sets} sets = {} samples (+1 warmup, discarded)", + ITERS * sets + ); println!( "CHAIN_DEPTH={CHAIN_DEPTH}, YIELD_TASKS={YIELD_TASKS}×{YIELD_ROUNDS}, \ PRIME_N={PRIME_N}/{PRIME_WORKERS} workers, PP_ROUNDS={PP_ROUNDS}" @@ -426,21 +459,29 @@ fn main() { // ---- 1. chained_spawn ---- print_header(&format!("chained_spawn: depth {CHAIN_DEPTH}")); run_n("smarm 1-thread", ITERS, || bench_chained_smarm(1)); - run_n(&format!("smarm {n}-thread"), ITERS, || bench_chained_smarm(n)); + run_n(&format!("smarm {n}-thread"), ITERS, || { + bench_chained_smarm(n) + }); run_n("tokio current_thread", ITERS, bench_chained_tokio_current); run_n("tokio multi-thread", ITERS, bench_chained_tokio_multi); // ---- 2. yield_many ---- - print_header(&format!("yield_many: {YIELD_TASKS} tasks × {YIELD_ROUNDS} yields")); + print_header(&format!( + "yield_many: {YIELD_TASKS} tasks × {YIELD_ROUNDS} yields" + )); run_n("smarm 1-thread", ITERS, || bench_yield_smarm(1)); run_n(&format!("smarm {n}-thread"), ITERS, || bench_yield_smarm(n)); run_n("tokio current_thread", ITERS, bench_yield_tokio_current); run_n("tokio multi-thread", ITERS, bench_yield_tokio_multi); // ---- 3. fan_out_compute ---- - print_header(&format!("fan_out_compute: primes in [2, {PRIME_N}) across {PRIME_WORKERS}")); + print_header(&format!( + "fan_out_compute: primes in [2, {PRIME_N}) across {PRIME_WORKERS}" + )); run_n("smarm 1-thread", ITERS, || bench_primes_smarm(1)); - run_n(&format!("smarm {n}-thread"), ITERS, || bench_primes_smarm(n)); + run_n(&format!("smarm {n}-thread"), ITERS, || { + bench_primes_smarm(n) + }); run_n("tokio current_thread", ITERS, bench_primes_tokio_current); run_n("tokio multi-thread", ITERS, bench_primes_tokio_multi); diff --git a/benches/multi_scheduler.rs b/benches/multi_scheduler.rs index 5771e73..2d4f9b5 100644 --- a/benches/multi_scheduler.rs +++ b/benches/multi_scheduler.rs @@ -64,11 +64,22 @@ const PRIME_N: u64 = 400_000; const WORKERS: u64 = 64; fn is_prime(n: u64) -> bool { - if n < 2 { return false; } - if n < 4 { return true; } - if n % 2 == 0 { return false; } + if n < 2 { + return false; + } + if n < 4 { + return true; + } + if n % 2 == 0 { + return false; + } let mut i = 3u64; - while i * i <= n { if n % i == 0 { return false; } i += 2; } + while i * i <= n { + if n % i == 0 { + return false; + } + i += 2; + } true } @@ -96,7 +107,9 @@ fn bench_primes_smarm(threads: usize) -> (u64, u128) { tc.fetch_add(count_primes(lo, hi), Ordering::Relaxed); })); } - for h in handles { h.join().unwrap(); } + for h in handles { + h.join().unwrap(); + } }); (total.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -104,7 +117,9 @@ fn bench_primes_smarm(threads: usize) -> (u64, u128) { fn bench_primes_tokio_current() -> (u64, u128) { let total = Arc::new(AtomicU64::new(0)); let t2 = total.clone(); - let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let start = Instant::now(); let local = tokio::task::LocalSet::new(); local.block_on(&rt, async move { @@ -116,7 +131,9 @@ fn bench_primes_tokio_current() -> (u64, u128) { tc.fetch_add(count_primes(lo, hi), Ordering::Relaxed); })); } - for h in handles { let _ = h.await; } + for h in handles { + let _ = h.await; + } }); (total.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -138,17 +155,21 @@ fn bench_primes_tokio_multi() -> (u64, u128) { tc.fetch_add(count_primes(lo, hi), Ordering::Relaxed); })); } - for h in handles { let _ = h.await; } + for h in handles { + let _ = h.await; + } }); (total.load(Ordering::Relaxed), start.elapsed().as_micros()) } fn bench_primes_baseline() -> (u64, u128) { let start = Instant::now(); - let total: u64 = (0..WORKERS).map(|w| { - let (lo, hi) = primes_slice(w); - count_primes(lo, hi) - }).sum(); + let total: u64 = (0..WORKERS) + .map(|w| { + let (lo, hi) = primes_slice(w); + count_primes(lo, hi) + }) + .sum(); (total, start.elapsed().as_micros()) } @@ -167,15 +188,17 @@ fn bench_pingpong_smarm(threads: usize) -> (u64, u128) { tx_a.send(0).unwrap(); loop { let v = rx_b.recv().unwrap(); - if v >= PING_ROUNDS { break; } + if v >= PING_ROUNDS { + break; + } tx_a.send(v + 1).unwrap(); } }); - let hb = smarm::spawn(move || { - loop { - let v = rx_a.recv().unwrap(); - tx_b.send(v + 1).unwrap(); - if v + 1 >= PING_ROUNDS { break; } + let hb = smarm::spawn(move || loop { + let v = rx_a.recv().unwrap(); + tx_b.send(v + 1).unwrap(); + if v + 1 >= PING_ROUNDS { + break; } }); ha.join().unwrap(); @@ -198,7 +221,9 @@ fn bench_pingpong_tokio_current() -> (u64, u128) { tx_a.send(0).unwrap(); loop { let v = rx_b.recv().await.unwrap(); - if v >= PING_ROUNDS { break; } + if v >= PING_ROUNDS { + break; + } tx_a.send(v + 1).unwrap(); } }); @@ -206,7 +231,9 @@ fn bench_pingpong_tokio_current() -> (u64, u128) { loop { let v = rx_a.recv().await.unwrap(); tx_b.send(v + 1).unwrap(); - if v + 1 >= PING_ROUNDS { break; } + if v + 1 >= PING_ROUNDS { + break; + } } }); let _ = ha.await; @@ -229,7 +256,9 @@ fn bench_pingpong_tokio_multi() -> (u64, u128) { tx_a.send(0).unwrap(); loop { let v = rx_b.recv().await.unwrap(); - if v >= PING_ROUNDS { break; } + if v >= PING_ROUNDS { + break; + } tx_a.send(v + 1).unwrap(); } }); @@ -237,7 +266,9 @@ fn bench_pingpong_tokio_multi() -> (u64, u128) { loop { let v = rx_a.recv().await.unwrap(); tx_b.send(v + 1).unwrap(); - if v + 1 >= PING_ROUNDS { break; } + if v + 1 >= PING_ROUNDS { + break; + } } }); let _ = ha.await; @@ -264,7 +295,9 @@ fn bench_spawn_smarm(threads: usize) -> (u64, u128) { cc.fetch_add(1, Ordering::Relaxed); })); } - for h in handles { h.join().unwrap(); } + for h in handles { + h.join().unwrap(); + } }); (counter.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -272,7 +305,9 @@ fn bench_spawn_smarm(threads: usize) -> (u64, u128) { fn bench_spawn_tokio_current() -> (u64, u128) { let counter = Arc::new(AtomicU64::new(0)); let c = counter.clone(); - let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let start = Instant::now(); let local = tokio::task::LocalSet::new(); local.block_on(&rt, async move { @@ -283,7 +318,9 @@ fn bench_spawn_tokio_current() -> (u64, u128) { cc.fetch_add(1, Ordering::Relaxed); })); } - for h in handles { let _ = h.await; } + for h in handles { + let _ = h.await; + } }); (counter.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -304,7 +341,9 @@ fn bench_spawn_tokio_multi() -> (u64, u128) { cc.fetch_add(1, Ordering::Relaxed); })); } - for h in handles { let _ = h.await; } + for h in handles { + let _ = h.await; + } }); (counter.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -320,24 +359,34 @@ fn main() { println!("PRIME_N={PRIME_N}, WORKERS={WORKERS}, PING_ROUNDS={PING_ROUNDS}, SPAWN_COUNT={SPAWN_COUNT}"); // ---- Primes ---- - print_header(&format!("Fan-out/fan-in: count primes in [2, {PRIME_N}) across {WORKERS} workers")); - run_n("baseline (serial)", ITERS, bench_primes_baseline); - run_n("smarm single-thread", ITERS, || bench_primes_smarm(1)); - run_n(&format!("smarm {n}-thread"), ITERS, || bench_primes_smarm(n)); - run_n("tokio current_thread", ITERS, bench_primes_tokio_current); - run_n("tokio multi-thread", ITERS, bench_primes_tokio_multi); + print_header(&format!( + "Fan-out/fan-in: count primes in [2, {PRIME_N}) across {WORKERS} workers" + )); + run_n("baseline (serial)", ITERS, bench_primes_baseline); + run_n("smarm single-thread", ITERS, || bench_primes_smarm(1)); + run_n(&format!("smarm {n}-thread"), ITERS, || { + bench_primes_smarm(n) + }); + run_n("tokio current_thread", ITERS, bench_primes_tokio_current); + run_n("tokio multi-thread", ITERS, bench_primes_tokio_multi); // ---- Ping-pong ---- - print_header(&format!("Ping-pong: {PING_ROUNDS} round-trips between two actors")); - run_n("smarm single-thread", ITERS, || bench_pingpong_smarm(1)); - run_n(&format!("smarm {n}-thread"), ITERS, || bench_pingpong_smarm(n)); - run_n("tokio current_thread", ITERS, bench_pingpong_tokio_current); - run_n("tokio multi-thread", ITERS, bench_pingpong_tokio_multi); + print_header(&format!( + "Ping-pong: {PING_ROUNDS} round-trips between two actors" + )); + run_n("smarm single-thread", ITERS, || bench_pingpong_smarm(1)); + run_n(&format!("smarm {n}-thread"), ITERS, || { + bench_pingpong_smarm(n) + }); + run_n("tokio current_thread", ITERS, bench_pingpong_tokio_current); + run_n("tokio multi-thread", ITERS, bench_pingpong_tokio_multi); // ---- Spawn throughput ---- - print_header(&format!("Spawn throughput: {SPAWN_COUNT} actors spawned and joined")); - run_n("smarm single-thread", ITERS, || bench_spawn_smarm(1)); + print_header(&format!( + "Spawn throughput: {SPAWN_COUNT} actors spawned and joined" + )); + run_n("smarm single-thread", ITERS, || bench_spawn_smarm(1)); run_n(&format!("smarm {n}-thread"), ITERS, || bench_spawn_smarm(n)); - run_n("tokio current_thread", ITERS, bench_spawn_tokio_current); - run_n("tokio multi-thread", ITERS, bench_spawn_tokio_multi); + run_n("tokio current_thread", ITERS, bench_spawn_tokio_current); + run_n("tokio multi-thread", ITERS, bench_spawn_tokio_multi); } diff --git a/benches/primes.rs b/benches/primes.rs index 7431e87..9d2ae97 100644 --- a/benches/primes.rs +++ b/benches/primes.rs @@ -16,12 +16,20 @@ const WORKERS: u64 = 16; const ITERATIONS: u32 = 5; fn is_prime(n: u64) -> bool { - if n < 2 { return false; } - if n < 4 { return true; } - if n % 2 == 0 { return false; } + if n < 2 { + return false; + } + if n < 4 { + return true; + } + if n % 2 == 0 { + return false; + } let mut i = 3u64; while i * i <= n { - if n % i == 0 { return false; } + if n % i == 0 { + return false; + } i += 2; } true @@ -30,7 +38,9 @@ fn is_prime(n: u64) -> bool { fn count_primes_in(lo: u64, hi: u64) -> u64 { let mut count = 0u64; for n in lo..hi { - if is_prime(n) { count += 1; } + if is_prime(n) { + count += 1; + } } count } @@ -38,7 +48,11 @@ fn count_primes_in(lo: u64, hi: u64) -> u64 { fn slice(worker: u64) -> (u64, u64) { let per = N / WORKERS; let lo = worker * per; - let hi = if worker + 1 == WORKERS { N } else { (worker + 1) * per }; + let hi = if worker + 1 == WORKERS { + N + } else { + (worker + 1) * per + }; (lo, hi) } @@ -125,7 +139,10 @@ fn main() { "Counting primes in [2, {}) across {} workers, {} iterations each\n", N, WORKERS, ITERATIONS ); - println!("{:>12} | {:>15} | {:>16} | {:>15} | {:>15}", "runtime", "primes found", "median", "min", "max"); + println!( + "{:>12} | {:>15} | {:>16} | {:>15} | {:>15}", + "runtime", "primes found", "median", "min", "max" + ); println!("{}", "-".repeat(80)); run_n("baseline", ITERATIONS, bench_baseline); diff --git a/benches/rq_micro.rs b/benches/rq_micro.rs index 4b76d35..4b6150a 100644 --- a/benches/rq_micro.rs +++ b/benches/rq_micro.rs @@ -27,12 +27,19 @@ use std::sync::Arc; use std::time::Instant; fn env_usize(key: &str, default: usize) -> usize { - std::env::var(key).ok().and_then(|v| v.parse().ok()).unwrap_or(default) + std::env::var(key) + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(default) } fn env_threads() -> Vec { std::env::var("SMARM_BENCH_THREADS") - .map(|v| v.split_whitespace().filter_map(|t| t.parse().ok()).collect()) + .map(|v| { + v.split_whitespace() + .filter_map(|t| t.parse().ok()) + .collect() + }) .unwrap_or_else(|_| vec![1, 2, 4]) } @@ -53,7 +60,11 @@ fn drive( for p in 0..producers { let q = q.clone(); // Give the last producer the remainder. - let n = if p == producers - 1 { items - per * (producers - 1) } else { per }; + let n = if p == producers - 1 { + items - per * (producers - 1) + } else { + per + }; hs.push(std::thread::spawn(move || { let pid = Pid::new(p as u32, 0); for _ in 0..n { @@ -132,7 +143,12 @@ fn main() { for &t in &threads_sweep { for (p, c) in ratios_for(t) { for s in ["mutex", "mpmc", "striped"] { - cases.push(Case { structure: s, threads: t, producers: p, consumers: c }); + cases.push(Case { + structure: s, + threads: t, + producers: p, + consumers: c, + }); } } } @@ -147,7 +163,14 @@ fn main() { if case.threads < 2 { drive_single(&*q, MutexQueue::push, MutexQueue::pop, items) } else { - drive(q, MutexQueue::push, MutexQueue::pop, case.producers, case.consumers, items) + drive( + q, + MutexQueue::push, + MutexQueue::pop, + case.producers, + case.consumers, + items, + ) } } "mpmc" => { @@ -155,7 +178,14 @@ fn main() { if case.threads < 2 { drive_single(&*q, MpmcRing::push, MpmcRing::pop, items) } else { - drive(q, MpmcRing::push, MpmcRing::pop, case.producers, case.consumers, items) + drive( + q, + MpmcRing::push, + MpmcRing::pop, + case.producers, + case.consumers, + items, + ) } } "striped" => { @@ -163,7 +193,14 @@ fn main() { if case.threads < 2 { drive_single(&*q, StripedRing::push, StripedRing::pop, items) } else { - drive(q, StripedRing::push, StripedRing::pop, case.producers, case.consumers, items) + drive( + q, + StripedRing::push, + StripedRing::pop, + case.producers, + case.consumers, + items, + ) } } _ => unreachable!(), diff --git a/benches/rq_runtime.rs b/benches/rq_runtime.rs index fef71d6..64f2cca 100644 --- a/benches/rq_runtime.rs +++ b/benches/rq_runtime.rs @@ -54,12 +54,19 @@ fn variant() -> &'static str { } fn env_usize(key: &str, default: usize) -> usize { - std::env::var(key).ok().and_then(|v| v.parse().ok()).unwrap_or(default) + std::env::var(key) + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(default) } fn env_threads() -> Vec { std::env::var("SMARM_BENCH_THREADS") - .map(|v| v.split_whitespace().filter_map(|t| t.parse().ok()).collect()) + .map(|v| { + v.split_whitespace() + .filter_map(|t| t.parse().ok()) + .collect() + }) .unwrap_or_else(|_| vec![1, 2, 4]) } @@ -238,12 +245,22 @@ fn main() { ); println!( "RQCSV,runtime,{},{},{},{},{},{},{}", - variant(), slot_str, name, t, work, mid.us, per_s + variant(), + slot_str, + name, + t, + work, + mid.us, + per_s ); if slot { println!( "RQSLOT,{},{},{},{},{}", - variant(), name, t, mid.hits, mid.displacements + variant(), + name, + t, + mid.hits, + mid.displacements ); } } diff --git a/benches/smarm_favored.rs b/benches/smarm_favored.rs index e1bda29..95fd38e 100644 --- a/benches/smarm_favored.rs +++ b/benches/smarm_favored.rs @@ -37,7 +37,9 @@ use std::time::Instant; const ITERS: u32 = 15; fn available_threads() -> usize { - std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1) + std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(1) } fn env_sets() -> u32 { @@ -116,7 +118,9 @@ fn bench_recurse_smarm(threads: usize) -> (u64, u128) { fn bench_recurse_tokio_current() -> (u64, u128) { let counter = Arc::new(AtomicU64::new(0)); let c2 = counter.clone(); - let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let start = Instant::now(); let local = tokio::task::LocalSet::new(); local.block_on(&rt, async move { @@ -199,7 +203,9 @@ fn bench_hot_smarm() -> (u64, u128) { } fn bench_hot_tokio_current() -> (u64, u128) { - let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let start = Instant::now(); let local = tokio::task::LocalSet::new(); local.block_on(&rt, async move { @@ -249,7 +255,9 @@ fn bench_unc_smarm() -> (u64, u128) { } fn bench_unc_tokio_current() -> (u64, u128) { - let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let start = Instant::now(); let local = tokio::task::LocalSet::new(); local.block_on(&rt, async move { @@ -297,8 +305,12 @@ fn bench_panic_smarm(threads: usize) -> (u64, u128) { } for h in handles { match h.join() { - Ok(()) => { ok2.fetch_add(1, Ordering::Relaxed); } - Err(_) => { err2.fetch_add(1, Ordering::Relaxed); } + Ok(()) => { + ok2.fetch_add(1, Ordering::Relaxed); + } + Err(_) => { + err2.fetch_add(1, Ordering::Relaxed); + } } } }); @@ -312,7 +324,9 @@ fn bench_panic_tokio_current() -> (u64, u128) { let err = Arc::new(AtomicU64::new(0)); let ok2 = ok.clone(); let err2 = err.clone(); - let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let prev_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(|_| {})); let start = Instant::now(); @@ -328,8 +342,12 @@ fn bench_panic_tokio_current() -> (u64, u128) { } for h in handles { match h.await { - Ok(()) => { ok2.fetch_add(1, Ordering::Relaxed); } - Err(_) => { err2.fetch_add(1, Ordering::Relaxed); } + Ok(()) => { + ok2.fetch_add(1, Ordering::Relaxed); + } + Err(_) => { + err2.fetch_add(1, Ordering::Relaxed); + } } } }); @@ -361,8 +379,12 @@ fn bench_panic_tokio_multi() -> (u64, u128) { } for h in handles { match h.await { - Ok(()) => { ok2.fetch_add(1, Ordering::Relaxed); } - Err(_) => { err2.fetch_add(1, Ordering::Relaxed); } + Ok(()) => { + ok2.fetch_add(1, Ordering::Relaxed); + } + Err(_) => { + err2.fetch_add(1, Ordering::Relaxed); + } } } }); @@ -375,7 +397,6 @@ fn bench_panic_tokio_multi() -> (u64, u128) { // main // --------------------------------------------------------------------------- - // --------------------------------------------------------------------------- // Knob helper — reads SMARM_ALLOC_INTERVAL / SMARM_TIMESLICE_CYCLES env vars // so the sweep script can override the preemption knobs without recompiling. @@ -384,10 +405,14 @@ fn bench_panic_tokio_multi() -> (u64, u128) { fn bench_cfg(threads: usize) -> smarm::runtime::Config { let mut cfg = smarm::runtime::Config::exact(threads); if let Ok(v) = std::env::var("SMARM_ALLOC_INTERVAL") { - if let Ok(n) = v.parse::() { cfg = cfg.alloc_interval(n); } + if let Ok(n) = v.parse::() { + cfg = cfg.alloc_interval(n); + } } if let Ok(v) = std::env::var("SMARM_TIMESLICE_CYCLES") { - if let Ok(n) = v.parse::() { cfg = cfg.timeslice_cycles(n); } + if let Ok(n) = v.parse::() { + cfg = cfg.timeslice_cycles(n); + } } cfg } @@ -397,7 +422,10 @@ fn main() { println!("smarm smarm-favored benchmarks"); println!("available parallelism: {n} threads"); let sets = env_sets(); - println!("ITERS={ITERS}×{sets} sets = {} samples (+1 warmup, discarded)", ITERS * sets); + println!( + "ITERS={ITERS}×{sets} sets = {} samples (+1 warmup, discarded)", + ITERS * sets + ); println!( "RECURSE_DEPTH={RECURSE_DEPTH}, HOT_YIELDS={HOT_YIELDS}×2, \ UNCONT_MSGS={UNCONT_MSGS}, PANIC_TASKS={PANIC_TASKS}" @@ -406,22 +434,30 @@ fn main() { // ---- 9. deep_recursion ---- print_header(&format!("deep_recursion: depth {RECURSE_DEPTH}")); run_n("smarm 1-thread", ITERS, || bench_recurse_smarm(1)); - run_n(&format!("smarm {n}-thread"), ITERS, || bench_recurse_smarm(n)); + run_n(&format!("smarm {n}-thread"), ITERS, || { + bench_recurse_smarm(n) + }); run_n("tokio current_thread", ITERS, bench_recurse_tokio_current); run_n("tokio multi-thread", ITERS, bench_recurse_tokio_multi); // ---- 10. yield_in_hot_loop ---- - print_header(&format!("yield_in_hot_loop: 2 actors × {HOT_YIELDS} yields (single thread)")); + print_header(&format!( + "yield_in_hot_loop: 2 actors × {HOT_YIELDS} yields (single thread)" + )); run_n("smarm 1-thread", ITERS, bench_hot_smarm); run_n("tokio current_thread", ITERS, bench_hot_tokio_current); // ---- 11. uncontended_channel ---- - print_header(&format!("uncontended_channel: 1→1, {UNCONT_MSGS} msgs (single thread)")); + print_header(&format!( + "uncontended_channel: 1→1, {UNCONT_MSGS} msgs (single thread)" + )); run_n("smarm 1-thread", ITERS, bench_unc_smarm); run_n("tokio current_thread", ITERS, bench_unc_tokio_current); // ---- 12. catch_unwind_panics ---- - print_header(&format!("catch_unwind_panics: {PANIC_TASKS} tasks, 50% panic")); + print_header(&format!( + "catch_unwind_panics: {PANIC_TASKS} tasks, 50% panic" + )); run_n("smarm 1-thread", ITERS, || bench_panic_smarm(1)); run_n(&format!("smarm {n}-thread"), ITERS, || bench_panic_smarm(n)); run_n("tokio current_thread", ITERS, bench_panic_tokio_current); diff --git a/benches/switch_cost.rs b/benches/switch_cost.rs index c4ff508..c8d2f1a 100644 --- a/benches/switch_cost.rs +++ b/benches/switch_cost.rs @@ -73,7 +73,10 @@ fn variant() -> &'static str { } fn env_usize(key: &str, default: usize) -> usize { - std::env::var(key).ok().and_then(|v| v.parse().ok()).unwrap_or(default) + std::env::var(key) + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(default) } // -------------------------------------------------------------------------- @@ -226,7 +229,11 @@ fn main() { let mean_cyc = pooled_cyc.iter().map(|&v| v as f64).sum::() / n.max(1) as f64; // Derived effective frequency: cycles per ns = GHz. Cross-checks the two // lenses against the box's known base clock. - let derived_ghz = if mean_ns > 0.0 { mean_cyc / mean_ns } else { 0.0 }; + let derived_ghz = if mean_ns > 0.0 { + mean_cyc / mean_ns + } else { + 0.0 + }; let p50 = pct(&pooled_ns, 50.0); let p90 = pct(&pooled_ns, 90.0); @@ -241,8 +248,14 @@ fn main() { " rounds={} warmup={} runs={} (instrumentation floor: {} ns / {} cyc, subtracted)", rounds, warmup, runs, floor_ns, floor_cyc ); - println!(" {:<10} {:<10} {:<10} {:<10} {:<10}", "p50 ns", "p90 ns", "p99 ns", "min ns", "max ns"); - println!(" {:<10} {:<10} {:<10} {:<10} {:<10}", p50, p90, p99, lo, hi); + println!( + " {:<10} {:<10} {:<10} {:<10} {:<10}", + "p50 ns", "p90 ns", "p99 ns", "min ns", "max ns" + ); + println!( + " {:<10} {:<10} {:<10} {:<10} {:<10}", + p50, p90, p99, lo, hi + ); println!( " mean {:.1} ns | mean {:.0} cyc | derived {:.3} GHz", mean_ns, mean_cyc, derived_ghz @@ -251,6 +264,18 @@ fn main() { // Greppable line — same spirit as SPINCSV. println!( "SWITCHCSV,{},{},{},{},{},{},{},{},{},{},{:.1},{:.0},{:.3}", - variant(), mode, rounds, runs, n, p50, p90, p99, lo, hi, mean_ns, mean_cyc, derived_ghz + variant(), + mode, + rounds, + runs, + n, + p50, + p90, + p99, + lo, + hi, + mean_ns, + mean_cyc, + derived_ghz ); } diff --git a/benches/tokio_favored.rs b/benches/tokio_favored.rs index bddb5b3..befd39c 100644 --- a/benches/tokio_favored.rs +++ b/benches/tokio_favored.rs @@ -36,7 +36,9 @@ use std::time::{Duration, Instant}; const ITERS: u32 = 15; fn available_threads() -> usize { - std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1) + std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(1) } fn env_sets() -> u32 { @@ -84,8 +86,8 @@ fn run_n (u64, u128)>(name: &str, n: u32, mut f: F) { // 5. spawn_storm_busy — workers loaded, then storm of zero-work spawns // --------------------------------------------------------------------------- -const STORM_BACKGROUND: u64 = 8; // number of background "busy" actors -const STORM_SPAWN: u64 = 10_000; // zero-work spawns to time +const STORM_BACKGROUND: u64 = 8; // number of background "busy" actors +const STORM_SPAWN: u64 = 10_000; // zero-work spawns to time fn bench_storm_smarm(threads: usize) -> (u64, u128) { let counter = Arc::new(AtomicU64::new(0)); @@ -114,11 +116,15 @@ fn bench_storm_smarm(threads: usize) -> (u64, u128) { cc.fetch_add(1, Ordering::Relaxed); })); } - for h in handles { h.join().unwrap(); } + for h in handles { + h.join().unwrap(); + } // Tear down background. s2.store(true, Ordering::Relaxed); - for h in bg_handles { h.join().unwrap(); } + for h in bg_handles { + h.join().unwrap(); + } }); (counter.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -129,7 +135,9 @@ fn bench_storm_tokio_current() -> (u64, u128) { let c2 = counter.clone(); let s2 = stop.clone(); - let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let start = Instant::now(); let local = tokio::task::LocalSet::new(); local.block_on(&rt, async move { @@ -149,9 +157,13 @@ fn bench_storm_tokio_current() -> (u64, u128) { cc.fetch_add(1, Ordering::Relaxed); })); } - for h in handles { let _ = h.await; } + for h in handles { + let _ = h.await; + } s2.store(true, Ordering::Relaxed); - for h in bg_handles { let _ = h.await; } + for h in bg_handles { + let _ = h.await; + } }); (counter.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -184,9 +196,13 @@ fn bench_storm_tokio_multi() -> (u64, u128) { cc.fetch_add(1, Ordering::Relaxed); })); } - for h in handles { let _ = h.await; } + for h in handles { + let _ = h.await; + } s2.store(true, Ordering::Relaxed); - for h in bg_handles { let _ = h.await; } + for h in bg_handles { + let _ = h.await; + } }); (counter.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -219,14 +235,21 @@ fn bench_mpsc_smarm(threads: usize) -> (u64, u128) { } let _ = count; // discard; run() closure must return () }); - for h in prod_handles { h.join().unwrap(); } + for h in prod_handles { + h.join().unwrap(); + } let _ = consumer.join().unwrap(); }); - (MPSC_PRODUCERS * MPSC_PER_PRODUCER, start.elapsed().as_micros()) + ( + MPSC_PRODUCERS * MPSC_PER_PRODUCER, + start.elapsed().as_micros(), + ) } fn bench_mpsc_tokio_current() -> (u64, u128) { - let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let start = Instant::now(); let local = tokio::task::LocalSet::new(); local.block_on(&rt, async move { @@ -248,10 +271,15 @@ fn bench_mpsc_tokio_current() -> (u64, u128) { } count }); - for h in prod_handles { let _ = h.await; } + for h in prod_handles { + let _ = h.await; + } let _ = consumer.await; }); - (MPSC_PRODUCERS * MPSC_PER_PRODUCER, start.elapsed().as_micros()) + ( + MPSC_PRODUCERS * MPSC_PER_PRODUCER, + start.elapsed().as_micros(), + ) } fn bench_mpsc_tokio_multi() -> (u64, u128) { @@ -279,10 +307,15 @@ fn bench_mpsc_tokio_multi() -> (u64, u128) { } count }); - for h in prod_handles { let _ = h.await; } + for h in prod_handles { + let _ = h.await; + } let _ = consumer.await; }); - (MPSC_PRODUCERS * MPSC_PER_PRODUCER, start.elapsed().as_micros()) + ( + MPSC_PRODUCERS * MPSC_PER_PRODUCER, + start.elapsed().as_micros(), + ) } // --------------------------------------------------------------------------- @@ -308,7 +341,9 @@ fn bench_timers_smarm(threads: usize) -> (u64, u128) { smarm::sleep(Duration::from_millis(ms)); })); } - for h in handles { h.join().unwrap(); } + for h in handles { + h.join().unwrap(); + } }); (TIMER_ACTORS, start.elapsed().as_micros()) } @@ -328,7 +363,9 @@ fn bench_timers_tokio_current() -> (u64, u128) { tokio::time::sleep(Duration::from_millis(ms)).await; })); } - for h in handles { let _ = h.await; } + for h in handles { + let _ = h.await; + } }); (TIMER_ACTORS, start.elapsed().as_micros()) } @@ -348,7 +385,9 @@ fn bench_timers_tokio_multi() -> (u64, u128) { tokio::time::sleep(Duration::from_millis(ms)).await; })); } - for h in handles { let _ = h.await; } + for h in handles { + let _ = h.await; + } }); (TIMER_ACTORS, start.elapsed().as_micros()) } @@ -361,11 +400,22 @@ const SCALING_N: u64 = 400_000; const SCALING_WORKERS: u64 = 64; fn is_prime(n: u64) -> bool { - if n < 2 { return false; } - if n < 4 { return true; } - if n % 2 == 0 { return false; } + if n < 2 { + return false; + } + if n < 4 { + return true; + } + if n % 2 == 0 { + return false; + } let mut i = 3u64; - while i * i <= n { if n % i == 0 { return false; } i += 2; } + while i * i <= n { + if n % i == 0 { + return false; + } + i += 2; + } true } @@ -376,7 +426,11 @@ fn count_primes(lo: u64, hi: u64) -> u64 { fn scaling_slice(w: u64) -> (u64, u64) { let per = SCALING_N / SCALING_WORKERS; let lo = w * per; - let hi = if w + 1 == SCALING_WORKERS { SCALING_N } else { lo + per }; + let hi = if w + 1 == SCALING_WORKERS { + SCALING_N + } else { + lo + per + }; (lo, hi) } @@ -393,7 +447,9 @@ fn bench_scaling_smarm(threads: usize) -> (u64, u128) { tc.fetch_add(count_primes(lo, hi), Ordering::Relaxed); })); } - for h in handles { h.join().unwrap(); } + for h in handles { + h.join().unwrap(); + } }); (total.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -415,7 +471,9 @@ fn bench_scaling_tokio_multi(threads: usize) -> (u64, u128) { tc.fetch_add(count_primes(lo, hi), Ordering::Relaxed); })); } - for h in handles { let _ = h.await; } + for h in handles { + let _ = h.await; + } }); (total.load(Ordering::Relaxed), start.elapsed().as_micros()) } @@ -424,7 +482,6 @@ fn bench_scaling_tokio_multi(threads: usize) -> (u64, u128) { // main // --------------------------------------------------------------------------- - // --------------------------------------------------------------------------- // Knob helper — reads SMARM_ALLOC_INTERVAL / SMARM_TIMESLICE_CYCLES env vars // so the sweep script can override the preemption knobs without recompiling. @@ -433,10 +490,14 @@ fn bench_scaling_tokio_multi(threads: usize) -> (u64, u128) { fn bench_cfg(threads: usize) -> smarm::runtime::Config { let mut cfg = smarm::runtime::Config::exact(threads); if let Ok(v) = std::env::var("SMARM_ALLOC_INTERVAL") { - if let Ok(n) = v.parse::() { cfg = cfg.alloc_interval(n); } + if let Ok(n) = v.parse::() { + cfg = cfg.alloc_interval(n); + } } if let Ok(v) = std::env::var("SMARM_TIMESLICE_CYCLES") { - if let Ok(n) = v.parse::() { cfg = cfg.timeslice_cycles(n); } + if let Ok(n) = v.parse::() { + cfg = cfg.timeslice_cycles(n); + } } cfg } @@ -446,7 +507,10 @@ fn main() { println!("smarm tokio-favored benchmarks"); println!("available parallelism: {n} threads"); let sets = env_sets(); - println!("ITERS={ITERS}×{sets} sets = {} samples (+1 warmup, discarded)", ITERS * sets); + println!( + "ITERS={ITERS}×{sets} sets = {} samples (+1 warmup, discarded)", + ITERS * sets + ); println!( "STORM_BACKGROUND={STORM_BACKGROUND}, STORM_SPAWN={STORM_SPAWN}, \ MPSC={MPSC_PRODUCERS}×{MPSC_PER_PRODUCER}, \ @@ -477,7 +541,9 @@ fn main() { "many_timers: {TIMER_ACTORS} actors sleeping {TIMER_MIN_MS}–{TIMER_MAX_MS} ms" )); run_n("smarm 1-thread", ITERS, || bench_timers_smarm(1)); - run_n(&format!("smarm {n}-thread"), ITERS, || bench_timers_smarm(n)); + run_n(&format!("smarm {n}-thread"), ITERS, || { + bench_timers_smarm(n) + }); run_n("tokio current_thread", ITERS, bench_timers_tokio_current); run_n("tokio multi-thread", ITERS, bench_timers_tokio_multi); @@ -487,13 +553,19 @@ fn main() { )); let sweep: Vec = { let mut v = vec![1usize, 2, 4]; - if n > 4 && !v.contains(&n) { v.push(n); } + if n > 4 && !v.contains(&n) { + v.push(n); + } v.into_iter().filter(|t| *t <= n).collect() }; for t in &sweep { - run_n(&format!("smarm {t}-thread"), ITERS, || bench_scaling_smarm(*t)); + run_n(&format!("smarm {t}-thread"), ITERS, || { + bench_scaling_smarm(*t) + }); } for t in &sweep { - run_n(&format!("tokio multi {t}-thread"), ITERS, || bench_scaling_tokio_multi(*t)); + run_n(&format!("tokio multi {t}-thread"), ITERS, || { + bench_scaling_tokio_multi(*t) + }); } } diff --git a/examples/causal_attrib_probe.rs b/examples/causal_attrib_probe.rs index 537831d..b474138 100644 --- a/examples/causal_attrib_probe.rs +++ b/examples/causal_attrib_probe.rs @@ -67,7 +67,9 @@ fn main() { println!("calibration: {per_us} work iters/µs"); let work_us = move |us: u64| work_iters(us * per_us); - let cores = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1); + let cores = std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(1); println!("cores: {cores}"); if cores < 4 { println!("probe: SKIPPED (needs the stages in parallel)"); diff --git a/examples/gen_statem_expanded.rs b/examples/gen_statem_expanded.rs index b31aa7f..932d958 100644 --- a/examples/gen_statem_expanded.rs +++ b/examples/gen_statem_expanded.rs @@ -35,8 +35,8 @@ #![deny(dead_code, unreachable_patterns)] +use smarm::gen_statem::{spawn, Cx, GenStatemRef, Machine, Reply, Resolution, Step}; use smarm::run; -use smarm::gen_statem::{spawn, Cx, Machine, Reply, Resolution, Step, GenStatemRef}; // === user types ============================================================ @@ -123,7 +123,11 @@ impl DoorSm { fn start(init: Door) -> GenStatemRef { spawn(DoorSm { state: init, - data: Data { enters: 0, pushes: 0, knocks: 0 }, + data: Data { + enters: 0, + pushes: 0, + knocks: 0, + }, }) } @@ -193,16 +197,12 @@ impl Machine for DoorSm { (Door::Closed, Ev::Cast(Cast::Push | Cast::Unlock(_))) => Resolution::Unhandled, // --- Locked (branching row: handler picks within UnlockOutcome) - - (Door::Locked, Ev::Cast(Cast::Unlock(key))) => { - Resolution::To(on_unlock(key).into()) - } + (Door::Locked, Ev::Cast(Cast::Unlock(key))) => Resolution::To(on_unlock(key).into()), // Routed out in phase 1; listed only to keep this match total. (Door::Locked, Ev::Cast(Cast::Knock)) => { unreachable!("postponed event is replayed, not dispatched here") } - (Door::Locked, Ev::Cast(Cast::Push | Cast::Pull | Cast::Lock)) => { - Resolution::Unhandled - } + (Door::Locked, Ev::Cast(Cast::Push | Cast::Pull | Cast::Lock)) => Resolution::Unhandled, // --- state-independent queries (reply, then stay) --------------- (_, Ev::Call(Call::GetState(r))) => { diff --git a/examples/gen_statem_macro.rs b/examples/gen_statem_macro.rs index f9f9e50..57f36c5 100644 --- a/examples/gen_statem_macro.rs +++ b/examples/gen_statem_macro.rs @@ -18,8 +18,8 @@ // dispatch's own unreachable_patterns internally. use smarm::gen_statem; -use smarm::run; use smarm::gen_statem::Reply; +use smarm::run; // === user types (identical to gen_statem_expanded.rs) ========================= @@ -135,7 +135,14 @@ gen_statem! { fn main() { run(|| { - let door = DoorSm::start(Door::Closed, Data { enters: 0, pushes: 0, knocks: 0 }); + let door = DoorSm::start( + Door::Closed, + Data { + enters: 0, + pushes: 0, + knocks: 0, + }, + ); door.send(Ev::Cast(Cast::Lock)).unwrap(); // Closed -> Locked door.send(Ev::Cast(Cast::Knock)).unwrap(); // Locked: postponed (not yet counted) diff --git a/examples/named_genserver.rs b/examples/named_genserver.rs index cb132ef..27f17ca 100644 --- a/examples/named_genserver.rs +++ b/examples/named_genserver.rs @@ -6,7 +6,9 @@ //! every use — so the address keeps working across a supervised restart, with //! no stale [`GenServerRef`] to refresh. -use smarm::{call, cast, run, whereis_server, GenServer, GenServerBuilder, GenServerName, GenServerRef}; +use smarm::{ + call, cast, run, whereis_server, GenServer, GenServerBuilder, GenServerName, GenServerRef, +}; /// A counter server: synchronous `Get`, asynchronous `Inc` / `Add`. struct Counter { diff --git a/examples/observer.rs b/examples/observer.rs index b65c669..91722cf 100644 --- a/examples/observer.rs +++ b/examples/observer.rs @@ -15,7 +15,9 @@ //! `call`, nothing more. use smarm::observer::{self, ObserverReply, ObserverRequest}; -use smarm::{channel, register, run, spawn, ActorState, Name, RuntimeSnapshot, RuntimeTree, TreeNode}; +use smarm::{ + channel, register, run, spawn, ActorState, Name, RuntimeSnapshot, RuntimeTree, TreeNode, +}; const ECHO: Name = Name::new("echo"); @@ -31,7 +33,11 @@ fn state_glyph(s: ActorState) -> &'static str { /// A `ps`-style table over the flat snapshot. fn print_snapshot(snap: &RuntimeSnapshot) { - println!("snapshot (format v{}, {} actors)", snap.format_version, snap.actors.len()); + println!( + "snapshot (format v{}, {} actors)", + snap.format_version, + snap.actors.len() + ); println!( " {:<10} {:<9} {:<10} {:>4} {:>4} {:>4} {:>4} {:>5} {}", "pid", "state", "parent", "mon", "lnk", "joi", "mbox", "msgs", "names" @@ -52,7 +58,11 @@ fn print_snapshot(snap: &RuntimeSnapshot) { a.joiners, a.mailbox_depth, a.messages_received, - if a.names.is_empty() { "-".to_string() } else { a.names.join(",") }, + if a.names.is_empty() { + "-".to_string() + } else { + a.names.join(",") + }, ); } } diff --git a/src/actor.rs b/src/actor.rs index 96c9e9f..f845a2c 100644 --- a/src/actor.rs +++ b/src/actor.rs @@ -99,7 +99,7 @@ pub extern "C-unwind" fn trampoline() { }; let outcome = match panic::catch_unwind(panic::AssertUnwindSafe(b)) { - Ok(()) => Outcome::Exit, + Ok(()) => Outcome::Exit, Err(payload) => { if payload.is::() { Outcome::Stopped diff --git a/src/causal.rs b/src/causal.rs index 9afc412..34a7c52 100644 --- a/src/causal.rs +++ b/src/causal.rs @@ -342,8 +342,7 @@ mod inner { // Count the loss in would-be delta terms so the audit's columns // compare directly against `injected_cycles`. DISCARD_OVERMAX_N.fetch_add(1, Ordering::Relaxed); - DISCARD_OVERMAX_CYCLES - .fetch_add(interval.saturating_mul(pct) / 100, Ordering::Relaxed); + DISCARD_OVERMAX_CYCLES.fetch_add(interval.saturating_mul(pct) / 100, Ordering::Relaxed); return; } let delta = interval.saturating_mul(pct) / 100; @@ -419,8 +418,7 @@ mod inner { let gap = preempt::rdtsc() .saturating_sub(desched_tsc) .min(MAX_SAMPLE_CYCLES); - OFFCPU_IN_SITE_CYCLES - .fetch_add(gap.saturating_mul(pct) / 100, Ordering::Relaxed); + OFFCPU_IN_SITE_CYCLES.fetch_add(gap.saturating_mul(pct) / 100, Ordering::Relaxed); OFFCPU_IN_SITE_N.fetch_add(1, Ordering::Relaxed); } } @@ -533,7 +531,9 @@ mod inner { park_forgiven_cycles: self .park_forgiven_cycles .saturating_sub(before.park_forgiven_cycles), - drop_park_cycles: self.drop_park_cycles.saturating_sub(before.drop_park_cycles), + drop_park_cycles: self + .drop_park_cycles + .saturating_sub(before.drop_park_cycles), drop_park_n: self.drop_park_n.saturating_sub(before.drop_park_n), drop_yield_cycles: self .drop_yield_cycles @@ -542,12 +542,18 @@ mod inner { discard_overmax_cycles: self .discard_overmax_cycles .saturating_sub(before.discard_overmax_cycles), - discard_overmax_n: self.discard_overmax_n.saturating_sub(before.discard_overmax_n), - discard_unarmed_n: self.discard_unarmed_n.saturating_sub(before.discard_unarmed_n), + discard_overmax_n: self + .discard_overmax_n + .saturating_sub(before.discard_overmax_n), + discard_unarmed_n: self + .discard_unarmed_n + .saturating_sub(before.discard_unarmed_n), offcpu_in_site_cycles: self .offcpu_in_site_cycles .saturating_sub(before.offcpu_in_site_cycles), - offcpu_in_site_n: self.offcpu_in_site_n.saturating_sub(before.offcpu_in_site_n), + offcpu_in_site_n: self + .offcpu_in_site_n + .saturating_sub(before.offcpu_in_site_n), } } } @@ -795,7 +801,9 @@ mod inner { let cell = results .iter() .find(|r| r.site == site && r.speedup_pct == speedup_pct)?; - let base = results.iter().find(|r| r.site == site && r.speedup_pct == 0)?; + let base = results + .iter() + .find(|r| r.site == site && r.speedup_pct == 0)?; let rate = normalized_rate(cell, point)?; let b = normalized_rate(base, point)?; if b <= 0.0 { @@ -946,7 +954,9 @@ macro_rules! progress { macro_rules! causal_site { ($name:literal) => {{ static __SMARM_SITE: ::std::sync::OnceLock = ::std::sync::OnceLock::new(); - $crate::causal::SiteGuard::enter(*__SMARM_SITE.get_or_init(|| $crate::causal::site_id($name))) + $crate::causal::SiteGuard::enter( + *__SMARM_SITE.get_or_init(|| $crate::causal::site_id($name)), + ) }}; } diff --git a/src/channel.rs b/src/channel.rs index 6ded9cb..a2af72a 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -104,7 +104,12 @@ pub fn channel() -> (Sender, Receiver) { senders: 1, receiver_alive: true, })); - (Sender { inner: inner.clone() }, Receiver { inner }) + ( + Sender { + inner: inner.clone(), + }, + Receiver { inner }, + ) } struct Inner { @@ -178,7 +183,9 @@ impl std::error::Error for RecvTimeoutError {} impl Clone for Sender { fn clone(&self) -> Self { self.inner.lock().senders += 1; - Sender { inner: self.inner.clone() } + Sender { + inner: self.inner.clone(), + } } } @@ -248,10 +255,18 @@ impl Sender { g.parked_receiver.take() }; if let Some((pid, epoch)) = unpark { - crate::te!(crate::trace::Event::Send { sender: crate::actor::current_pid().unwrap_or(crate::pid::Pid::new(u32::MAX, u32::MAX)), receiver: Some(pid) }); + crate::te!(crate::trace::Event::Send { + sender: crate::actor::current_pid() + .unwrap_or(crate::pid::Pid::new(u32::MAX, u32::MAX)), + receiver: Some(pid) + }); crate::scheduler::unpark_at(pid, epoch); } else { - crate::te!(crate::trace::Event::Send { sender: crate::actor::current_pid().unwrap_or(crate::pid::Pid::new(u32::MAX, u32::MAX)), receiver: None }); + crate::te!(crate::trace::Event::Send { + sender: crate::actor::current_pid() + .unwrap_or(crate::pid::Pid::new(u32::MAX, u32::MAX)), + receiver: None + }); } Ok(()) } @@ -290,10 +305,12 @@ 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(match crate::actor::current_pid() { - Some(p) => p, - None => panic!("smarm: RecvWake outside an actor (core corrupt)"), - })); + crate::te!(crate::trace::Event::RecvWake( + match crate::actor::current_pid() { + Some(p) => p, + None => panic!("smarm: RecvWake outside an actor (core corrupt)"), + } + )); } } @@ -347,10 +364,12 @@ impl Receiver { crate::scheduler::insert_wait_timer(deadline, me, target, epoch); crate::scheduler::park_current(); - crate::te!(crate::trace::Event::RecvWake(match crate::actor::current_pid() { - Some(p) => p, - None => panic!("smarm: RecvWake outside an actor (core corrupt)"), - })); + 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(); @@ -412,10 +431,12 @@ impl Receiver { } // Release the lock before parking: the unparker will need it. crate::scheduler::park_current(); - crate::te!(crate::trace::Event::RecvWake(match crate::actor::current_pid() { - Some(p) => p, - None => panic!("smarm: RecvWake outside an actor (core corrupt)"), - })); + crate::te!(crate::trace::Event::RecvWake( + match crate::actor::current_pid() { + Some(p) => p, + None => panic!("smarm: RecvWake outside an actor (core corrupt)"), + } + )); } } @@ -616,7 +637,12 @@ pub fn try_select(arms: &[&dyn Selectable]) -> std::io::Result { // Channel-only selects skip all of it: `eager` is false, the guard // is disarmed, and the loser-arm self-cleaning story is unchanged. let eager = arms.iter().any(|a| a.sel_eager_cleanup()); - let mut guard = UnregisterGuard { arms, me, epoch, armed: eager }; + let mut guard = UnregisterGuard { + arms, + me, + epoch, + armed: eager, + }; crate::scheduler::park_current(); @@ -687,11 +713,7 @@ impl Drop for UnregisterGuard<'_> { // unregistered eagerly so none are left dangling. `Err` = an arm failed to // register; same unwind (earlier fd arms unregistered, wait retired). // `Ok(None)` = every arm registered successfully; the caller parks. -fn register_arms( - me: Pid, - epoch: u32, - arms: &[&dyn Selectable], -) -> std::io::Result> { +fn register_arms(me: Pid, epoch: u32, arms: &[&dyn Selectable]) -> std::io::Result> { for (i, arm) in arms.iter().enumerate() { let registered = match arm.sel_register(me, epoch) { Ok(r) => r, @@ -736,10 +758,7 @@ impl crate::timer::TimerTarget for SelectTimeout { /// Panics if `arms` is empty, if called outside an actor, or if an fd arm /// fails to register (see [`try_select_timeout`] for the fallible form; a /// channel-only select can never fail). -pub fn select_timeout( - arms: &[&dyn Selectable], - timeout: std::time::Duration, -) -> Option { +pub fn select_timeout(arms: &[&dyn Selectable], timeout: std::time::Duration) -> Option { match try_select_timeout(arms, timeout) { Ok(r) => r, Err(e) => panic!( @@ -776,7 +795,12 @@ pub fn try_select_timeout( // would leave those fds unusable until a kernel event happened to // clear them. let eager = arms.iter().any(|a| a.sel_eager_cleanup()); - let mut guard = UnregisterGuard { arms, me, epoch, armed: eager }; + let mut guard = UnregisterGuard { + arms, + me, + epoch, + armed: eager, + }; crate::scheduler::park_current(); diff --git a/src/context.rs b/src/context.rs index cefc245..24910f5 100644 --- a/src/context.rs +++ b/src/context.rs @@ -16,10 +16,18 @@ thread_local! { static ACTOR_SP: Cell = const { Cell::new(0) }; } -fn get_scheduler_sp() -> usize { SCHEDULER_SP.with(|c| c.get()) } -fn set_scheduler_sp(v: usize) { SCHEDULER_SP.with(|c| c.set(v)) } -pub fn get_actor_sp() -> usize { ACTOR_SP.with(|c| c.get()) } -pub fn set_actor_sp(v: usize) { ACTOR_SP.with(|c| c.set(v)) } +fn get_scheduler_sp() -> usize { + SCHEDULER_SP.with(|c| c.get()) +} +fn set_scheduler_sp(v: usize) { + SCHEDULER_SP.with(|c| c.set(v)) +} +pub fn get_actor_sp() -> usize { + ACTOR_SP.with(|c| c.get()) +} +pub fn set_actor_sp(v: usize) { + ACTOR_SP.with(|c| c.set(v)) +} // --------------------------------------------------------------------------- // Initial stack layout @@ -49,13 +57,20 @@ pub fn set_actor_sp(v: usize) { ACTOR_SP.with(|c| c.set(v)) } pub fn init_actor_stack(top: *mut u8, entry: extern "C-unwind" fn()) -> usize { unsafe { let mut sp = (top as usize & !15) - 8; - sp -= 8; (sp as *mut usize).write(entry as usize); // ret target - sp -= 8; (sp as *mut usize).write(0); // rbx - sp -= 8; (sp as *mut usize).write(0); // rbp - sp -= 8; (sp as *mut usize).write(0); // r12 - sp -= 8; (sp as *mut usize).write(0); // r13 - sp -= 8; (sp as *mut usize).write(0); // r14 - sp -= 8; (sp as *mut usize).write(0); // r15 + sp -= 8; + (sp as *mut usize).write(entry as usize); // ret target + sp -= 8; + (sp as *mut usize).write(0); // rbx + sp -= 8; + (sp as *mut usize).write(0); // rbp + sp -= 8; + (sp as *mut usize).write(0); // r12 + sp -= 8; + (sp as *mut usize).write(0); // r13 + sp -= 8; + (sp as *mut usize).write(0); // r14 + sp -= 8; + (sp as *mut usize).write(0); // r15 sp } } diff --git a/src/gen_server.rs b/src/gen_server.rs index 847012f..43ed5be 100644 --- a/src/gen_server.rs +++ b/src/gen_server.rs @@ -178,7 +178,9 @@ //! from any handler via [`Watcher::watch`]) because monitors are inherently //! created at runtime. The idle window is set once, in `init`. -use crate::channel::{channel, select, select_timeout, Receiver, RecvTimeoutError, Selectable, Sender}; +use crate::channel::{ + channel, select, select_timeout, Receiver, RecvTimeoutError, Selectable, Sender, +}; use crate::monitor::{demonitor, monitor, Down, Monitor}; use crate::pid::Pid; use crate::registry::{register_with, resolve_named_sender, RegisterError}; @@ -273,7 +275,10 @@ pub struct GenServerRef { impl Clone for GenServerRef { fn clone(&self) -> Self { - GenServerRef { tx: self.tx.clone(), pid: self.pid } + GenServerRef { + tx: self.tx.clone(), + pid: self.pid, + } } } @@ -412,7 +417,9 @@ impl GenServerCtx { /// A clonable handle to the loop's monitor intake. Store it in the state /// during `init` to watch monitors from later handlers. pub fn watcher(&self) -> Watcher { - Watcher { tx: self.sys_tx.clone() } + Watcher { + tx: self.sys_tx.clone(), + } } /// Shorthand for `ctx.watcher().watch(m)` when watching during `init`. @@ -426,7 +433,10 @@ impl GenServerCtx { /// [`tick_every`](TimerHandle::tick_every) / /// [`cancel`](TimerHandle::cancel) from any later handler. pub fn timer(&self) -> TimerHandle { - TimerHandle { sys_tx: self.sys_tx.clone(), reg: self.reg.clone() } + TimerHandle { + sys_tx: self.sys_tx.clone(), + reg: self.reg.clone(), + } } /// Set a quiet-period window: if the loop goes `after` without dispatching @@ -518,7 +528,10 @@ pub struct TimerHandle { // Manual Clone for the same reason as `Watcher`: no `G: Clone` needed. impl Clone for TimerHandle { fn clone(&self) -> Self { - TimerHandle { sys_tx: self.sys_tx.clone(), reg: self.reg.clone() } + TimerHandle { + sys_tx: self.sys_tx.clone(), + reg: self.reg.clone(), + } } } @@ -569,8 +582,18 @@ impl TimerHandle { // First instance fires after `every`; the payload is produced loop-side // from `make` on fire, so the tick carries only the stable id. let sub = send_after_to(every, self.sys_tx.clone(), Sys::Tick(local)); - reg.periodics.insert(local, Periodic { every, live: sub, make }); - debug_assert!(reg.rearm_tx.is_some(), "rearm_tx must be Some while periodics is non-empty"); + reg.periodics.insert( + local, + Periodic { + every, + live: sub, + make, + }, + ); + debug_assert!( + reg.rearm_tx.is_some(), + "rearm_tx must be Some while periodics is non-empty" + ); local } @@ -617,7 +640,9 @@ pub struct Watcher { // regardless of the server type (it clones only the inner sender). impl Clone for Watcher { fn clone(&self) -> Self { - Watcher { tx: self.tx.clone() } + Watcher { + tx: self.tx.clone(), + } } } @@ -691,7 +716,10 @@ impl GenServerBuilder { /// live server). Consumes the builder, carrying its `with_info` / `under` /// configuration through. pub fn named(self, name: GenServerName) -> NamedGenServerBuilder { - NamedGenServerBuilder { builder: self, name: name.as_str() } + NamedGenServerBuilder { + builder: self, + name: name.as_str(), + } } /// Private shared body behind [`start`](Self::start) and @@ -700,18 +728,24 @@ impl GenServerBuilder { /// under the name before returning. fn spawn_server(self) -> GenServerRef { let (tx, rx) = channel::>(); - let GenServerBuilder { state, infos, supervisor, stack_opts } = self; + let GenServerBuilder { + state, + infos, + supervisor, + stack_opts, + } = self; let handle = match supervisor { - Some(sup) => { - crate::scheduler::spawn_under_with(sup, stack_opts, move || { - server_loop::(rx, state, infos) - }) - } - None => crate::scheduler::spawn_with(stack_opts, move || { + Some(sup) => crate::scheduler::spawn_under_with(sup, stack_opts, move || { server_loop::(rx, state, infos) }), + None => { + crate::scheduler::spawn_with(stack_opts, move || server_loop::(rx, state, infos)) + } }; - GenServerRef { tx, pid: handle.pid() } + GenServerRef { + tx, + pid: handle.pid(), + } } } @@ -739,7 +773,10 @@ impl GenServerName { /// associated constants at call sites. #[inline] pub const fn new(name: &'static str) -> Self { - Self { name, _marker: PhantomData } + Self { + name, + _marker: PhantomData, + } } /// The underlying registry key. @@ -933,7 +970,11 @@ fn server_loop( // Bind the ctx so the idle window set during init can be read back, then // drop it — that drops the loop's own Sys sender, so a state that cloned no // Watcher/TimerHandle lets the arm auto-close (the unused-ctx behaviour). - let ctx = GenServerCtx { sys_tx, reg: reg.clone(), idle: Cell::new(None) }; + let ctx = GenServerCtx { + sys_tx, + reg: reg.clone(), + idle: Cell::new(None), + }; guard.0.init(&ctx); let idle = ctx.idle.get(); drop(ctx); @@ -983,13 +1024,12 @@ fn server_loop( // inbox. The slice is rebuilt each iteration because the monitor // and info sets shrink/grow. Mirrors the fast-path inbox park // above; keep them in sync. - let nd = monitors.len(); // monitor band: [0, nd) - let nw = sys_open as usize; // system arm: [nd, nd+nw) - // info band: [nd+nw, nd+nw+ni) - // inbox arm: [nd+nw+ni] + let nd = monitors.len(); // monitor band: [0, nd) + let nw = sys_open as usize; // system arm: [nd, nd+nw) + // info band: [nd+nw, nd+nw+ni) + // inbox arm: [nd+nw+ni] let sel = { - let mut arms: Vec<&dyn Selectable> = - Vec::with_capacity(nd + nw + infos.len() + 1); + let mut arms: Vec<&dyn Selectable> = Vec::with_capacity(nd + nw + infos.len() + 1); for m in &monitors { arms.push(&m.rx); } @@ -1001,9 +1041,7 @@ fn server_loop( } arms.push(&rx); match idle_deadline { - Some(dl) => { - select_timeout(&arms, dl.saturating_duration_since(Instant::now())) - } + Some(dl) => select_timeout(&arms, dl.saturating_duration_since(Instant::now())), None => Some(select(&arms)), } }; @@ -1034,8 +1072,12 @@ fn server_loop( // live set tracks only still-pending timers, then // dispatch. match reg.lock() { - Ok(mut g) => { g.oneshots.remove(&id); } - Err(e) => panic!("smarm: gen_server reg lock poisoned (core corrupt): {e}"), + 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); @@ -1049,7 +1091,9 @@ fn server_loop( let msg = { let mut g = match reg.lock() { Ok(g) => g, - Err(e) => panic!("smarm: gen_server reg lock poisoned (core corrupt): {e}"), + 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) { @@ -1057,9 +1101,9 @@ fn server_loop( let msg = (p.make)(); let tx = match r.rearm_tx.as_ref() { Some(tx) => tx.clone(), - None => panic!( - "smarm: live periodic without rearm_tx (logic bug)" - ), + None => { + panic!("smarm: live periodic without rearm_tx (logic bug)") + } }; p.live = send_after_to(every, tx, Sys::Tick(id)); Some(msg) diff --git a/src/gen_statem.rs b/src/gen_statem.rs index e23e1ca..6533020 100644 --- a/src/gen_statem.rs +++ b/src/gen_statem.rs @@ -219,7 +219,11 @@ struct Timers { impl Timers { fn new() -> Self { - Timers { next_local: 0, state: None, named: HashMap::new() } + Timers { + next_local: 0, + state: None, + named: HashMap::new(), + } } fn mint(&mut self) -> u64 { @@ -248,7 +252,11 @@ pub struct Cx { impl Cx { fn new(sys_tx: Sender, reg: Arc>) -> Self { - Cx { sys_tx, reg, _ev: PhantomData } + Cx { + sys_tx, + reg, + _ev: PhantomData, + } } /// Arm the **state timeout**: fire a `state_timeout` event after `after` in @@ -387,7 +395,10 @@ pub struct GenStatemRef { impl Clone for GenStatemRef { fn clone(&self) -> Self { - GenStatemRef { tx: self.tx.clone(), pid: self.pid } + GenStatemRef { + tx: self.tx.clone(), + pid: self.pid, + } } } @@ -443,13 +454,13 @@ pub fn spawn(machine: M) -> GenStatemRef { /// a `_with` variant like the scheduler's own spawns. /// /// Panics if called outside `Runtime::run()`. -pub fn spawn_with( - opts: crate::scheduler::SpawnOpts, - machine: M, -) -> GenStatemRef { +pub fn spawn_with(opts: crate::scheduler::SpawnOpts, machine: M) -> GenStatemRef { let (tx, rx) = channel::(); let handle = crate::scheduler::spawn_with(opts, move || statem_loop(rx, machine)); - GenStatemRef { tx, pid: handle.pid() } + GenStatemRef { + tx, + pid: handle.pid(), + } } /// The machine actor body: `on_start`, then one `handle` per event until the @@ -488,7 +499,9 @@ fn statem_loop(rx: Receiver, mut machine: M) { Sys::StateTimeout(local) => { let mut t = match reg.lock() { Ok(g) => g, - Err(e) => panic!("smarm: gen_statem reg lock poisoned (core corrupt): {e}"), + Err(e) => panic!( + "smarm: gen_statem reg lock poisoned (core corrupt): {e}" + ), }; match t.state { Some((live, _)) if live == local => { @@ -501,7 +514,9 @@ fn statem_loop(rx: Receiver, mut machine: M) { Sys::Timeout(name, local) => { let mut t = match reg.lock() { Ok(g) => g, - Err(e) => panic!("smarm: gen_statem reg lock poisoned (core corrupt): {e}"), + Err(e) => panic!( + "smarm: gen_statem reg lock poisoned (core corrupt): {e}" + ), }; match t.named.get(name) { Some(&(live, _)) if live == local => { diff --git a/src/introspect.rs b/src/introspect.rs index df60971..ace3b26 100644 --- a/src/introspect.rs +++ b/src/introspect.rs @@ -239,7 +239,10 @@ pub fn snapshot() -> RuntimeSnapshot { actors.push(info); } } - RuntimeSnapshot { format_version: SNAPSHOT_FORMAT_VERSION, actors } + RuntimeSnapshot { + format_version: SNAPSHOT_FORMAT_VERSION, + actors, + } }) } @@ -387,7 +390,10 @@ pub fn tree() -> RuntimeTree { /// want to inspect again) and want the tree view of it without re-reading /// the runtime. pub fn tree_from(snap: RuntimeSnapshot) -> RuntimeTree { - let RuntimeSnapshot { format_version, actors } = snap; + let RuntimeSnapshot { + format_version, + actors, + } = snap; let mut index_of: HashMap = HashMap::with_capacity(actors.len()); for (i, a) in actors.iter().enumerate() { @@ -418,7 +424,10 @@ pub fn tree_from(snap: RuntimeSnapshot) -> RuntimeTree { .into_iter() .filter_map(|i| build_node(i, &children_of, &orphaned, &mut slots)) .collect(); - RuntimeTree { format_version, roots: root_nodes } + RuntimeTree { + format_version, + roots: root_nodes, + } } fn build_node( @@ -436,5 +445,9 @@ fn build_node( .collect() }) .unwrap_or_default(); - Some(TreeNode { info, orphaned: orphaned[i], children }) + Some(TreeNode { + info, + orphaned: orphaned[i], + children, + }) } diff --git a/src/io.rs b/src/io.rs index 2fd01dc..5bd0b45 100644 --- a/src/io.rs +++ b/src/io.rs @@ -136,7 +136,6 @@ pub struct IoThread { waiters: Waiters, // ----- Epoll machinery ----- - /// The epollfd, owned by `IoThread`. Callable cross-thread via /// `epoll_ctl` per the man page. epollfd: RawFd, @@ -147,7 +146,6 @@ pub struct IoThread { shutdown_write: RawFd, // ----- Threads ----- - pool_thread: Option>, epoll_thread: Option>, } @@ -284,9 +282,8 @@ impl IoThread { events, u64: fd as u64, }; - let r = unsafe { - libc::epoll_ctl(self.epollfd, libc::EPOLL_CTL_ADD, fd, &mut ev as *mut _) - }; + let r = + unsafe { libc::epoll_ctl(self.epollfd, libc::EPOLL_CTL_ADD, fd, &mut ev as *mut _) }; if r < 0 { return Err(io::Error::last_os_error()); } @@ -398,12 +395,7 @@ fn epoll_loop(epollfd: RawFd, waiters: Waiters, rt: Weak) { loop { let n = unsafe { - libc::epoll_wait( - epollfd, - events.as_mut_ptr(), - MAX_EVENTS as libc::c_int, - -1, - ) + libc::epoll_wait(epollfd, events.as_mut_ptr(), MAX_EVENTS as libc::c_int, -1) }; if n < 0 { @@ -438,12 +430,7 @@ fn epoll_loop(epollfd: RawFd, waiters: Waiters, rt: Weak) { let entry = w.remove(&fd); if entry.is_some() { unsafe { - libc::epoll_ctl( - epollfd, - libc::EPOLL_CTL_DEL, - fd, - std::ptr::null_mut(), - ); + libc::epoll_ctl(epollfd, libc::EPOLL_CTL_DEL, fd, std::ptr::null_mut()); } } entry diff --git a/src/lib.rs b/src/lib.rs index de360b6..c108b62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,36 +11,36 @@ //! //! See `LOOM.md` for the design intent and the deferred-for-later list. -pub mod stack; -pub(crate) mod signal; -pub mod context; -pub mod preempt; -pub mod pid; pub mod actor; +pub mod causal; pub mod channel; -pub mod scheduler; -pub mod supervisor; -pub mod timer; -pub mod io; -pub mod mutex; -pub mod monitor; -pub mod registry; -pub mod pg; -pub mod link; +pub mod context; pub mod gen_server; pub mod gen_statem; pub mod introspect; +pub mod io; +pub mod link; +pub mod monitor; +pub mod mutex; #[cfg(feature = "observer")] pub mod observer; -pub mod runtime; pub(crate) mod park; +pub mod pg; +pub mod pid; +pub mod preempt; pub(crate) mod raw_mutex; -pub(crate) mod slot_state; -pub(crate) mod sync_shim; +pub mod registry; #[doc(hidden)] // pub only so benches/rq_micro.rs can drive the raw structures pub mod run_queue; +pub mod runtime; +pub mod scheduler; +pub(crate) mod signal; +pub(crate) mod slot_state; +pub mod stack; +pub mod supervisor; +pub(crate) mod sync_shim; +pub mod timer; pub mod trace; -pub mod causal; // --------------------------------------------------------------------------- // Global allocator @@ -59,23 +59,28 @@ pub use channel::{ }; pub use gen_server::{ call, cast, shutdown, whereis_server, CallError, CallTimeoutError, CastError, GenServer, - NamedGenServerBuilder, GenServerBuilder, GenServerCtx, GenServerName, GenServerRef, TimerHandle, Watcher, + GenServerBuilder, GenServerCtx, GenServerName, GenServerRef, NamedGenServerBuilder, + TimerHandle, Watcher, }; pub use gen_statem::{ - CallError as GenStatemCallError, Cx, Machine, Reply, Resolution, SendError as GenStatemSendError, - GenStatemRef, + CallError as GenStatemCallError, Cx, GenStatemRef, Machine, Reply, Resolution, + SendError as GenStatemSendError, }; -pub use introspect::{StackInfo, +pub use introspect::{ actor_info, snapshot, tree, tree_from, ActorInfo, ActorState, RuntimeSnapshot, RuntimeTree, - TreeNode, SNAPSHOT_FORMAT_VERSION, + StackInfo, TreeNode, SNAPSHOT_FORMAT_VERSION, }; +pub use link::{link, trap_exit, unlink, ExitSignal}; +pub use monitor::{ + demonitor, mark_watchable, monitor, terminal_reason, Down, DownReason, Monitor, MonitorId, +}; +pub use mutex::{LockTimeout, Mutex, MutexGuard}; #[cfg(feature = "observer")] pub use observer::{ObserverReply, ObserverRequest}; -pub use link::{link, trap_exit, unlink, ExitSignal}; -pub use monitor::{demonitor, mark_watchable, monitor, terminal_reason, Down, DownReason, Monitor, MonitorId}; -pub use mutex::{LockTimeout, Mutex, MutexGuard}; +pub use pg::{ + dispatch, join, leave, members, members_as, pick, pick_as, Incarnation, Member, NodeId, +}; pub use pid::{Addressable, Erased, Name, Pid, RawPid}; -pub use pg::{dispatch, join, leave, members, members_as, pick, pick_as, Incarnation, Member, NodeId}; pub use registry::{ install, lookup_as, register, resolve_name, send, send_dyn, send_to, unregister, whereis, NameResolution, RegisterError, SendError, @@ -83,9 +88,8 @@ pub use registry::{ pub use runtime::{init, Config, Runtime}; pub use scheduler::{ block_on_io, cancel_timer, request_stop, run, self_pid, send_after, send_after_named, - send_after_named_wall, send_after_wall, sleep, sleep_wall, - spawn, spawn_addr, spawn_addr_with, spawn_under, spawn_under_with, spawn_with, - wait_readable, wait_readable_timeout, wait_writable, + send_after_named_wall, send_after_wall, sleep, sleep_wall, spawn, spawn_addr, spawn_addr_with, + spawn_under, spawn_under_with, spawn_with, wait_readable, wait_readable_timeout, wait_writable, wait_writable_timeout, yield_now, FdArm, JoinError, JoinHandle, SpawnOpts, }; pub use supervisor::{ChildSpec, OneForOne, Restart, Signal, Strategy}; diff --git a/src/link.rs b/src/link.rs index c4242d2..e732540 100644 --- a/src/link.rs +++ b/src/link.rs @@ -157,7 +157,10 @@ pub fn link(target: Pid) { }); match my_trap { Some(tx) => { - let _ = tx.send(ExitSignal { from: target, reason: DownReason::NoProc }); + let _ = tx.send(ExitSignal { + from: target, + reason: DownReason::NoProc, + }); } None => request_stop(me), } diff --git a/src/monitor.rs b/src/monitor.rs index f82058e..ac233d3 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -171,7 +171,10 @@ pub fn monitor(target: Pid) -> Monitor { }); if !registered { - let _ = tx.send(Down { pid: target, reason: DownReason::NoProc }); + let _ = tx.send(Down { + pid: target, + reason: DownReason::NoProc, + }); } Monitor { id, target, rx } diff --git a/src/mutex.rs b/src/mutex.rs index 0fabec3..9124882 100644 --- a/src/mutex.rs +++ b/src/mutex.rs @@ -158,7 +158,11 @@ impl TimerTarget for MutexCore { if st.holder == Some(pid) { return; } - match st.waiters.iter().position(|w| w.pid == pid && w.epoch == epoch) { + match st + .waiters + .iter() + .position(|w| w.pid == pid && w.epoch == epoch) + { Some(pos) => { st.waiters.remove(pos); true @@ -246,7 +250,10 @@ impl Mutex { Some(v) => v, None => panic!("smarm: Mutex value missing on free fast path (core corrupt)"), }; - return Ok(MutexGuard { mutex: self, value: Some(value) }); + return Ok(MutexGuard { + mutex: self, + value: Some(value), + }); } } @@ -287,7 +294,10 @@ impl Mutex { Some(v) => v, None => panic!("smarm: Mutex value missing after grant (core corrupt)"), }; - Ok(MutexGuard { mutex: self, value: Some(value) }) + Ok(MutexGuard { + mutex: self, + value: Some(value), + }) } else { Err(LockTimeout) } @@ -315,7 +325,10 @@ impl Mutex { Some(v) => v, None => panic!("smarm: Mutex value missing on try_lock free path (core corrupt)"), }; - Some(MutexGuard { mutex: self, value: Some(value) }) + Some(MutexGuard { + mutex: self, + value: Some(value), + }) } /// Blocking fallback used when called outside the smarm runtime. @@ -329,10 +342,15 @@ impl Mutex { Ok(mut g) => g.take(), Err(e) => panic!("smarm: mutex value lock poisoned (core corrupt): {e}"), }; - if let Some(v) = v { break v; } + if let Some(v) = v { + break v; + } std::thread::yield_now(); }; - Ok(MutexGuard { mutex: self, value: Some(value) }) + Ok(MutexGuard { + mutex: self, + value: Some(value), + }) } } @@ -342,7 +360,10 @@ impl Clone for Mutex { /// lock and one protected value; locking through any clone excludes /// every other clone. fn clone(&self) -> Self { - Self { core: self.core.clone(), value: self.value.clone() } + Self { + core: self.core.clone(), + value: self.value.clone(), + } } } @@ -388,9 +409,7 @@ impl std::fmt::Debug for MutexGuard<'_, T> { Some(v) => v, None => panic!("smarm: MutexGuard value missing (core corrupt)"), }; - f.debug_tuple("MutexGuard") - .field(value) - .finish() + f.debug_tuple("MutexGuard").field(value).finish() } } diff --git a/src/park.rs b/src/park.rs index f411fac..c8c1443 100644 --- a/src/park.rs +++ b/src/park.rs @@ -105,7 +105,9 @@ mod parker { impl Parker { pub(super) fn new() -> Self { - Self { state: AtomicU32::new(EMPTY) } + Self { + state: AtomicU32::new(EMPTY), + } } /// Returns `true` = woken (permit consumed), `false` = timed out. @@ -211,7 +213,10 @@ mod parker { impl Parker { pub(super) fn new() -> Self { - Self { permit: Mutex::new(false), cv: Condvar::new() } + Self { + permit: Mutex::new(false), + cv: Condvar::new(), + } } /// Returns `true` = woken (permit consumed), `false` = timed out. @@ -395,12 +400,10 @@ impl Coordinator { // The CAS is the exactly-one guarantee: whoever clears the bit // owns the wake; a racing wake_one retries on the observed value // (coherence: a failed CAS can never read older than `mask`). - match self.idle.compare_exchange( - mask, - mask & !bit, - Ordering::AcqRel, - Ordering::Acquire, - ) { + match self + .idle + .compare_exchange(mask, mask & !bit, Ordering::AcqRel, Ordering::Acquire) + { Ok(_) => { self.parkers[id].unpark(); return true; @@ -467,7 +470,8 @@ impl Coordinator { // with the deadline still NO_DEADLINE compares `new < MAX` = true // and over-wakes — the benign direction. (Under the mandated timer // serialization this interleaving cannot occur anyway.) - self.tk_armed.store(self.deadline_nanos(deadline), Ordering::SeqCst); + self.tk_armed + .store(self.deadline_nanos(deadline), Ordering::SeqCst); true } @@ -578,7 +582,10 @@ mod tests { let t0 = Instant::now(); let r = c.park(0, None, || false); assert_eq!(r, ParkResult::Woken); - assert!(t0.elapsed() < Duration::from_millis(100), "park blocked despite permit"); + assert!( + t0.elapsed() < Duration::from_millis(100), + "park blocked despite permit" + ); } #[test] @@ -632,14 +639,20 @@ mod tests { // Wait until all four are published idle. let t0 = Instant::now(); while c.idle_mask().count_ones() != N as u32 { - assert!(t0.elapsed() < Duration::from_secs(5), "threads never parked"); + assert!( + t0.elapsed() < Duration::from_secs(5), + "threads never parked" + ); std::thread::yield_now(); } assert!(c.wake_one()); // Exactly one wakes; give the others a beat to (incorrectly) wake. let t0 = Instant::now(); while woken.load(O::SeqCst) == 0 { - assert!(t0.elapsed() < Duration::from_secs(5), "wake_one woke nobody"); + assert!( + t0.elapsed() < Duration::from_secs(5), + "wake_one woke nobody" + ); std::thread::yield_now(); } std::thread::sleep(Duration::from_millis(100)); @@ -706,7 +719,10 @@ mod tests { assert_eq!(c.armed_deadline_nanos(), c.deadline_nanos(d2)); c.disarm_timer(0); assert_eq!(c.armed_deadline_nanos(), NO_DEADLINE); - assert!(c.try_arm_timer(1, d1), "role must be re-takeable after disarm"); + assert!( + c.try_arm_timer(1, d1), + "role must be re-takeable after disarm" + ); c.disarm_timer(1); } @@ -721,7 +737,10 @@ mod tests { let t0 = Instant::now(); let r = c.park(0, Some(far), || false); assert_eq!(r, ParkResult::Woken, "re-arm wake lost"); - assert!(t0.elapsed() < Duration::from_secs(5), "slept toward the stale deadline"); + assert!( + t0.elapsed() < Duration::from_secs(5), + "slept toward the stale deadline" + ); c.disarm_timer(0); } @@ -796,7 +815,11 @@ mod tests { assert!(c.try_arm_timer(0, far)); c.note_deadline(near); let r = c.park(0, Some(far), || false); - assert_eq!(r, ParkResult::Woken, "re-arm wake lost through note_deadline"); + assert_eq!( + r, + ParkResult::Woken, + "re-arm wake lost through note_deadline" + ); c.disarm_timer(0); } } diff --git a/src/pg.rs b/src/pg.rs index 7581b42..2b75f6e 100644 --- a/src/pg.rs +++ b/src/pg.rs @@ -221,7 +221,9 @@ pub(crate) struct ProcessGroups { impl ProcessGroups { pub(crate) fn new() -> Self { - Self { groups: HashMap::new() } + Self { + groups: HashMap::new(), + } } /// Insert `ms` into `group`. Idempotent on the *member*: if the member is @@ -323,20 +325,33 @@ impl ProcessGroups { fn members_where(&self, group: &str, mut is_live: impl FnMut(Pid) -> bool) -> Vec { self.groups .get(group) - .map(|v| v.iter().map(|e| e.member.pid).filter(|&p| is_live(p)).collect()) + .map(|v| { + v.iter() + .map(|e| e.member.pid) + .filter(|&p| is_live(p)) + .collect() + }) .unwrap_or_default() } /// The first live member of `group` in insertion order — stateless /// first-live `pick`, with the same read-path backstop as `members_where`. fn first_member_where(&self, group: &str, mut is_live: impl FnMut(Pid) -> bool) -> Option { - self.groups.get(group)?.iter().map(|e| e.member.pid).find(|&p| is_live(p)) + self.groups + .get(group)? + .iter() + .map(|e| e.member.pid) + .find(|&p| is_live(p)) } } /// Build the full member identity for `pid` from runtime identity. fn member_for(inner: &crate::runtime::RuntimeInner, pid: Pid) -> Member { - Member { node: inner.node_id, incarnation: inner.incarnation, pid } + Member { + node: inner.node_id, + incarnation: inner.incarnation, + pid, + } } /// Is `pid` a live actor right now? Generation-checked atomic slot-word read, @@ -367,7 +382,10 @@ pub fn join(group: impl Into, pid: Pid) -> bool { let mon = monitor(pid); let (rejected, reaped) = with_runtime(|inner| { - let ms = Membership { member: member_for(inner, pid), monitor: mon }; + let ms = Membership { + member: member_for(inner, pid), + monitor: mon, + }; let mut pg = inner.process_groups.lock(); let reaped = pg.reap_group(&group); let rejected = pg.join(&group, ms); @@ -507,7 +525,11 @@ mod tests { let (tx, rx) = channel::(); let ms = Membership { member: member(index, generation), - monitor: Monitor { id: MonitorId(0), target: pid, rx }, + monitor: Monitor { + id: MonitorId(0), + target: pid, + rx, + }, }; (ms, tx) } @@ -518,7 +540,10 @@ mod tests { let (a, _ta) = synth(1, 0); let (b, _tb) = synth(1, 0); assert!(pg.join("workers", a).is_none(), "first join inserts"); - assert!(pg.join("workers", b).is_some(), "second identical join is handed back"); + assert!( + pg.join("workers", b).is_some(), + "second identical join is handed back" + ); assert_eq!(pg.members_of("workers"), vec![member(1, 0)]); } @@ -542,7 +567,10 @@ mod tests { let (a, _ta) = synth(1, 0); let (b, _tb) = synth(1, 1); assert!(pg.join("g", a).is_none()); - assert!(pg.join("g", b).is_none(), "different generation is a distinct member"); + assert!( + pg.join("g", b).is_none(), + "different generation is a distinct member" + ); assert_eq!(pg.members_of("g"), vec![member(1, 0), member(1, 1)]); } @@ -555,16 +583,27 @@ mod tests { pg.join("g", b); assert!(pg.leave("g", member(1, 0)).is_some()); assert_eq!(pg.members_of("g"), vec![member(2, 0)]); - assert!(pg.leave("g", member(1, 0)).is_none(), "second leave finds nothing"); + assert!( + pg.leave("g", member(1, 0)).is_none(), + "second leave finds nothing" + ); assert!(pg.leave("g", member(2, 0)).is_some()); assert!(pg.members_of("g").is_empty(), "group is now empty"); - assert!(pg.leave("never", member(9, 0)).is_none(), "leaving an unknown group is a no-op"); + assert!( + pg.leave("never", member(9, 0)).is_none(), + "leaving an unknown group is a no-op" + ); } #[test] fn remove_where_sweeps_every_group() { let mut pg = ProcessGroups::new(); - for (g, (m, _t)) in [("a", synth(1, 0)), ("a", synth(2, 0)), ("b", synth(1, 0)), ("c", synth(3, 0))] { + for (g, (m, _t)) in [ + ("a", synth(1, 0)), + ("a", synth(2, 0)), + ("b", synth(1, 0)), + ("c", synth(3, 0)), + ] { pg.join(g, m); } // Death of pid index 1 (any generation) evicts it everywhere. @@ -582,8 +621,16 @@ mod tests { let pid = Pid::new(1, 0); let (tx, rx) = channel::(); let dead = Membership { - member: Member { node: DEFAULT_NODE_ID, incarnation: Incarnation::new(7), pid }, - monitor: Monitor { id: MonitorId(0), target: pid, rx }, + member: Member { + node: DEFAULT_NODE_ID, + incarnation: Incarnation::new(7), + pid, + }, + monitor: Monitor { + id: MonitorId(0), + target: pid, + rx, + }, }; let _keep = tx; let (live, _tl) = synth(2, 0); @@ -614,9 +661,17 @@ mod tests { pg.join("b", b1); // pid 1 dies: its group-a monitor receives a Down. Its group-b monitor // has not — reap must still sweep pid 1 out of b by the pid predicate. - ta1.send(Down { pid: Pid::new(1, 0), reason: DownReason::Exit }).unwrap(); + ta1.send(Down { + pid: Pid::new(1, 0), + reason: DownReason::Exit, + }) + .unwrap(); let evicted = pg.reap_group("a"); - assert_eq!(evicted.len(), 2, "pid 1's memberships in both a and b are evicted"); + assert_eq!( + evicted.len(), + 2, + "pid 1's memberships in both a and b are evicted" + ); assert_eq!(pg.members_of("a"), vec![member(2, 0)]); assert!(pg.members_of("b").is_empty(), "swept from b too; pruned"); } @@ -646,8 +701,16 @@ mod tests { let dead = Pid::new(1, 0); let oracle = |pid: Pid| pid != dead; - assert_eq!(pg.members_where("g", oracle), vec![Pid::new(2, 0)], "dead pid filtered from read"); - assert_eq!(pg.first_member_where("g", oracle), Some(Pid::new(2, 0)), "pick skips the dead first member"); + assert_eq!( + pg.members_where("g", oracle), + vec![Pid::new(2, 0)], + "dead pid filtered from read" + ); + assert_eq!( + pg.first_member_where("g", oracle), + Some(Pid::new(2, 0)), + "pick skips the dead first member" + ); // Backstop does not evict — that stays the monitor's job; raw storage // still holds both until reap runs. diff --git a/src/pid.rs b/src/pid.rs index 18e4158..96b3f21 100644 --- a/src/pid.rs +++ b/src/pid.rs @@ -79,7 +79,10 @@ impl Pid { /// here; typing happens at typed-actor boundaries via [`Pid::from_raw`]. #[inline] pub const fn new(index: u32, generation: u32) -> Self { - Self { raw: RawPid::new(index, generation), _marker: PhantomData } + Self { + raw: RawPid::new(index, generation), + _marker: PhantomData, + } } } @@ -90,7 +93,10 @@ impl Pid { /// resolution paths. #[inline] pub(crate) const fn from_raw(raw: RawPid) -> Self { - Self { raw, _marker: PhantomData } + Self { + raw, + _marker: PhantomData, + } } /// The raw identity, dropping the actor type — the key for identity-only @@ -192,7 +198,10 @@ impl Name { /// associated constants at call sites. #[inline] pub const fn new(name: &'static str) -> Self { - Self { name, _marker: PhantomData } + Self { + name, + _marker: PhantomData, + } } /// The underlying registry key. diff --git a/src/raw_mutex.rs b/src/raw_mutex.rs index 628c898..3dafe9b 100644 --- a/src/raw_mutex.rs +++ b/src/raw_mutex.rs @@ -166,7 +166,10 @@ impl RawMutex { { self.lock_slow(); } - RawMutexGuard { m: self, prev_preempt } + RawMutexGuard { + m: self, + prev_preempt, + } } #[cold] diff --git a/src/registry.rs b/src/registry.rs index b104079..7c20b1d 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -1,4 +1,3 @@ - //! Give an actor a name so other actors can find it and message it. //! //! Without the registry, the only way to reach an actor is to already be @@ -196,7 +195,9 @@ impl std::fmt::Display for SendError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SendError::Unresolved(_) => write!(f, "no live actor registered under that name"), - SendError::Dead(_) => write!(f, "the addressed actor is no longer the live incarnation"), + SendError::Dead(_) => { + write!(f, "the addressed actor is no longer the live incarnation") + } SendError::NoChannel(_) => write!(f, "actor has no channel for this message type"), SendError::Closed(_) => write!(f, "the actor's channel for this type is closed"), SendError::NoMember(_) => write!(f, "no live member in the process group"), @@ -243,7 +244,10 @@ struct Mailbox { impl Mailbox { fn new(pid: Pid) -> Self { - Self { pid, channels: HashMap::new() } + Self { + pid, + channels: HashMap::new(), + } } /// Clone the `Sender` for this actor, if it has one. Called **under the @@ -294,7 +298,10 @@ pub(crate) struct Registry { impl Registry { pub(crate) fn new() -> Self { - Self { by_index: HashMap::new(), by_name: HashMap::new() } + Self { + by_index: HashMap::new(), + by_name: HashMap::new(), + } } /// Drop a dead holder's artifacts: every name bound to it, and its @@ -303,7 +310,11 @@ impl Registry { /// wholesale on pid mismatch) and is left untouched. fn prune_holder(&mut self, holder: Pid) { self.by_name.retain(|_, p| *p != holder); - if self.by_index.get(&holder.index()).is_some_and(|mb| mb.pid == holder) { + if self + .by_index + .get(&holder.index()) + .is_some_and(|mb| mb.pid == holder) + { self.by_index.remove(&holder.index()); } } @@ -351,7 +362,11 @@ impl Registry { .iter() .filter_map(|(&n, &p)| (p == mb.pid).then_some(n)) .collect(); - Some(MailboxInfo { pid: mb.pid, names, depth: depth.min(u32::MAX as usize) as u32 }) + Some(MailboxInfo { + pid: mb.pid, + names, + depth: depth.min(u32::MAX as usize) as u32, + }) } } @@ -428,13 +443,19 @@ pub(crate) fn register_with( /// index from a dead prior incarnation (pid mismatch) is replaced wholesale. /// Caller holds the registry lock and has established that `me` is live. fn publish_channel(reg: &mut Registry, me: Pid, tx: Sender) { - let mb = reg.by_index.entry(me.index()).or_insert_with(|| Mailbox::new(me)); + let mb = reg + .by_index + .entry(me.index()) + .or_insert_with(|| Mailbox::new(me)); if mb.pid != me { *mb = Mailbox::new(me); } mb.channels.insert( TypeId::of::(), - Channel { sender: Box::new(tx), msg_type: type_name::() }, + Channel { + sender: Box::new(tx), + msg_type: type_name::(), + }, ); } @@ -473,7 +494,10 @@ pub fn install(tx: Sender) -> Pid { pub(crate) fn install_for(pid: Pid, tx: Sender) { with_runtime(|inner| { let mut reg = inner.registry.lock(); - debug_assert!(live(inner, pid), "install_for: pid must be a freshly spawned, live actor"); + debug_assert!( + live(inner, pid), + "install_for: pid must be a freshly spawned, live actor" + ); publish_channel::(&mut reg, pid, tx); }); } @@ -573,7 +597,10 @@ pub(crate) fn resolve_named_sender(name: &str) -> Option<(Pid } // A live holder's mailbox is its own (publish replaces wholesale on // pid mismatch, and one live actor per slot), so index lookup is safe. - let tx = reg.by_index.get(&pid.index()).and_then(Mailbox::clone_sender::)?; + let tx = reg + .by_index + .get(&pid.index()) + .and_then(Mailbox::clone_sender::)?; Some((pid, tx)) }) } @@ -586,7 +613,11 @@ pub fn unregister(name: &str) -> Option { with_runtime(|inner| { let mut reg = inner.registry.lock(); let pid = reg.by_name.remove(name)?; - if live(inner, pid) { Some(pid) } else { None } + if live(inner, pid) { + Some(pid) + } else { + None + } }) } @@ -618,12 +649,17 @@ pub fn send(name: Name, msg: M) -> Result<(), SendError reg.prune_holder(pid); return Err(SendError::Unresolved(msg)); } - match reg.by_index.get(&pid.index()).and_then(Mailbox::clone_sender::) { + match reg + .by_index + .get(&pid.index()) + .and_then(Mailbox::clone_sender::) + { Some(tx) => tx, None => return Err(SendError::NoChannel(msg)), } }; - tx.send(msg).map_err(|crate::channel::SendError(m)| SendError::Closed(m)) + tx.send(msg) + .map_err(|crate::channel::SendError(m)| SendError::Closed(m)) }) } @@ -647,7 +683,11 @@ fn send_to_pid( match reg.by_index.get(&pid.index()).map(|m| m.pid) { // Exact incarnation, still alive: its `M` channel, or NoChannel. Some(stored) if stored == pid && live(inner, pid) => { - match reg.by_index.get(&pid.index()).and_then(Mailbox::clone_sender::) { + match reg + .by_index + .get(&pid.index()) + .and_then(Mailbox::clone_sender::) + { Some(tx) => tx, None => return Err(SendError::NoChannel(msg)), } @@ -662,7 +702,8 @@ fn send_to_pid( _ => return Err(SendError::Dead(msg)), } }; - tx.send(msg).map_err(|crate::channel::SendError(m)| SendError::Closed(m)) + tx.send(msg) + .map_err(|crate::channel::SendError(m)| SendError::Closed(m)) } /// Deliver `msg` directly to the exact actor identified by `pid`. Unlike diff --git a/src/run_queue.rs b/src/run_queue.rs index 4df4bc8..16656e5 100644 --- a/src/run_queue.rs +++ b/src/run_queue.rs @@ -222,7 +222,10 @@ impl MpmcRing { if diff == 0 { // Our turn: claim the position. match self.enqueue_pos.0.compare_exchange_weak( - pos, pos + 1, Ordering::Relaxed, Ordering::Relaxed, + pos, + pos + 1, + Ordering::Relaxed, + Ordering::Relaxed, ) { Ok(_) => { // SAFETY: the claim gives us exclusive write access @@ -250,7 +253,10 @@ impl MpmcRing { let diff = seq as isize - (pos + 1) as isize; if diff == 0 { match self.dequeue_pos.0.compare_exchange_weak( - pos, pos + 1, Ordering::Relaxed, Ordering::Relaxed, + pos, + pos + 1, + Ordering::Relaxed, + Ordering::Relaxed, ) { Ok(_) => { // SAFETY: the claim gives us exclusive read access; @@ -464,19 +470,36 @@ mod tests { let popped = popped.lock().unwrap(); assert_eq!(popped.len(), total, "count mismatch"); - let set: HashSet = popped.iter().map(|p| ((p.index() as u64) << 32) | p.generation() as u64).collect(); + let set: HashSet = popped + .iter() + .map(|p| ((p.index() as u64) << 32) | p.generation() as u64) + .collect(); assert_eq!(set.len(), total, "duplicate or lost element"); assert_eq!(pop(&q), None); } #[test] fn mpmc_exactly_once_contended() { - exactly_once(MpmcRing::new(8, 4096), |q, p| q.push(p), |q| q.pop(), 4, 4, 1000); + exactly_once( + MpmcRing::new(8, 4096), + |q, p| q.push(p), + |q| q.pop(), + 4, + 4, + 1000, + ); } #[test] fn striped_exactly_once_contended() { - exactly_once(StripedRing::new(8, 4096), |q, p| q.push(p), |q| q.pop(), 4, 4, 1000); + exactly_once( + StripedRing::new(8, 4096), + |q, p| q.push(p), + |q| q.pop(), + 4, + 4, + 1000, + ); } #[test] diff --git a/src/runtime.rs b/src/runtime.rs index d6c7b6c..34d205b 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -112,10 +112,11 @@ //! deadline, so an expiry wakes one scheduler, not a herd. use crate::actor::{ - clear_current_pid, is_actor_done, reset_actor_done, set_current_actor_box, - set_current_pid, take_last_outcome, Actor, Outcome, + clear_current_pid, is_actor_done, reset_actor_done, set_current_actor_box, set_current_pid, + take_last_outcome, Actor, Outcome, }; use crate::channel::Sender; +use crate::context::{get_actor_sp, set_actor_sp, switch_to_actor}; use crate::io::IoThread; use crate::monitor::{Down, DownReason, MonitorId}; use crate::pid::Pid; @@ -124,11 +125,8 @@ use crate::raw_mutex::RawMutex; use crate::slot_state::{StateWord, Status, Unpark}; use crate::supervisor::Signal; use crate::timer::Timers; -use crate::context::{get_actor_sp, set_actor_sp, switch_to_actor}; -use std::sync::atomic::{ - AtomicBool, AtomicPtr, AtomicU32, AtomicU64, AtomicUsize, Ordering, -}; +use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use std::thread; @@ -175,7 +173,9 @@ impl Config { pub fn exact(n: usize) -> Self { assert!(n >= 1, "scheduler thread count must be ≥ 1"); Self { - min: n, max: n, exact: Some(n), + min: n, + max: n, + exact: Some(n), alloc_interval: crate::preempt::DEFAULT_ALLOC_INTERVAL, timeslice_cycles: crate::preempt::DEFAULT_TIMESLICE_CYCLES, stack_pool_cap: n * 4, @@ -196,7 +196,9 @@ impl Config { assert!(e >= 1, "exact must be ≥ 1"); } Self { - min, max, exact, + min, + max, + exact, alloc_interval: crate::preempt::DEFAULT_ALLOC_INTERVAL, timeslice_cycles: crate::preempt::DEFAULT_TIMESLICE_CYCLES, stack_pool_cap: max * 4, @@ -322,7 +324,9 @@ impl Default for Config { .map(|n| n.get()) .unwrap_or(1); Self { - min: 1, max: avail, exact: None, + min: 1, + max: avail, + exact: None, alloc_interval: crate::preempt::DEFAULT_ALLOC_INTERVAL, timeslice_cycles: crate::preempt::DEFAULT_TIMESLICE_CYCLES, stack_pool_cap: avail * 4, @@ -376,7 +380,9 @@ pub struct RuntimeStats { impl RuntimeStats { /// Sum of run queue lengths across all scheduler threads. pub fn total_run_queue_len(&self) -> u64 { - self.inner.stats.iter() + self.inner + .stats + .iter() .map(|s| s.run_queue_len.load(Ordering::Relaxed)) .sum() } @@ -400,7 +406,9 @@ impl RuntimeStats { /// scheduler threads. Counters are reset at the start of each `run()`, /// so after a run this reads that run's total. pub fn slot_hits(&self) -> u64 { - self.inner.stats.iter() + self.inner + .stats + .iter() .map(|s| s.slot_hits.load(Ordering::Relaxed)) .sum() } @@ -408,7 +416,9 @@ impl RuntimeStats { /// RFC 005: total slot occupants displaced to the shared queue, summed /// across scheduler threads. Reset at the start of each `run()`. pub fn slot_displacements(&self) -> u64 { - self.inner.stats.iter() + self.inner + .stats + .iter() .map(|s| s.slot_displacements.load(Ordering::Relaxed)) .sum() } @@ -713,7 +723,8 @@ impl Slot { #[inline] pub(crate) fn record_message(&self) { let v = self.messages_received.load(Ordering::Relaxed); - self.messages_received.store(v.wrapping_add(1), Ordering::Relaxed); + self.messages_received + .store(v.wrapping_add(1), Ordering::Relaxed); } /// Read the received-message tally (Relaxed; cross-thread snapshot read). @@ -732,7 +743,8 @@ impl Slot { #[inline] pub(crate) fn add_budget(&self, cycles: u64) { let v = self.budget_cycles.load(Ordering::Relaxed); - self.budget_cycles.store(v.wrapping_add(cycles), Ordering::Relaxed); + self.budget_cycles + .store(v.wrapping_add(cycles), Ordering::Relaxed); } /// Read the accumulated budget cycles (Relaxed). Always 0 unless the @@ -871,7 +883,6 @@ impl Slot { Some(*unsafe { Box::from_raw(raw) }) } } - } // --------------------------------------------------------------------------- @@ -1270,11 +1281,13 @@ impl Runtime { // Re-initialise shared state for this run. assert_eq!( - self.inner.run_queue.len(), 0, + self.inner.run_queue.len(), + 0, "run() called while previous run still active" ); debug_assert_eq!( - self.inner.live_actors.load(Ordering::Acquire), 0, + self.inner.live_actors.load(Ordering::Acquire), + 0, "run() called while previous run still active" ); // RFC 018: the IO producers reach the runtime (slot table + unpark) @@ -1421,7 +1434,9 @@ impl Runtime { /// Snapshot of runtime statistics for introspection / tests. pub fn stats(&self) -> RuntimeStats { - RuntimeStats { inner: self.inner.clone() } + RuntimeStats { + inner: self.inner.clone(), + } } } @@ -1456,7 +1471,10 @@ thread_local! { } #[derive(Copy, Clone)] -pub(crate) enum YieldIntent { Yield, Park } +pub(crate) enum YieldIntent { + Yield, + Park, +} pub(crate) fn set_yield_intent(i: YieldIntent) { YIELD_INTENT.with(|c| c.set(i)); @@ -1491,7 +1509,10 @@ pub const ROOT_PID: Pid = Pid::new(u32::MAX, u32::MAX); /// for writes after reclaim — so an over-eager mark costs a cancel-write, /// never data. fn maybe_shrink_stack(slot: &Slot) { - let parks = slot.parks_since_shrink.load(Ordering::Relaxed).saturating_add(1); + let parks = slot + .parks_since_shrink + .load(Ordering::Relaxed) + .saturating_add(1); slot.parks_since_shrink.store(parks, Ordering::Relaxed); let sp = slot.sp.load(Ordering::Relaxed); @@ -1590,12 +1611,19 @@ pub(crate) fn install_actor( // publish below. let (diag_reserve, diag_guard) = stack.shape(); let diag_top = stack.top() as usize; - slot.stop_ptr.store(Arc::as_ptr(&stop) as *mut _, Ordering::Release); + slot.stop_ptr + .store(Arc::as_ptr(&stop) as *mut _, Ordering::Release); { let mut cold = slot.cold.lock(); debug_assert!(cold.actor.is_none(), "install over live actor"); debug_assert!(cold.waiters.is_empty() && cold.monitors.is_empty() && cold.links.is_empty()); - cold.actor = Some(Actor { pid, stack, supervisor, stop, trap: None }); + cold.actor = Some(Actor { + pid, + stack, + supervisor, + stop, + trap: None, + }); cold.outstanding_handles = 1; cold.outcome = None; cold.pending_io_result = None; @@ -1607,9 +1635,11 @@ pub(crate) fn install_actor( slot.parks_since_shrink.store(0, Ordering::Relaxed); slot.shrink_count.store(0, Ordering::Relaxed); slot.diag_stack_top.store(diag_top, Ordering::Relaxed); - slot.diag_stack_reserve.store(diag_reserve, Ordering::Relaxed); + slot.diag_stack_reserve + .store(diag_reserve, Ordering::Relaxed); slot.diag_stack_guard.store(diag_guard, Ordering::Relaxed); - slot.diag_pid.store(((idx as u64) << 32) | gen as u64, Ordering::Relaxed); + slot.diag_pid + .store(((idx as u64) << 32) | gen as u64, Ordering::Relaxed); slot.store_closure(closure); slot.reset_counters(); inner.live_actors.fetch_add(1, Ordering::Relaxed); @@ -1618,7 +1648,10 @@ pub(crate) fn install_actor( // Release store orders everything above before any Acquire reader. slot.word.publish_queued(gen); inner.enqueue(pid); - crate::te!(crate::trace::Event::Spawn { parent: supervisor, child: pid }); + crate::te!(crate::trace::Event::Spawn { + parent: supervisor, + child: pid + }); pid } @@ -1635,14 +1668,19 @@ pub(crate) fn install_actor( /// is released — a last-sender drop can unpark a receiver, which takes the /// run-queue mutex; legal under a cold lock, but pointless to nest. pub(crate) fn reclaim_slot(inner: &RuntimeInner, pid: Pid) { - let Some(slot) = inner.slot_at(pid) else { return }; + let Some(slot) = inner.slot_at(pid) else { + return; + }; let dropped_outside; { let mut cold = slot.cold.lock(); if slot.status_for(pid) != Status::Done || cold.outstanding_handles != 0 { return; // already reclaimed, or not yet eligible } - debug_assert!(cold.actor.is_none(), "reclaiming a slot that still owns an actor"); + debug_assert!( + cold.actor.is_none(), + "reclaiming a slot that still owns an actor" + ); dropped_outside = ( cold.outcome.take(), cold.supervisor_channel.take(), @@ -1741,7 +1779,10 @@ fn finalize_actor(inner: &Arc, pid: Pid, outcome: Outcome) { // Notify monitors. Sent outside any slot lock: `send` may unpark a parked // receiver, which takes the run-queue mutex. for (_, m) in monitors { - let _ = m.send(Down { pid, reason: down_reason }); + let _ = m.send(Down { + pid, + reason: down_reason, + }); } // Walk linked peers ONE AT A TIME (cold locks are leaves). For every @@ -1773,7 +1814,10 @@ fn finalize_actor(inner: &Arc, pid: Pid, outcome: Outcome) { }; match trap { Some(Some(tx)) => { - let _ = tx.send(crate::link::ExitSignal { from: pid, reason: down_reason }); + let _ = tx.send(crate::link::ExitSignal { + from: pid, + reason: down_reason, + }); } Some(None) => crate::scheduler::request_stop(peer), None => {} @@ -1929,7 +1973,9 @@ fn schedule_loop(inner: &Arc, slot_idx: usize) { let io_out = inner.io_outstanding.load(Ordering::Acquire) + inner.io_fd_waiters.load(Ordering::Acquire); - stats.run_queue_len.store(inner.run_queue.len(), Ordering::Relaxed); + stats + .run_queue_len + .store(inner.run_queue.len(), Ordering::Relaxed); let pop = match inner.run_queue.pop() { Some(pid) => Pop::Got(pid), None => { @@ -2081,7 +2127,9 @@ fn schedule_loop(inner: &Arc, slot_idx: usize) { } // Update per-thread stats: record who's on-CPU. - stats.current_pid_index.store(pid.index(), Ordering::Relaxed); + stats + .current_pid_index + .store(pid.index(), Ordering::Relaxed); set_actor_sp(sp); set_current_pid(pid); diff --git a/src/scheduler.rs b/src/scheduler.rs index 049aafd..02329c9 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -68,9 +68,7 @@ use crate::actor::current_pid; use crate::channel::Sender; use crate::pid::{Name, Pid}; -use crate::runtime::{ - self, RuntimeInner, YieldIntent, RUNTIME, -}; +use crate::runtime::{self, RuntimeInner, YieldIntent, RUNTIME}; use crate::supervisor::Signal; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -152,7 +150,9 @@ pub struct JoinHandle { impl JoinHandle { /// The identity of the actor this handle refers to. - pub fn pid(&self) -> Pid { self.pid } + pub fn pid(&self) -> Pid { + self.pid + } /// Block the calling actor until the spawned actor finishes, then /// report how it finished: `Ok(())` if it returned normally or stopped @@ -182,12 +182,10 @@ impl JoinHandle { crate::slot_state::Status::Stale => { panic!("join: target slot has been reused") } - crate::slot_state::Status::Done => { - Some(match cold.outcome.take() { - Some(outcome) => outcome, - None => panic!("Done slot must have outcome"), - }) - } + crate::slot_state::Status::Done => Some(match cold.outcome.take() { + Some(outcome) => outcome, + None => panic!("Done slot must have outcome"), + }), crate::slot_state::Status::Live => { // begin_wait is lock-free, legal under the cold lock; // registering under it makes the epoch atomic with @@ -227,8 +225,7 @@ impl JoinHandle { match slot.status_for(self.pid) { crate::slot_state::Status::Stale => false, status => { - cold.outstanding_handles = - cold.outstanding_handles.saturating_sub(1); + cold.outstanding_handles = cold.outstanding_handles.saturating_sub(1); cold.outstanding_handles == 0 && status == crate::slot_state::Status::Done } @@ -305,9 +302,7 @@ pub fn spawn(f: impl FnOnce() + Send + 'static) -> JoinHandle { /// [`spawn`] with per-actor stack shape overrides (RFC 019). pub fn spawn_with(opts: SpawnOpts, f: impl FnOnce() + Send + 'static) -> JoinHandle { - let parent = current_pid().unwrap_or_else(|| { - with_runtime(|_| crate::runtime::ROOT_PID) - }); + let parent = current_pid().unwrap_or_else(|| with_runtime(|_| crate::runtime::ROOT_PID)); spawn_under_with(parent, opts, f) } @@ -339,7 +334,10 @@ pub fn spawn_under_with( crate::runtime::install_actor(inner, idx, sp, stack, supervisor, closure) }); - JoinHandle { pid, consumed: false } + JoinHandle { + pid, + consumed: false, + } } /// Spawn an actor that other actors can message directly by its [`Pid`], @@ -566,11 +564,9 @@ pub fn sleep(duration: std::time::Duration) { let _np = NoPreempt::enter(); let epoch = begin_wait(); let deadline = crate::timer::deadline_from_now(duration); - with_runtime(|inner| { - match inner.timers.lock() { - Ok(mut timers) => timers.insert_sleep(deadline, me, epoch), - Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), - } + with_runtime(|inner| match inner.timers.lock() { + Ok(mut timers) => timers.insert_sleep(deadline, me, epoch), + Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), }); park_current(); } @@ -588,11 +584,9 @@ pub fn sleep_wall(duration: std::time::Duration) { let _np = NoPreempt::enter(); let epoch = begin_wait(); let deadline = crate::timer::deadline_from_now(duration); - with_runtime(|inner| { - match inner.timers.lock() { - Ok(mut timers) => timers.insert_sleep_wall(deadline, me, epoch), - Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), - } + with_runtime(|inner| match inner.timers.lock() { + Ok(mut timers) => timers.insert_sleep_wall(deadline, me, epoch), + Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), }); park_current(); } @@ -608,15 +602,13 @@ pub fn insert_wait_timer( target: std::sync::Arc, epoch: u32, ) { - with_runtime(|inner| { - match inner.timers.lock() { - Ok(mut timers) => timers.insert( - deadline, - pid, - crate::timer::Reason::WaitTimeout { target, epoch }, - ), - Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), - } + with_runtime(|inner| match inner.timers.lock() { + Ok(mut timers) => timers.insert( + deadline, + pid, + crate::timer::Reason::WaitTimeout { target, epoch }, + ), + Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), }); } @@ -646,11 +638,9 @@ pub fn send_after( let fire = Box::new(move || { let _ = crate::registry::send_to(dest, msg); }); - with_runtime(|inner| { - match inner.timers.lock() { - Ok(mut timers) => timers.insert_send(deadline, dest.erase(), fire), - Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), - } + with_runtime(|inner| match inner.timers.lock() { + Ok(mut timers) => timers.insert_send(deadline, dest.erase(), fire), + Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), }) } @@ -670,11 +660,9 @@ pub fn send_after_named( let fire = Box::new(move || { let _ = crate::registry::send(dest, msg); }); - with_runtime(|inner| { - match inner.timers.lock() { - Ok(mut timers) => timers.insert_send(deadline, armed_by, fire), - Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), - } + with_runtime(|inner| match inner.timers.lock() { + Ok(mut timers) => timers.insert_send(deadline, armed_by, fire), + Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), }) } @@ -693,11 +681,9 @@ pub fn send_after_wall( let fire = Box::new(move || { let _ = crate::registry::send_to(dest, msg); }); - with_runtime(|inner| { - match inner.timers.lock() { - Ok(mut timers) => timers.insert_send_wall(deadline, dest.erase(), fire), - Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), - } + with_runtime(|inner| match inner.timers.lock() { + Ok(mut timers) => timers.insert_send_wall(deadline, dest.erase(), fire), + Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), }) } @@ -714,11 +700,9 @@ pub fn send_after_named_wall( let fire = Box::new(move || { let _ = crate::registry::send(dest, msg); }); - with_runtime(|inner| { - match inner.timers.lock() { - Ok(mut timers) => timers.insert_send_wall(deadline, armed_by, fire), - Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), - } + with_runtime(|inner| match inner.timers.lock() { + Ok(mut timers) => timers.insert_send_wall(deadline, armed_by, fire), + Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), }) } @@ -738,11 +722,9 @@ pub(crate) fn send_after_to( let fire = Box::new(move || { let _ = tx.send(msg); }); - with_runtime(|inner| { - match inner.timers.lock() { - Ok(mut timers) => timers.insert_send(deadline, armed_by, fire), - Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), - } + with_runtime(|inner| match inner.timers.lock() { + Ok(mut timers) => timers.insert_send(deadline, armed_by, fire), + Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), }) } @@ -750,11 +732,9 @@ pub(crate) fn send_after_to( /// it fires. Returns `true` if the timer was still pending and delivery is /// now prevented, `false` if it had already fired or was already cancelled. pub fn cancel_timer(id: crate::timer::TimerId) -> bool { - with_runtime(|inner| { - match inner.timers.lock() { - Ok(mut timers) => timers.cancel(id), - Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), - } + with_runtime(|inner| match inner.timers.lock() { + Ok(mut timers) => timers.cancel(id), + Err(e) => panic!("smarm: timers lock poisoned (core corrupt): {e}"), }) } @@ -820,7 +800,8 @@ where }; let mut cold = slot.cold.lock(); debug_assert_eq!( - slot.generation(), me.generation(), + slot.generation(), + me.generation(), "block_on_io: own slot reused mid-park" ); match cold.pending_io_result.take() { @@ -946,12 +927,20 @@ pub struct FdArm { impl FdArm { /// An arm that becomes ready when `fd` is readable. pub fn readable(fd: std::os::fd::RawFd) -> Self { - FdArm { fd, readable: true, writable: false } + FdArm { + fd, + readable: true, + writable: false, + } } /// An arm that becomes ready when `fd` is writable. pub fn writable(fd: std::os::fd::RawFd) -> Self { - FdArm { fd, readable: false, writable: true } + FdArm { + fd, + readable: false, + writable: true, + } } } @@ -976,9 +965,7 @@ impl crate::channel::Selectable for FdArm { match io.as_mut() { Some(io) => { inner.io_fd_waiters.fetch_add(1, Ordering::AcqRel); - let r = io.epoll_register( - self.fd, pid, epoch, self.readable, self.writable, - ); + let r = io.epoll_register(self.fd, pid, epoch, self.readable, self.writable); if r.is_err() { inner.io_fd_waiters.fetch_sub(1, Ordering::AcqRel); } @@ -1033,7 +1020,11 @@ fn poll_events(fd: std::os::fd::RawFd, readable: bool, writable: bool) -> std::i if writable { events |= libc::POLLOUT; } - let mut pfd = libc::pollfd { fd, events, revents: 0 }; + let mut pfd = libc::pollfd { + fd, + events, + revents: 0, + }; loop { let r = unsafe { libc::poll(&mut pfd, 1, 0) }; if r < 0 { @@ -1081,7 +1072,11 @@ pub fn wait_writable_timeout( pub fn read(fd: std::os::fd::RawFd, buf: &mut [u8]) -> std::io::Result { wait_readable(fd)?; let n = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, buf.len()) }; - if n < 0 { Err(std::io::Error::last_os_error()) } else { Ok(n as usize) } + if n < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(n as usize) + } } /// Convenience wrapper: park until `fd` is writable, then perform the @@ -1090,7 +1085,11 @@ pub fn read(fd: std::os::fd::RawFd, buf: &mut [u8]) -> std::io::Result { pub fn write(fd: std::os::fd::RawFd, buf: &[u8]) -> std::io::Result { wait_writable(fd)?; let n = unsafe { libc::write(fd, buf.as_ptr() as *const _, buf.len()) }; - if n < 0 { Err(std::io::Error::last_os_error()) } else { Ok(n as usize) } + if n < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(n as usize) + } } // --------------------------------------------------------------------------- @@ -1099,12 +1098,15 @@ pub fn write(fd: std::os::fd::RawFd, buf: &[u8]) -> std::io::Result { pub fn register_supervisor_channel(pid: Pid, sender: Sender) { with_runtime(|inner| { - let slot = inner.slot_at(pid) + let slot = inner + .slot_at(pid) .unwrap_or_else(|| panic!("register_supervisor_channel: pid {:?} not found", pid)); let mut cold = slot.cold.lock(); assert_eq!( - slot.generation(), pid.generation(), - "register_supervisor_channel: pid {:?} not found", pid + slot.generation(), + pid.generation(), + "register_supervisor_channel: pid {:?} not found", + pid ); cold.supervisor_channel = Some(sender); }); @@ -1177,6 +1179,9 @@ mod send_after_to_tests { crate::sleep(Duration::from_millis(30)); r2.store(true, Ordering::SeqCst); }); - assert!(reached.load(Ordering::SeqCst), "runtime survived the dead-channel fire"); + assert!( + reached.load(Ordering::SeqCst), + "runtime survived the dead-channel fire" + ); } } diff --git a/src/signal.rs b/src/signal.rs index be35144..328d01f 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -214,7 +214,10 @@ struct Buf { impl Buf { fn new() -> Self { - Buf { b: [0; 320], len: 0 } + Buf { + b: [0; 320], + len: 0, + } } fn s(&mut self, s: &str) { for &c in s.as_bytes() { @@ -274,8 +277,14 @@ mod tests { #[test] fn inside_guard_both_edges() { assert_eq!(classify(GUARD_LO, TOP, RESERVE, GUARD), FaultClass::Guard); - assert_eq!(classify(GUARD_HI - 1, TOP, RESERVE, GUARD), FaultClass::Guard); - assert_eq!(classify(GUARD_LO + GUARD / 2, TOP, RESERVE, GUARD), FaultClass::Guard); + assert_eq!( + classify(GUARD_HI - 1, TOP, RESERVE, GUARD), + FaultClass::Guard + ); + assert_eq!( + classify(GUARD_LO + GUARD / 2, TOP, RESERVE, GUARD), + FaultClass::Guard + ); } #[test] diff --git a/src/slot_state.rs b/src/slot_state.rs index 01f65a5..0458744 100644 --- a/src/slot_state.rs +++ b/src/slot_state.rs @@ -188,8 +188,7 @@ impl StateWord { loop { let w = self.load(); debug_assert!( - matches!(word_state(w), ST_RUNNING | ST_RUNNING_NOTIFIED) - && word_gen(w) == gen, + matches!(word_state(w), ST_RUNNING | ST_RUNNING_NOTIFIED) && word_gen(w) == gen, "yield return from invalid word {w:#x}" ); if self @@ -247,8 +246,7 @@ impl StateWord { loop { let w = self.load(); debug_assert!( - matches!(word_state(w), ST_RUNNING | ST_RUNNING_NOTIFIED) - && word_gen(w) == gen, + matches!(word_state(w), ST_RUNNING | ST_RUNNING_NOTIFIED) && word_gen(w) == gen, "begin_wait from invalid word {w:#x}" ); let next = word_epoch(w).wrapping_add(1) & EPOCH_MASK; @@ -342,8 +340,7 @@ impl StateWord { loop { let w = self.load(); debug_assert!( - matches!(word_state(w), ST_RUNNING | ST_RUNNING_NOTIFIED) - && word_gen(w) == gen, + matches!(word_state(w), ST_RUNNING | ST_RUNNING_NOTIFIED) && word_gen(w) == gen, "clear_notify from invalid word {w:#x}" ); if word_state(w) != ST_RUNNING_NOTIFIED { @@ -372,8 +369,7 @@ impl StateWord { pub(crate) fn set_done(&self, gen: u32) { let prev = self.0.swap(pack(gen, 0, ST_DONE), Ordering::AcqRel); debug_assert!( - matches!(word_state(prev), ST_RUNNING | ST_RUNNING_NOTIFIED) - && word_gen(prev) == gen, + matches!(word_state(prev), ST_RUNNING | ST_RUNNING_NOTIFIED) && word_gen(prev) == gen, "finalize from invalid word {prev:#x}" ); } @@ -538,7 +534,11 @@ mod loom_tests { // not a pending notification. assert!(word.try_claim(0)); assert_eq!(word.unpark(0, Some(epoch)), Unpark::Noop); - assert_eq!(word_state(word.load()), ST_RUNNING, "stale epoch notified a live run"); + assert_eq!( + word_state(word.load()), + ST_RUNNING, + "stale epoch notified a live run" + ); }); } diff --git a/src/stack.rs b/src/stack.rs index 2f3386f..8a3f33b 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -57,16 +57,19 @@ impl Stack { } let base = base as *mut u8; - let ret = unsafe { - libc::mprotect(base as *mut libc::c_void, guard_size, libc::PROT_NONE) - }; + let ret = unsafe { libc::mprotect(base as *mut libc::c_void, guard_size, libc::PROT_NONE) }; if ret != 0 { let err = io::Error::last_os_error(); unsafe { libc::munmap(base as *mut libc::c_void, total_size) }; return Err(err); } - Ok(Self { base, total_size, stack_size, guard_size }) + Ok(Self { + base, + total_size, + stack_size, + guard_size, + }) } /// 16-byte-aligned top of the usable region. @@ -177,7 +180,11 @@ pub(crate) fn shrink_range(hwm: usize, sp: usize, page: usize) -> Option<(usize, /// with `stack_size` page-rounded by `Stack::new` the result is always /// page-aligned. Checked math: `retain ≥ stack_size` (notably the default /// 64 KiB reserve with the 64 KiB RETAIN) and overflow collapse to `None`. -pub(crate) fn retain_range(stack_size: usize, retain: usize, page: usize) -> Option<(usize, usize)> { +pub(crate) fn retain_range( + stack_size: usize, + retain: usize, + page: usize, +) -> Option<(usize, usize)> { debug_assert!(page.is_power_of_two()); let retain = retain.checked_add(page - 1)? & !(page - 1); // page_up(retain) let len = stack_size.checked_sub(retain)?; diff --git a/src/supervisor.rs b/src/supervisor.rs index cd92709..8ab32b2 100644 --- a/src/supervisor.rs +++ b/src/supervisor.rs @@ -123,9 +123,9 @@ pub enum Signal { impl std::fmt::Debug for Signal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Signal::Exit(pid) => write!(f, "Signal::Exit({:?})", pid), + Signal::Exit(pid) => write!(f, "Signal::Exit({:?})", pid), Signal::Panic(pid, _) => write!(f, "Signal::Panic({:?}, ..)", pid), - Signal::Stopped(pid) => write!(f, "Signal::Stopped({:?})", pid), + Signal::Stopped(pid) => write!(f, "Signal::Stopped({:?})", pid), } } } @@ -133,9 +133,9 @@ impl std::fmt::Debug for Signal { impl Signal { pub fn pid(&self) -> Pid { match self { - Signal::Exit(p) => *p, + Signal::Exit(p) => *p, Signal::Panic(p, _) => *p, - Signal::Stopped(p) => *p, + Signal::Stopped(p) => *p, } } } @@ -169,7 +169,10 @@ pub struct ChildSpec { impl ChildSpec { pub fn new(restart: Restart, start: impl Fn() + Send + Sync + 'static) -> Self { - Self { start: Arc::new(start), restart } + Self { + start: Arc::new(start), + restart, + } } } @@ -392,4 +395,3 @@ impl OneForOne { } } } - diff --git a/src/timer.rs b/src/timer.rs index e16c431..86b9eb9 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -129,7 +129,9 @@ impl Ord for Entry { // Earlier deadline first; ties broken by insertion order so the // ordering is total. `Reason` and `Pid` deliberately don't // participate. - self.deadline.cmp(&other.deadline).then_with(|| self.seq.cmp(&other.seq)) + self.deadline + .cmp(&other.deadline) + .then_with(|| self.seq.cmp(&other.seq)) } } diff --git a/src/trace.rs b/src/trace.rs index e39c1a3..923cbbd 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -16,13 +16,17 @@ #[cfg(feature = "smarm-trace")] #[macro_export] macro_rules! te { - ($kind:expr) => { $crate::trace::record($kind) }; + ($kind:expr) => { + $crate::trace::record($kind) + }; } #[cfg(not(feature = "smarm-trace"))] #[macro_export] macro_rules! te { - ($kind:expr) => { () }; + ($kind:expr) => { + () + }; } #[cfg(feature = "smarm-trace")] @@ -68,8 +72,8 @@ mod inner { // ----------------------------------------------------------------------- struct Record { - nanos: u64, // ns since open() - tid: u64, // OS thread id + nanos: u64, // ns since open() + tid: u64, // OS thread id event: Event, } @@ -84,8 +88,8 @@ mod inner { // ----------------------------------------------------------------------- struct Global { - sender: mpsc::Sender, - start: Instant, + sender: mpsc::Sender, + start: Instant, } static GLOBAL: Mutex> = Mutex::new(None); @@ -95,7 +99,7 @@ mod inner { // The start Instant is copied alongside it — also one mutex hit per thread. // record() never touches GLOBAL after that. struct LocalState { - tx: mpsc::Sender, + tx: mpsc::Sender, start: Instant, } @@ -109,8 +113,8 @@ mod inner { // ----------------------------------------------------------------------- pub fn open() { - let path = std::env::var("SMARM_TRACE_FILE") - .unwrap_or_else(|_| "smarm_trace.json".to_owned()); + let path = + std::env::var("SMARM_TRACE_FILE").unwrap_or_else(|_| "smarm_trace.json".to_owned()); let (tx, rx) = mpsc::channel::(); let start = Instant::now(); @@ -164,8 +168,11 @@ mod inner { // which would try to re-acquire inner.shared (already held at many // te!() call sites) -> deadlock. Guard at the very top, before any // allocation-capable call. - let was_enabled = crate::preempt::PREEMPTION_ENABLED - .with(|e| { let v = e.get(); e.set(false); v }); + let was_enabled = crate::preempt::PREEMPTION_ENABLED.with(|e| { + let v = e.get(); + e.set(false); + v + }); LOCAL_STATE.with(|cell| { let mut opt = cell.borrow_mut(); @@ -182,7 +189,7 @@ mod inner { } if let Some(ls) = opt.as_ref() { let nanos = ls.start.elapsed().as_nanos() as u64; - let tid = os_tid(); + let tid = os_tid(); let _ = ls.tx.send(Msg::Event(Record { nanos, tid, event })); } }); @@ -197,7 +204,10 @@ mod inner { fn drain_thread(rx: mpsc::Receiver, path: &str) { let f = match std::fs::File::create(path) { Ok(f) => f, - Err(e) => { eprintln!("[smarm-trace] create failed: {}", e); return; } + Err(e) => { + eprintln!("[smarm-trace] create failed: {}", e); + return; + } }; let mut w = std::io::BufWriter::new(f); let _ = writeln!(w, "{{\"traceEvents\":["); @@ -210,7 +220,9 @@ mod inner { Ok(Msg::Event(r)) => { let (name, actor_idx) = chrome_fields(&r.event); let ts_us = r.nanos as f64 / 1000.0; - if !first { let _ = w.write_all(b",\n"); } + if !first { + let _ = w.write_all(b",\n"); + } first = false; let _ = write!(w, "{{\"ph\":\"i\",\"ts\":{:.3},\"pid\":{},\"tid\":{},\"name\":{:?},\"s\":\"g\"}}", @@ -234,27 +246,31 @@ mod inner { fn chrome_fields(ev: &Event) -> (String, u32) { match ev { - Event::Spawn { parent, child } => - (format!("spawn c={}", child.index()), parent.index()), - Event::Resume(p) => ("resume".into(), p.index()), - Event::Yield(p) => ("yield".into(), p.index()), - Event::Park(p) => ("park".into(), p.index()), - Event::Done(p) => ("done".into(), p.index()), - Event::UnparkDirect(p) => ("unpark_direct".into(), p.index()), - Event::UnparkDeferred(p) => ("unpark_deferred".into(), p.index()), + Event::Spawn { parent, child } => { + (format!("spawn c={}", child.index()), parent.index()) + } + Event::Resume(p) => ("resume".into(), p.index()), + Event::Yield(p) => ("yield".into(), p.index()), + Event::Park(p) => ("park".into(), p.index()), + Event::Done(p) => ("done".into(), p.index()), + Event::UnparkDirect(p) => ("unpark_direct".into(), p.index()), + Event::UnparkDeferred(p) => ("unpark_deferred".into(), p.index()), Event::UnparkFlagConsumed(p) => ("unpark_flag_consumed".into(), p.index()), Event::Send { sender, receiver } => ( - format!("send rx={}", receiver - .map(|p| p.index().to_string()) - .unwrap_or_else(|| "none".into())), + format!( + "send rx={}", + receiver + .map(|p| p.index().to_string()) + .unwrap_or_else(|| "none".into()) + ), sender.index(), ), Event::RecvPark(p) => ("recv_park".into(), p.index()), Event::RecvWake(p) => ("recv_wake".into(), p.index()), - Event::Enqueue(p) => ("enqueue".into(), p.index()), - Event::Dequeue(p) => ("dequeue".into(), p.index()), + Event::Enqueue(p) => ("enqueue".into(), p.index()), + Event::Dequeue(p) => ("dequeue".into(), p.index()), Event::SlotPush(p) => ("slot_push".into(), p.index()), - Event::SlotPop(p) => ("slot_pop".into(), p.index()), + Event::SlotPop(p) => ("slot_pop".into(), p.index()), } } diff --git a/tests/cancel.rs b/tests/cancel.rs index 6f0ddfa..caea417 100644 --- a/tests/cancel.rs +++ b/tests/cancel.rs @@ -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" + ); } diff --git a/tests/causal.rs b/tests/causal.rs index 5be55dc..7a9fac9 100644 --- a/tests/causal.rs +++ b/tests/causal.rs @@ -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(); diff --git a/tests/channel.rs b/tests/channel.rs index 7fd2e64..cc92e95 100644 --- a/tests/channel.rs +++ b/tests/channel.rs @@ -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::()); + assert_eq!( + total.load(std::sync::atomic::Ordering::Relaxed), + (0..32).sum::() + ); } // --------------------------------------------------------------------------- @@ -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::(); - 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::(); 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 || { diff --git a/tests/context.rs b/tests/context.rs index dc02b23..f548020 100644 --- a/tests/context.rs +++ b/tests/context.rs @@ -11,9 +11,15 @@ thread_local! { static LOG: Cell = 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); diff --git a/tests/fd_select.rs b/tests/fd_select.rs index 38de6eb..4c5a48a 100644 --- a/tests/fd_select.rs +++ b/tests/fd_select.rs @@ -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)); diff --git a/tests/gen_server.rs b/tests/gen_server.rs index 240bd42..694a2b6 100644 --- a/tests/gen_server.rs +++ b/tests/gen_server.rs @@ -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>>, cancel_won: Arc>>) -> 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" + ); }); } diff --git a/tests/gen_statem.rs b/tests/gen_statem.rs index 4f03fd8..356b9f8 100644 --- a/tests/gen_statem.rs +++ b/tests/gen_statem.rs @@ -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::>)); 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)] diff --git a/tests/introspect.rs b/tests/introspect.rs index accb4a4..700f403 100644 --- a/tests/introspect.rs +++ b/tests/introspect.rs @@ -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: {}", diff --git a/tests/io.rs b/tests/io.rs index 820ee66..06b7d67 100644 --- a/tests/io.rs +++ b/tests/io.rs @@ -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); } diff --git a/tests/io_epoll.rs b/tests/io_epoll.rs index 89cacf4..89e8318 100644 --- a/tests/io_epoll.rs +++ b/tests/io_epoll.rs @@ -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 + ); } // --------------------------------------------------------------------------- diff --git a/tests/link.rs b/tests/link.rs index f82a22e..5749464 100644 --- a/tests/link.rs +++ b/tests/link.rs @@ -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); } diff --git a/tests/monitor.rs b/tests/monitor.rs index 428b69a..e0b5455 100644 --- a/tests/monitor.rs +++ b/tests/monitor.rs @@ -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(); }); } diff --git a/tests/mutex.rs b/tests/mutex.rs index ac24c1b..8f58c50 100644 --- a/tests/mutex.rs +++ b/tests/mutex.rs @@ -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" + ); } // --------------------------------------------------------------------------- diff --git a/tests/observer.rs b/tests/observer.rs index 909cf07..02c276b 100644 --- a/tests/observer.rs +++ b/tests/observer.rs @@ -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(); diff --git a/tests/pg.rs b/tests/pg.rs index eb46ca0..180ae10 100644 --- a/tests/pg.rs +++ b/tests/pg.rs @@ -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); }); } diff --git a/tests/poison_stop.rs b/tests/poison_stop.rs index 3845ab5..e619417 100644 --- a/tests/poison_stop.rs +++ b/tests/poison_stop.rs @@ -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" + ); } diff --git a/tests/preempt.rs b/tests/preempt.rs index 1f3e43c..bd9026a 100644 --- a/tests/preempt.rs +++ b/tests/preempt.rs @@ -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] diff --git a/tests/registry.rs b/tests/registry.rs index 908af90..d1e23f3 100644 --- a/tests/registry.rs +++ b/tests/registry.rs @@ -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::(); - 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::::new("ghost"), 1u64), Err(SendError::Unresolved(_)))); + assert!(matches!( + send(Name::::new("ghost"), 1u64), + Err(SendError::Unresolved(_)) + )); let (ready_tx, ready_rx) = channel::<()>(); let (tx, rx) = channel::(); @@ -227,8 +233,11 @@ fn send_dyn_delivers_and_reports_wrong_type() { ready_rx.recv().unwrap(); let p = h.pid(); // a bare Pid, as if recovered off a Down send_dyn::(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(); }); diff --git a/tests/runtime.rs b/tests/runtime.rs index 2947315..c5f1748 100644 --- a/tests/runtime.rs +++ b/tests/runtime.rs @@ -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>> = - Arc::new(smarm::Mutex::new(HashSet::new())); + let thread_ids: Arc>> = 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) } diff --git a/tests/scheduler.rs b/tests/scheduler.rs index ed7a70d..6616285 100644 --- a/tests/scheduler.rs +++ b/tests/scheduler.rs @@ -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>> = - Arc::new(std::sync::Mutex::new(None)); + let pid_cell: Arc>> = Arc::new(std::sync::Mutex::new(None)); let p2 = pid_cell.clone(); run(move || { let h = spawn(move || { diff --git a/tests/select.rs b/tests/select.rs index 703cc05..7fdd5fa 100644 --- a/tests/select.rs +++ b/tests/select.rs @@ -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::(); let (_keep_b, rxb) = channel::(); 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::(); let (txb, rxb) = channel::(); 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()); }); } diff --git a/tests/spawn_opts.rs b/tests/spawn_opts.rs index 097503f..8b8e41b 100644 --- a/tests/spawn_opts.rs +++ b/tests/spawn_opts.rs @@ -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(); diff --git a/tests/stack.rs b/tests/stack.rs index 1ca44c3..0bf30af 100644 --- a/tests/stack.rs +++ b/tests/stack.rs @@ -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 + ); } } diff --git a/tests/stack_diag.rs b/tests/stack_diag.rs index eca3bbb..3b5f448 100644 --- a/tests/stack_diag.rs +++ b/tests/stack_diag.rs @@ -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] diff --git a/tests/stack_recycle.rs b/tests/stack_recycle.rs index 03b485b..51cc126 100644 --- a/tests/stack_recycle.rs +++ b/tests/stack_recycle.rs @@ -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" + ); }); } diff --git a/tests/stack_shrink.rs b/tests/stack_shrink.rs index 79370a9..8ba1261 100644 --- a/tests/stack_shrink.rs +++ b/tests/stack_shrink.rs @@ -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(); diff --git a/tests/stop_with_queued_call.rs b/tests/stop_with_queued_call.rs index d48d068..3a77a83 100644 --- a/tests/stop_with_queued_call.rs +++ b/tests/stop_with_queued_call.rs @@ -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::>(); // 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. diff --git a/tests/stress.rs b/tests/stress.rs index d993ac4..3ebf25b 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -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 = - Vec::with_capacity(CHILDREN_PER_PARENT); + let mut child_handles: Vec = 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>> = - Arc::new(smarm::Mutex::new(Vec::new())); + let pids: Arc>> = 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() { diff --git a/tests/supervisor.rs b/tests/supervisor.rs index 4344235..3205728 100644 --- a/tests/supervisor.rs +++ b/tests/supervisor.rs @@ -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] diff --git a/tests/terminal_outcome_after_death.rs b/tests/terminal_outcome_after_death.rs index eaf1f37..4aa6d7c 100644 --- a/tests/terminal_outcome_after_death.rs +++ b/tests/terminal_outcome_after_death.rs @@ -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; diff --git a/tests/terminal_wake.rs b/tests/terminal_wake.rs index 6f5260b..3fe9780 100644 --- a/tests/terminal_wake.rs +++ b/tests/terminal_wake.rs @@ -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)` diff --git a/tests/timer.rs b/tests/timer.rs index 8569ab1..763ffa9 100644 --- a/tests/timer.rs +++ b/tests/timer.rs @@ -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"); }); }