runtime: wake_slot default ON (RFC 005 accepted, finding 18); supervisor stops survivors sequentially so reverse-order teardown is guaranteed; deflake gen_server/channel tests; baseline.json regenerated slot-on; ROADMAP notes

This commit is contained in:
claude-asm-audit
2026-08-21 12:23:33 +00:00
parent 7001f04b65
commit ea2b222cff
6 changed files with 333 additions and 300 deletions
+14 -1
View File
@@ -28,6 +28,15 @@ and **excised** (not worth the code cost; preserved on branch
`rfc-004-spinning`). Also a false-sharing fix (`align(64)` on `SchedulerStats`)
and a termination wake for idle siblings.
Commits `2708042`, `37d9319`, `eddf3fe`.
**Default flipped ON 2026-08-18** (history.md findings 17/18): the slot had
shipped default-off "until the shootout accepts it" and the flip was never
made, so every general.rs number since was slot-off. Acceptance sweep
(rq_runtime, 1/2/4/8/20 schedulers, rq-mpmc, 7 runs): ping-pong-pairs
−7% at 1T, 3.6×/5.3×/15.6×/10× faster at 2/4/8/20T, 100% slot hits, 0
displaced; yield-storm and spawn-storm within ±10% noise both ways.
general.rs on the flip: ping_pong_steady 20T 18485→1549 µs, 1T −15%,
mpsc_contention 1T −51%, ping_pong_oneshot 20T −18%; no smarm regressions.
`benches/baseline.json` regenerated slot-on.
---
@@ -146,7 +155,11 @@ Needs an RFC.
#### Per-switch cost (context shims, epoch protocol)
The shootout's residual: per-wake latency is 0.16–0.18 µs at N=1 and
0.8–1.2 µs at N=8+, dominated by the context-switch shims and the epoch
protocol, not the queue. On current evidence this is the larger constant —
protocol, not the queue. **Premise corrected 2026-08-18 (finding 17): the
N=8+ figure was the slot-off futex path (one futex_wake per landed wake,
woken worker steals the pair); slot-on it is ~1.3× N=1. The shims are ~5
cycles (finding 3) and the epoch CASes ~117 cycles/roundtrip (finding 15).
Re-measure slot-on before spending anything here.** On current evidence this is the larger constant —
"the whole game" alongside the v0.9 work — but there is no spec yet. Needs a
profiling spike (where do the cycles actually go per park/unpark round-trip)
and then an RFC before it can be scheduled.
+290 -290
View File
@@ -1,366 +1,366 @@
{
"catch_unwind_panics": {
"smarm 1-thread": {
"result": 10000,
"median": 112187,
"min": 111086,
"max": 121834
},
"smarm 20-thread": {
"result": 10000,
"median": 20374,
"min": 19939,
"max": 21381
},
"tokio current_thread": {
"result": 10000,
"median": 10425,
"min": 10338,
"max": 10795
},
"tokio multi-thread": {
"result": 10000,
"median": 2420,
"min": 2309,
"max": 5657
}
},
"chained_spawn": {
"smarm 1-thread": {
"result": 1000,
"median": 449,
"min": 440,
"max": 452
"median": 454,
"min": 443,
"max": 473
},
"smarm 20-thread": {
"result": 1000,
"median": 2102,
"min": 1936,
"max": 2226
"median": 1884,
"min": 1866,
"max": 2124
},
"tokio current_thread": {
"result": 1000,
"median": 109,
"min": 108,
"max": 125
"median": 110,
"min": 109,
"max": 124
},
"tokio multi-thread": {
"result": 1000,
"median": 186,
"min": 156,
"max": 211
}
},
"yield_many": {
"smarm 1-thread": {
"result": 200000,
"median": 10563,
"min": 10181,
"max": 10589
},
"smarm 20-thread": {
"result": 200000,
"median": 43218,
"min": 42523,
"max": 43864
},
"tokio current_thread": {
"result": 200000,
"median": 5258,
"min": 5251,
"max": 5864
},
"tokio multi-thread": {
"result": 200000,
"median": 5583,
"min": 5512,
"max": 6563
}
},
"fan_out_compute": {
"smarm 1-thread": {
"result": 33860,
"median": 15030,
"min": 14584,
"max": 15039
},
"smarm 20-thread": {
"result": 33860,
"median": 2362,
"min": 2274,
"max": 2426
},
"tokio current_thread": {
"result": 33860,
"median": 14040,
"min": 14037,
"max": 14042
},
"tokio multi-thread": {
"result": 33860,
"median": 2386,
"min": 2342,
"max": 2418
}
},
"ping_pong_oneshot": {
"smarm 1-thread": {
"result": 1000,
"median": 805,
"min": 789,
"max": 829
},
"smarm 20-thread": {
"result": 1000,
"median": 7503,
"min": 7005,
"max": 7677
},
"tokio current_thread": {
"result": 1000,
"median": 410,
"min": 396,
"max": 440
},
"tokio multi-thread": {
"result": 1000,
"median": 8911,
"min": 8844,
"max": 9198
}
},
"spawn_storm_busy": {
"smarm 1-thread": {
"result": 10000,
"median": 103743,
"min": 103453,
"max": 104373
},
"smarm 20-thread": {
"result": 10000,
"median": 15364,
"min": 14982,
"max": 15642
},
"tokio current_thread": {
"result": 10000,
"median": 1071,
"min": 1063,
"max": 1079
},
"tokio multi-thread": {
"result": 10000,
"median": 12183,
"min": 11551,
"max": 12852
}
},
"mpsc_contention": {
"smarm 1-thread": {
"result": 320000,
"median": 6711,
"min": 6703,
"max": 6722
},
"smarm 20-thread": {
"result": 320000,
"median": 35331,
"min": 34287,
"max": 36335
},
"tokio current_thread": {
"result": 320000,
"median": 6207,
"min": 5495,
"max": 6278
},
"tokio multi-thread": {
"result": 320000,
"median": 72132,
"min": 66649,
"max": 75892
}
},
"many_timers": {
"smarm 1-thread": {
"result": 10000,
"median": 117002,
"min": 103516,
"max": 118009
},
"smarm 20-thread": {
"result": 10000,
"median": 53418,
"min": 52807,
"max": 53785
},
"tokio current_thread": {
"result": 10000,
"median": 12444,
"min": 12389,
"max": 12483
},
"tokio multi-thread": {
"result": 10000,
"median": 13948,
"min": 13791,
"max": 14268
}
},
"multi_thread_scaling": {
"smarm 1-thread": {
"result": 33860,
"median": 15036,
"min": 15029,
"max": 15060
},
"smarm 2-thread": {
"result": 33860,
"median": 7987,
"min": 7965,
"max": 8027
},
"smarm 4-thread": {
"result": 33860,
"median": 4299,
"min": 4269,
"max": 4309
},
"smarm 20-thread": {
"result": 33860,
"median": 2381,
"min": 2321,
"max": 2409
},
"tokio multi 1-thread": {
"result": 33860,
"median": 14300,
"min": 14213,
"max": 14386
},
"tokio multi 2-thread": {
"result": 33860,
"median": 7356,
"min": 7308,
"max": 7408
},
"tokio multi 4-thread": {
"result": 33860,
"median": 3725,
"min": 3714,
"max": 3728
},
"tokio multi 20-thread": {
"result": 33860,
"median": 2402,
"min": 2383,
"max": 2421
"median": 172,
"min": 160,
"max": 177
}
},
"deep_recursion": {
"smarm 1-thread": {
"result": 1,
"median": 291,
"min": 288,
"max": 323
"median": 289,
"min": 283,
"max": 290
},
"smarm 20-thread": {
"result": 1,
"median": 673,
"min": 627,
"max": 740
"median": 683,
"min": 632,
"max": 725
},
"tokio current_thread": {
"result": 1,
"median": 14,
"min": 13,
"max": 17
"median": 12,
"min": 11,
"max": 12
},
"tokio multi-thread": {
"result": 1,
"median": 53,
"min": 50,
"max": 54
"median": 51,
"min": 47,
"max": 56
}
},
"yield_in_hot_loop": {
"fan_out_compute": {
"smarm 1-thread": {
"result": 1000000,
"median": 30361,
"min": 29592,
"max": 30509
"result": 33860,
"median": 15040,
"min": 15026,
"max": 15050
},
"smarm 20-thread": {
"result": 33860,
"median": 2365,
"min": 2324,
"max": 2393
},
"tokio current_thread": {
"result": 1000000,
"median": 69408,
"min": 67891,
"max": 70101
"result": 33860,
"median": 14097,
"min": 14087,
"max": 14102
},
"tokio multi-thread": {
"result": 33860,
"median": 2390,
"min": 2142,
"max": 2827
}
},
"uncontended_channel": {
"smarm 1-thread": {
"result": 1000000,
"median": 11962,
"min": 11943,
"max": 12052
},
"tokio current_thread": {
"result": 1000000,
"median": 14664,
"min": 14584,
"max": 14702
}
},
"catch_unwind_panics": {
"many_timers": {
"smarm 1-thread": {
"result": 10000,
"median": 121767,
"min": 120583,
"max": 122123
"median": 113255,
"min": 105504,
"max": 118516
},
"smarm 20-thread": {
"result": 10000,
"median": 19950,
"min": 19260,
"max": 20565
"median": 53400,
"min": 53293,
"max": 54901
},
"tokio current_thread": {
"result": 10000,
"median": 10184,
"min": 10037,
"max": 10660
"median": 12443,
"min": 12402,
"max": 13170
},
"tokio multi-thread": {
"result": 10000,
"median": 2430,
"min": 2339,
"max": 2628
"median": 13701,
"min": 13374,
"max": 13913
}
},
"mpsc_contention": {
"smarm 1-thread": {
"result": 320000,
"median": 3303,
"min": 3271,
"max": 3391
},
"smarm 20-thread": {
"result": 320000,
"median": 35379,
"min": 34372,
"max": 36334
},
"tokio current_thread": {
"result": 320000,
"median": 6034,
"min": 5428,
"max": 6037
},
"tokio multi-thread": {
"result": 320000,
"median": 63753,
"min": 59875,
"max": 77970
}
},
"multi_thread_scaling": {
"smarm 1-thread": {
"result": 33860,
"median": 15115,
"min": 15102,
"max": 15209
},
"smarm 2-thread": {
"result": 33860,
"median": 8002,
"min": 7972,
"max": 8061
},
"smarm 4-thread": {
"result": 33860,
"median": 4284,
"min": 4255,
"max": 4327
},
"smarm 20-thread": {
"result": 33860,
"median": 2402,
"min": 2313,
"max": 2414
},
"tokio multi 1-thread": {
"result": 33860,
"median": 14310,
"min": 14254,
"max": 14602
},
"tokio multi 2-thread": {
"result": 33860,
"median": 7374,
"min": 7248,
"max": 7567
},
"tokio multi 4-thread": {
"result": 33860,
"median": 3734,
"min": 3724,
"max": 3749
},
"tokio multi 20-thread": {
"result": 33860,
"median": 2390,
"min": 2195,
"max": 2411
}
},
"ping_pong_oneshot": {
"smarm 1-thread": {
"result": 1000,
"median": 796,
"min": 789,
"max": 814
},
"smarm 20-thread": {
"result": 1000,
"median": 6118,
"min": 5849,
"max": 6493
},
"tokio current_thread": {
"result": 1000,
"median": 422,
"min": 405,
"max": 426
},
"tokio multi-thread": {
"result": 1000,
"median": 8983,
"min": 8906,
"max": 9037
}
},
"ping_pong_steady": {
"smarm 1-thread": {
"result": 10000,
"median": 1427,
"min": 1419,
"max": 1437
"median": 1209,
"min": 1191,
"max": 1222
},
"smarm 20-thread": {
"result": 10000,
"median": 18485,
"min": 17162,
"max": 18897
"median": 1549,
"min": 1523,
"max": 1584
},
"tokio current_thread": {
"result": 10000,
"median": 1285,
"min": 1281,
"max": 1287
"median": 1283,
"min": 1279,
"max": 1303
},
"tokio multi-thread": {
"result": 10000,
"median": 79958,
"min": 77547,
"max": 82155
"median": 82303,
"min": 81303,
"max": 83249
}
},
"spawn_pair_control": {
"smarm 1-thread": {
"result": 1000,
"median": 632,
"min": 624,
"max": 634
"median": 558,
"min": 546,
"max": 584
},
"smarm 20-thread": {
"result": 1000,
"median": 5017,
"min": 4707,
"max": 5149
"median": 4918,
"min": 4417,
"max": 5022
},
"tokio current_thread": {
"result": 1000,
"median": 287,
"min": 277,
"max": 335
"median": 292,
"min": 280,
"max": 323
},
"tokio multi-thread": {
"result": 1000,
"median": 8621,
"min": 8415,
"max": 8828
"median": 8561,
"min": 8546,
"max": 9121
}
},
"spawn_storm_busy": {
"smarm 1-thread": {
"result": 10000,
"median": 105394,
"min": 97447,
"max": 106921
},
"smarm 20-thread": {
"result": 10000,
"median": 15479,
"min": 15041,
"max": 16061
},
"tokio current_thread": {
"result": 10000,
"median": 1237,
"min": 1114,
"max": 1248
},
"tokio multi-thread": {
"result": 10000,
"median": 12354,
"min": 11581,
"max": 15660
}
},
"uncontended_channel": {
"smarm 1-thread": {
"result": 1000000,
"median": 12085,
"min": 12020,
"max": 12162
},
"tokio current_thread": {
"result": 1000000,
"median": 14840,
"min": 14763,
"max": 15051
}
},
"yield_in_hot_loop": {
"smarm 1-thread": {
"result": 1000000,
"median": 30730,
"min": 30542,
"max": 30859
},
"tokio current_thread": {
"result": 1000000,
"median": 70423,
"min": 65167,
"max": 70699
}
},
"yield_many": {
"smarm 1-thread": {
"result": 200000,
"median": 10872,
"min": 10697,
"max": 10936
},
"smarm 20-thread": {
"result": 200000,
"median": 44358,
"min": 43982,
"max": 44790
},
"tokio current_thread": {
"result": 200000,
"median": 5312,
"min": 4751,
"max": 5478
},
"tokio multi-thread": {
"result": 200000,
"median": 5832,
"min": 5533,
"max": 6951
}
}
}
+4 -4
View File
@@ -182,7 +182,7 @@ impl Config {
stack_reserve: DEFAULT_STACK_RESERVE,
stack_guard: DEFAULT_STACK_GUARD,
max_actors: DEFAULT_MAX_ACTORS,
wake_slot: false,
wake_slot: true,
node_id: crate::pg::DEFAULT_NODE_ID,
incarnation: crate::pg::DEFAULT_INCARNATION,
}
@@ -205,7 +205,7 @@ impl Config {
stack_reserve: DEFAULT_STACK_RESERVE,
stack_guard: DEFAULT_STACK_GUARD,
max_actors: DEFAULT_MAX_ACTORS,
wake_slot: false,
wake_slot: true,
node_id: crate::pg::DEFAULT_NODE_ID,
incarnation: crate::pg::DEFAULT_INCARNATION,
}
@@ -283,7 +283,7 @@ impl Config {
/// thread's slot; it is resumed next on that core and inherits the
/// remainder of the waker's timeslice. Scheduler-context wakes
/// (timer/IO drain) and spawns always go to the shared queue.
/// Default: `false` (off until the slot shootout accepts it).
/// Default: `true` (accepted 2026-08-18, history.md finding 17/18).
pub fn wake_slot(mut self, on: bool) -> Self {
self.wake_slot = on;
self
@@ -333,7 +333,7 @@ impl Default for Config {
stack_reserve: DEFAULT_STACK_RESERVE,
stack_guard: DEFAULT_STACK_GUARD,
max_actors: DEFAULT_MAX_ACTORS,
wake_slot: false,
wake_slot: true,
node_id: crate::pg::DEFAULT_NODE_ID,
incarnation: crate::pg::DEFAULT_INCARNATION,
}
+9 -1
View File
@@ -137,11 +137,19 @@ fn channel_ops_interleaved_with_monitor_churn_multi_thread() {
for i in 0..32i64 {
let tx = tx.clone();
handles.push(spawn(move || {
// Short-lived target whose death fires the monitor below.
// Short-lived target whose death fires the monitor below. It
// waits for `go` so the monitor is registered before it can
// die — otherwise a fast target yields an immediate NoProc
// Down instead of the finalize-sent Exit this test is about
// (pre-existing ~8% flake at 4 threads, independent of the
// wake slot).
let (go_tx, go_rx) = channel::<()>();
let t = spawn(move || {
let _ = go_rx.recv();
tx.send(i).unwrap();
});
let m = smarm::monitor(t.pid());
go_tx.send(()).unwrap();
t.join().unwrap();
// Down delivery exercises send-from-finalize.
let d = m.rx.recv().unwrap();
+14 -3
View File
@@ -396,8 +396,11 @@ impl GenServer for Pool {
}
// A worker spawned and watched from inside a handler delivers its Down to
// handle_down. Down arms outrank the inbox, so the death is in the log by
// the time the follow-up call is answered.
// handle_down. Nothing orders the worker's death before the follow-up
// call (the worker sits in the shared queue while call/reply wakes ride the
// wake slot), so poll: the log must become exactly [Panic] within a bounded
// number of yields. Down-outranks-inbox ordering is covered by
// `watch_dead_pid_is_noproc_down`.
#[test]
fn worker_pool_down_reaches_handle_down() {
let got = Arc::new(Mutex::new(Vec::new()));
@@ -409,7 +412,15 @@ fn worker_pool_down_reaches_handle_down() {
});
server.cast(PoolCast::SpawnDoomedWorker).unwrap();
let _ = server.call(()).unwrap(); // sync point: cast handled, worker live
*got2.lock().unwrap() = server.call(()).unwrap();
let mut log = Vec::new();
for _ in 0..10_000 {
log = server.call(()).unwrap();
if !log.is_empty() {
break;
}
smarm::yield_now();
}
*got2.lock().unwrap() = log;
});
assert_eq!(*got.lock().unwrap(), vec![DownReason::Panic]);
}
+3 -2
View File
@@ -80,10 +80,11 @@ fn slot_off_means_zero_slot_traffic() {
assert_eq!(rt.stats().slot_hits(), 0);
assert_eq!(rt.stats().slot_displacements(), 0);
// …and off by default (RFC 005: default off until the shootout accepts).
// …and ON by default (RFC 005 accepted after the shootout — history.md
// finding 17/18: ping-pong 12.5× at 20T, ~9% at 1T, 100% slot hits).
let rt = init(Config::exact(1));
rt.run(ping_pong(2, 100));
assert_eq!(rt.stats().slot_hits(), 0, "wake_slot must default to OFF");
assert!(rt.stats().slot_hits() > 0, "wake_slot must default to ON");
}
// ---------------------------------------------------------------------------