use std::cell::RefCell;
use std::sync::{Arc, OnceLock};
use super::fabric::ComputeFabric;
use super::telemetry::TelemetryEngine;
use crate::parallelism::Parallelism;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AdaptiveContext {
pub admission_budget: usize,
pub parallelism_threshold: usize,
pub rayon_threads: usize,
}
impl AdaptiveContext {
pub const DEFAULT_THRESHOLD: usize = Parallelism::DEFAULT_THRESHOLD;
#[inline]
pub fn standalone() -> Self {
let threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
.max(1);
Self {
admission_budget: threads,
parallelism_threshold: Self::DEFAULT_THRESHOLD,
rayon_threads: threads,
}
}
#[inline]
pub fn from_fabric<E: TelemetryEngine + 'static>(fabric: &ComputeFabric<E>) -> Self {
let admission = fabric.admission();
let budget = admission.available().max(1);
let threads = admission.max_permits().max(1);
let threshold = effective_threshold_for_budget(budget);
Self {
admission_budget: budget,
parallelism_threshold: threshold,
rayon_threads: threads,
}
}
}
#[inline]
#[allow(dead_code)]
pub(crate) fn effective_threshold(policy: Parallelism) -> usize {
current_adaptive_context().apply_threshold(policy)
}
impl AdaptiveContext {
#[inline]
pub(crate) fn apply_threshold(self, policy: Parallelism) -> usize {
match policy {
Parallelism::Serial => usize::MAX,
Parallelism::ForceParallel => 0,
Parallelism::Auto { threshold } => threshold.min(self.parallelism_threshold),
}
}
}
fn effective_threshold_for_budget(budget: usize) -> usize {
let base = AdaptiveContext::DEFAULT_THRESHOLD;
match budget {
n if n >= 8 => (base / 4).max(1),
n if n >= 4 => (base / 2).max(1),
1 => base.saturating_mul(2),
_ => base,
}
}
static FABRIC: OnceLock<std::sync::Mutex<Option<Arc<dyn FabricSource>>>> = OnceLock::new();
thread_local! {
static ADAPTIVE_CTX: RefCell<AdaptiveContext> = RefCell::new(AdaptiveContext::standalone());
}
fn fabric_slot() -> &'static std::sync::Mutex<Option<Arc<dyn FabricSource>>> {
FABRIC.get_or_init(|| std::sync::Mutex::new(None))
}
pub trait FabricSource: Send + Sync {
fn adaptive_context(&self) -> AdaptiveContext;
}
impl<E: TelemetryEngine + 'static> FabricSource for ComputeFabric<E> {
fn adaptive_context(&self) -> AdaptiveContext {
AdaptiveContext::from_fabric(self)
}
}
pub fn install_fabric<E: TelemetryEngine + 'static>(fabric: Arc<ComputeFabric<E>>) {
let erased: Arc<dyn FabricSource> = fabric;
*fabric_slot().lock().expect("fabric slot mutex") = Some(erased);
refresh_adaptive_context();
}
#[inline]
pub fn refresh_adaptive_context() {
let ctx = fabric_slot()
.lock()
.ok()
.and_then(|guard| guard.as_ref().map(|f| f.adaptive_context()))
.unwrap_or_else(AdaptiveContext::standalone);
ADAPTIVE_CTX.with(|c| *c.borrow_mut() = ctx);
}
#[inline]
pub fn current_adaptive_context() -> AdaptiveContext {
ADAPTIVE_CTX.with(|c| *c.borrow())
}
#[allow(dead_code)]
pub fn with_adaptive_context<F, R>(ctx: AdaptiveContext, f: F) -> R
where
F: FnOnce() -> R,
{
ADAPTIVE_CTX.with(|cell| {
let prev = *cell.borrow();
*cell.borrow_mut() = ctx;
let out = f();
*cell.borrow_mut() = prev;
out
})
}
#[inline]
pub fn ensure_run_context() {
refresh_adaptive_context();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compute::{ComputeFabric, ResourcePolicy};
#[test]
fn low_budget_raises_threshold() {
let ctx = AdaptiveContext {
admission_budget: 1,
parallelism_threshold: effective_threshold_for_budget(1),
rayon_threads: 1,
};
assert!(ctx.parallelism_threshold >= AdaptiveContext::DEFAULT_THRESHOLD);
}
#[test]
fn high_budget_lowers_threshold() {
let ctx = AdaptiveContext {
admission_budget: 8,
parallelism_threshold: effective_threshold_for_budget(8),
rayon_threads: 8,
};
assert!(ctx.parallelism_threshold < AdaptiveContext::DEFAULT_THRESHOLD);
}
#[test]
fn fabric_install_updates_context() {
let fabric = Arc::new(ComputeFabric::with_mock(
ResourcePolicy::memory_cap_max_cpu(0.85),
0.4,
0.61,
));
fabric.supervisor().tick();
install_fabric(Arc::clone(&fabric));
let ctx = current_adaptive_context();
assert!(ctx.admission_budget >= 1);
assert!(ctx.rayon_threads >= 1);
}
#[test]
fn apply_threshold_respects_parallelism_policy() {
let ctx = AdaptiveContext::standalone();
assert_eq!(ctx.apply_threshold(Parallelism::Serial), usize::MAX);
assert_eq!(ctx.apply_threshold(Parallelism::ForceParallel), 0);
let auto = ctx.apply_threshold(Parallelism::Auto { threshold: 4 });
assert!(auto <= 4);
}
#[test]
fn effective_threshold_reads_thread_local_context() {
let ctx = AdaptiveContext {
admission_budget: 8,
parallelism_threshold: 2,
rayon_threads: 8,
};
with_adaptive_context(ctx, || {
assert_eq!(effective_threshold(Parallelism::Auto { threshold: 100 }), 2);
});
}
#[test]
fn from_fabric_matches_admission_budget() {
let fabric = ComputeFabric::with_mock(ResourcePolicy::memory_cap_max_cpu(0.85), 0.2, 0.3);
fabric.supervisor().tick();
let ctx = AdaptiveContext::from_fabric(&fabric);
assert!(ctx.admission_budget >= 1);
assert!(ctx.rayon_threads >= 1);
}
#[test]
fn ensure_run_context_refreshes_without_panic() {
ensure_run_context();
let ctx = current_adaptive_context();
assert!(ctx.rayon_threads >= 1);
}
}