Three changes, each independently measured, landed together.
Fuse pre-resume mutex acquisitions
-----------------------------------
The schedule loop previously took the shared lock three separate times
per actor resume: once to pop the run queue, once to read the stack
pointer, and once to pop the first-resume closure. These are now a
single acquisition that returns everything needed to resume the actor.
As a side effect, pending_closures was changed from a Vec<(Pid, Closure)>
with O(n) linear scan to a Vec<Option<Closure>> indexed by slot index,
making first-closure lookup O(1).
Move stack allocation outside the shared lock
----------------------------------------------
Stack::new() (mmap + mprotect) was previously called inside with_shared,
stalling every other scheduler thread for the duration of two syscalls on
every spawn. It now runs before the lock is acquired.
Stack pool
----------
Rather than munmap-ing a stack when an actor finishes and mmap-ing a fresh
one on the next spawn, stacks are now recycled through a per-Runtime pool
(Mutex<Vec<Stack>>). finalize_actor extracts the stack from the Actor
before clearing the slot and pushes it to the pool outside the shared lock.
spawn_under pops from the pool before falling back to Stack::new().
The pool is unbounded for now (shrink policy TBD) but capped at
stack_pool_cap stacks on return, defaulting to thread_count * 4.
The cap is configurable via Config::stack_pool_cap(n).
Results (24-thread, against stored baseline)
--------------------------------------------
chained_spawn smarm 1-thread: 9763 → 261 µs (-97%)
chained_spawn smarm 24-thread: 23562 → 838 µs (-96%)
ping_pong_oneshot smarm 1-thread: 18409 → 742 µs (-96%)
ping_pong_oneshot smarm 24-thread: 44596 → 1425 µs (-97%)
catch_unwind_panics smarm 24-thread: 267812 → 124094 µs (-54%)
fan_out_compute smarm 24-thread: 2839 → 2226 µs (-22%)
Tokio regressions in the checker output are baseline measurement drift;
no tokio code was changed.
Add `Config::alloc_interval()` and `Config::timeslice_cycles()` so
callers can tune preemption sensitivity at runtime. The values flow
through `RuntimeInner` and are written into per-scheduler-thread locals
via a new `configure_preempt()` call at thread startup, keeping the hot
path free of cross-thread coherency traffic.
Fix unused-variable warnings in channel.rs by inlining `current_pid()`
directly into `te!` macro arguments — since the no-op macro arm never
evaluates its argument, no binding is needed at the call site.
Clean up a handful of dead imports exposed by the refactor.