Make preemption knobs configurable; fix unused-variable warnings
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.
This commit is contained in:
@@ -31,8 +31,8 @@
|
||||
//! becomes a measured bottleneck.
|
||||
|
||||
use crate::actor::{
|
||||
clear_current_pid, 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};
|
||||
@@ -70,13 +70,19 @@ pub struct Config {
|
||||
min: usize,
|
||||
max: usize,
|
||||
exact: Option<usize>,
|
||||
alloc_interval: u32,
|
||||
timeslice_cycles: u64,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Exact thread count; takes precedence over min/max.
|
||||
pub fn exact(n: usize) -> Self {
|
||||
assert!(n >= 1, "scheduler thread count must be ≥ 1");
|
||||
Self { min: n, max: n, exact: Some(n) }
|
||||
Self {
|
||||
min: n, max: n, exact: Some(n),
|
||||
alloc_interval: crate::preempt::DEFAULT_ALLOC_INTERVAL,
|
||||
timeslice_cycles: crate::preempt::DEFAULT_TIMESLICE_CYCLES,
|
||||
}
|
||||
}
|
||||
|
||||
/// Bounded range. Thread count = clamp(available_parallelism, min, max).
|
||||
@@ -86,7 +92,28 @@ impl Config {
|
||||
if let Some(e) = exact {
|
||||
assert!(e >= 1, "exact must be ≥ 1");
|
||||
}
|
||||
Self { min, max, exact }
|
||||
Self {
|
||||
min, max, exact,
|
||||
alloc_interval: crate::preempt::DEFAULT_ALLOC_INTERVAL,
|
||||
timeslice_cycles: crate::preempt::DEFAULT_TIMESLICE_CYCLES,
|
||||
}
|
||||
}
|
||||
|
||||
/// How many allocations (or `smarm::check!()` calls) between RDTSC checks.
|
||||
/// Lower = more responsive preemption, higher = less overhead.
|
||||
/// Default: 128.
|
||||
pub fn alloc_interval(mut self, n: u32) -> Self {
|
||||
assert!(n >= 1, "alloc_interval must be ≥ 1");
|
||||
self.alloc_interval = n;
|
||||
self
|
||||
}
|
||||
|
||||
/// How many TSC cycles constitute one timeslice.
|
||||
/// Default: 300_000 (≈ 100µs on a 3 GHz CPU).
|
||||
pub fn timeslice_cycles(mut self, n: u64) -> Self {
|
||||
assert!(n >= 1, "timeslice_cycles must be ≥ 1");
|
||||
self.timeslice_cycles = n;
|
||||
self
|
||||
}
|
||||
|
||||
/// The number of scheduler threads this config resolves to.
|
||||
@@ -106,7 +133,11 @@ impl Default for Config {
|
||||
let avail = thread::available_parallelism()
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(1);
|
||||
Self { min: 1, max: avail, exact: None }
|
||||
Self {
|
||||
min: 1, max: avail, exact: None,
|
||||
alloc_interval: crate::preempt::DEFAULT_ALLOC_INTERVAL,
|
||||
timeslice_cycles: crate::preempt::DEFAULT_TIMESLICE_CYCLES,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,10 +301,13 @@ pub(crate) struct RuntimeInner {
|
||||
/// Global counters for RFC 000 primitives.
|
||||
pub(crate) io_parked: AtomicU32,
|
||||
pub(crate) sleeping: AtomicU32,
|
||||
/// Preemption knobs, written into each scheduler thread's locals on startup.
|
||||
pub(crate) alloc_interval: u32,
|
||||
pub(crate) timeslice_cycles: u64,
|
||||
}
|
||||
|
||||
impl RuntimeInner {
|
||||
fn new(thread_count: usize) -> Arc<Self> {
|
||||
fn new(thread_count: usize, alloc_interval: u32, timeslice_cycles: u64) -> Arc<Self> {
|
||||
let stats = (0..thread_count).map(|_| SchedulerStats::new()).collect();
|
||||
Arc::new(Self {
|
||||
shared: Mutex::new(SharedState::new()),
|
||||
@@ -281,6 +315,8 @@ impl RuntimeInner {
|
||||
stats,
|
||||
io_parked: AtomicU32::new(0),
|
||||
sleeping: AtomicU32::new(0),
|
||||
alloc_interval,
|
||||
timeslice_cycles,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -295,18 +331,6 @@ impl RuntimeInner {
|
||||
crate::preempt::PREEMPTION_ENABLED.with(|c| c.set(prev));
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns `None` when the mutex is poisoned.
|
||||
/// Used in `unpark` / channel Drop which can fire after teardown.
|
||||
pub(crate) fn try_with_shared<R>(&self, f: impl FnOnce(&mut SharedState) -> R) -> Option<R> {
|
||||
let prev = crate::preempt::PREEMPTION_ENABLED.with(|c| c.replace(false));
|
||||
let result = match self.shared.lock() {
|
||||
Ok(mut g) => Some(f(&mut g)),
|
||||
Err(p) => Some(f(&mut p.into_inner())),
|
||||
};
|
||||
crate::preempt::PREEMPTION_ENABLED.with(|c| c.set(prev));
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -322,7 +346,7 @@ pub struct Runtime {
|
||||
pub fn init(config: Config) -> Runtime {
|
||||
let n = config.resolved_thread_count();
|
||||
Runtime {
|
||||
inner: RuntimeInner::new(n),
|
||||
inner: RuntimeInner::new(n, config.alloc_interval, config.timeslice_cycles),
|
||||
thread_count: n,
|
||||
}
|
||||
}
|
||||
@@ -526,6 +550,7 @@ fn finalize_actor(inner: &Arc<RuntimeInner>, pid: Pid, outcome: Outcome) {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn schedule_loop(inner: &Arc<RuntimeInner>, slot: usize) {
|
||||
crate::preempt::configure_preempt(inner.alloc_interval, inner.timeslice_cycles);
|
||||
let stats = &inner.stats[slot];
|
||||
|
||||
loop {
|
||||
|
||||
Reference in New Issue
Block a user