use std::future::Future;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::time::Duration;
use tokio::runtime::{Builder, Runtime};
use tokio::task::LocalSet;
#[derive(Debug, Clone)]
pub struct RuntimeConfig {
pub worker_threads: Option<usize>,
pub thread_name: String,
pub thread_stack_size: Option<usize>,
pub enable_io: bool,
pub enable_time: bool,
pub global_queue_interval: Option<u32>,
pub event_interval: Option<u32>,
pub max_blocking_threads: Option<usize>,
pub thread_keep_alive: Option<Duration>,
pub current_thread: bool,
pub use_local_set: bool,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
worker_threads: None,
thread_name: "armature-worker".to_string(),
thread_stack_size: Some(2 * 1024 * 1024), enable_io: true,
enable_time: true,
global_queue_interval: Some(61), event_interval: Some(61),
max_blocking_threads: Some(512),
thread_keep_alive: Some(Duration::from_secs(10)),
current_thread: false,
use_local_set: false,
}
}
}
impl RuntimeConfig {
pub fn new() -> Self {
Self::default()
}
pub fn throughput() -> Self {
let cpus = num_cpus();
Self {
worker_threads: Some(cpus),
thread_name: "armature-tp".to_string(),
thread_stack_size: Some(4 * 1024 * 1024), enable_io: true,
enable_time: true,
global_queue_interval: Some(128), event_interval: Some(128),
max_blocking_threads: Some(1024),
thread_keep_alive: Some(Duration::from_secs(30)),
current_thread: false,
use_local_set: false,
}
}
pub fn low_latency() -> Self {
let cpus = num_cpus();
Self {
worker_threads: Some(cpus.min(4)), thread_name: "armature-ll".to_string(),
thread_stack_size: Some(1024 * 1024), enable_io: true,
enable_time: true,
global_queue_interval: Some(31), event_interval: Some(31),
max_blocking_threads: Some(64),
thread_keep_alive: Some(Duration::from_secs(5)),
current_thread: false,
use_local_set: false,
}
}
pub fn single_threaded() -> Self {
Self {
worker_threads: Some(1),
thread_name: "armature-st".to_string(),
thread_stack_size: Some(2 * 1024 * 1024),
enable_io: true,
enable_time: true,
global_queue_interval: None,
event_interval: None,
max_blocking_threads: Some(32),
thread_keep_alive: Some(Duration::from_secs(10)),
current_thread: true,
use_local_set: true,
}
}
pub fn worker_threads(mut self, count: usize) -> Self {
self.worker_threads = Some(count);
self
}
pub fn thread_name(mut self, name: impl Into<String>) -> Self {
self.thread_name = name.into();
self
}
pub fn thread_stack_size(mut self, size: usize) -> Self {
self.thread_stack_size = Some(size);
self
}
pub fn global_queue_interval(mut self, interval: u32) -> Self {
self.global_queue_interval = Some(interval);
self
}
pub fn event_interval(mut self, interval: u32) -> Self {
self.event_interval = Some(interval);
self
}
pub fn max_blocking_threads(mut self, count: usize) -> Self {
self.max_blocking_threads = Some(count);
self
}
pub fn current_thread(mut self, enabled: bool) -> Self {
self.current_thread = enabled;
self
}
pub fn use_local_set(mut self, enabled: bool) -> Self {
self.use_local_set = enabled;
self
}
pub fn build(&self) -> std::io::Result<Runtime> {
let mut builder = if self.current_thread {
Builder::new_current_thread()
} else {
Builder::new_multi_thread()
};
if let Some(threads) = self.worker_threads
&& !self.current_thread
{
builder.worker_threads(threads);
}
builder.thread_name(&self.thread_name);
if let Some(size) = self.thread_stack_size {
builder.thread_stack_size(size);
}
if self.enable_io {
builder.enable_io();
}
if self.enable_time {
builder.enable_time();
}
if let Some(interval) = self.global_queue_interval {
builder.global_queue_interval(interval);
}
if let Some(interval) = self.event_interval {
builder.event_interval(interval);
}
if let Some(max) = self.max_blocking_threads {
builder.max_blocking_threads(max);
}
if let Some(duration) = self.thread_keep_alive {
builder.thread_keep_alive(duration);
}
builder.build()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SpawnPolicy {
Always,
Never,
#[default]
Adaptive,
LoadBased,
}
#[derive(Debug, Clone)]
pub struct SpawnConfig {
pub policy: SpawnPolicy,
pub duration_threshold_us: u64,
pub load_threshold: usize,
pub spawn_cpu_bound: bool,
}
impl Default for SpawnConfig {
fn default() -> Self {
Self {
policy: SpawnPolicy::Adaptive,
duration_threshold_us: 100, load_threshold: 100,
spawn_cpu_bound: true,
}
}
}
impl SpawnConfig {
pub fn new() -> Self {
Self::default()
}
pub fn inline_all() -> Self {
Self {
policy: SpawnPolicy::Never,
..Default::default()
}
}
pub fn always_spawn() -> Self {
Self {
policy: SpawnPolicy::Always,
..Default::default()
}
}
pub fn policy(mut self, policy: SpawnPolicy) -> Self {
self.policy = policy;
self
}
pub fn duration_threshold(mut self, us: u64) -> Self {
self.duration_threshold_us = us;
self
}
pub fn load_threshold(mut self, count: usize) -> Self {
self.load_threshold = count;
self
}
}
#[derive(Debug, Default)]
pub struct HandlerMetrics {
avg_duration_us: AtomicU64,
invocations: AtomicU64,
spawned: AtomicU64,
inlined: AtomicU64,
}
impl HandlerMetrics {
pub fn new() -> Self {
Self::default()
}
pub fn record_duration(&self, us: u64) {
let current = self.avg_duration_us.load(Ordering::Relaxed);
let new_avg = if current == 0 {
us
} else {
(current * 7 + us) / 8 };
self.avg_duration_us.store(new_avg, Ordering::Relaxed);
self.invocations.fetch_add(1, Ordering::Relaxed);
}
pub fn record_spawn(&self, spawned: bool) {
if spawned {
self.spawned.fetch_add(1, Ordering::Relaxed);
} else {
self.inlined.fetch_add(1, Ordering::Relaxed);
}
}
pub fn avg_duration_us(&self) -> u64 {
self.avg_duration_us.load(Ordering::Relaxed)
}
pub fn invocations(&self) -> u64 {
self.invocations.load(Ordering::Relaxed)
}
pub fn spawn_ratio(&self) -> f64 {
let spawned = self.spawned.load(Ordering::Relaxed) as f64;
let inlined = self.inlined.load(Ordering::Relaxed) as f64;
let total = spawned + inlined;
if total > 0.0 { spawned / total } else { 0.0 }
}
}
pub struct SmartSpawner {
config: SpawnConfig,
pending_tasks: AtomicUsize,
metrics: HandlerMetrics,
}
impl SmartSpawner {
pub fn new(config: SpawnConfig) -> Self {
Self {
config,
pending_tasks: AtomicUsize::new(0),
metrics: HandlerMetrics::new(),
}
}
pub fn should_spawn(&self, estimated_duration_us: Option<u64>) -> bool {
let should = match self.config.policy {
SpawnPolicy::Always => true,
SpawnPolicy::Never => false,
SpawnPolicy::Adaptive => {
let duration =
estimated_duration_us.unwrap_or_else(|| self.metrics.avg_duration_us());
duration > self.config.duration_threshold_us
}
SpawnPolicy::LoadBased => {
let pending = self.pending_tasks.load(Ordering::Relaxed);
pending < self.config.load_threshold
}
};
self.metrics.record_spawn(should);
RUNTIME_STATS.record_spawn_decision(should);
should
}
pub async fn execute<F, T>(&self, future: F) -> T
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
if self.should_spawn(None) {
self.pending_tasks.fetch_add(1, Ordering::Relaxed);
let result = tokio::spawn(future).await.expect("task panicked");
self.pending_tasks.fetch_sub(1, Ordering::Relaxed);
result
} else {
future.await
}
}
pub async fn execute_timed<F, T>(&self, future: F) -> T
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let start = std::time::Instant::now();
let result = self.execute(future).await;
let duration = start.elapsed().as_micros() as u64;
self.metrics.record_duration(duration);
result
}
pub fn metrics(&self) -> &HandlerMetrics {
&self.metrics
}
pub fn pending_tasks(&self) -> usize {
self.pending_tasks.load(Ordering::Relaxed)
}
}
pub struct LocalRunner {
local_set: LocalSet,
enabled: AtomicBool,
}
impl LocalRunner {
pub fn new() -> Self {
Self {
local_set: LocalSet::new(),
enabled: AtomicBool::new(true),
}
}
pub fn is_enabled(&self) -> bool {
self.enabled.load(Ordering::Relaxed)
}
pub fn set_enabled(&self, enabled: bool) {
self.enabled.store(enabled, Ordering::Relaxed);
}
pub fn spawn_local<F>(&self, future: F) -> tokio::task::JoinHandle<F::Output>
where
F: Future + 'static,
F::Output: 'static,
{
RUNTIME_STATS.record_local_spawn();
self.local_set.spawn_local(future)
}
pub async fn run<F, T>(&self, future: F) -> T
where
F: Future<Output = T>,
{
self.local_set.run_until(future).await
}
pub fn local_set(&self) -> &LocalSet {
&self.local_set
}
}
impl Default for LocalRunner {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct WorkStealingConfig {
pub global_queue_interval: u32,
pub event_interval: u32,
pub enabled: bool,
pub prefer_local: bool,
}
impl Default for WorkStealingConfig {
fn default() -> Self {
Self {
global_queue_interval: 61,
event_interval: 61,
enabled: true,
prefer_local: false,
}
}
}
impl WorkStealingConfig {
pub fn new() -> Self {
Self::default()
}
pub fn aggressive() -> Self {
Self {
global_queue_interval: 31,
event_interval: 31,
enabled: true,
prefer_local: false,
}
}
pub fn minimal() -> Self {
Self {
global_queue_interval: 255,
event_interval: 255,
enabled: true,
prefer_local: true,
}
}
pub fn disabled() -> Self {
Self {
enabled: false,
..Default::default()
}
}
pub fn apply_to_builder(&self, builder: &mut Builder) {
builder.global_queue_interval(self.global_queue_interval);
builder.event_interval(self.event_interval);
}
}
pub struct ManagedRuntime {
runtime: Runtime,
config: RuntimeConfig,
spawner: Arc<SmartSpawner>,
local_runner: Option<LocalRunner>,
}
impl ManagedRuntime {
pub fn new(config: RuntimeConfig) -> std::io::Result<Self> {
let spawn_config = if config.current_thread {
SpawnConfig::inline_all()
} else {
SpawnConfig::default()
};
let runtime = config.build()?;
let local_runner = if config.use_local_set {
Some(LocalRunner::new())
} else {
None
};
Ok(Self {
runtime,
config,
spawner: Arc::new(SmartSpawner::new(spawn_config)),
local_runner,
})
}
pub fn default_runtime() -> std::io::Result<Self> {
Self::new(RuntimeConfig::default())
}
pub fn throughput_runtime() -> std::io::Result<Self> {
Self::new(RuntimeConfig::throughput())
}
pub fn low_latency_runtime() -> std::io::Result<Self> {
Self::new(RuntimeConfig::low_latency())
}
pub fn single_threaded_runtime() -> std::io::Result<Self> {
Self::new(RuntimeConfig::single_threaded())
}
pub fn handle(&self) -> tokio::runtime::Handle {
self.runtime.handle().clone()
}
pub fn spawner(&self) -> Arc<SmartSpawner> {
Arc::clone(&self.spawner)
}
pub fn local_runner(&self) -> Option<&LocalRunner> {
self.local_runner.as_ref()
}
pub fn block_on<F: Future>(&self, future: F) -> F::Output {
if let Some(ref local) = self.local_runner {
self.runtime.block_on(local.run(future))
} else {
self.runtime.block_on(future)
}
}
pub fn spawn<F>(&self, future: F) -> tokio::task::JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
self.runtime.spawn(future)
}
pub fn config(&self) -> &RuntimeConfig {
&self.config
}
}
#[derive(Debug, Default)]
pub struct RuntimeStats {
tasks_spawned: AtomicU64,
tasks_inlined: AtomicU64,
local_spawns: AtomicU64,
blocking_tasks: AtomicU64,
}
impl RuntimeStats {
fn record_spawn_decision(&self, spawned: bool) {
if spawned {
self.tasks_spawned.fetch_add(1, Ordering::Relaxed);
} else {
self.tasks_inlined.fetch_add(1, Ordering::Relaxed);
}
}
fn record_local_spawn(&self) {
self.local_spawns.fetch_add(1, Ordering::Relaxed);
}
#[allow(dead_code)]
fn record_blocking(&self) {
self.blocking_tasks.fetch_add(1, Ordering::Relaxed);
}
pub fn tasks_spawned(&self) -> u64 {
self.tasks_spawned.load(Ordering::Relaxed)
}
pub fn tasks_inlined(&self) -> u64 {
self.tasks_inlined.load(Ordering::Relaxed)
}
pub fn local_spawns(&self) -> u64 {
self.local_spawns.load(Ordering::Relaxed)
}
pub fn spawn_ratio(&self) -> f64 {
let spawned = self.tasks_spawned() as f64;
let total = spawned + self.tasks_inlined() as f64;
if total > 0.0 { spawned / total } else { 0.0 }
}
}
static RUNTIME_STATS: RuntimeStats = RuntimeStats {
tasks_spawned: AtomicU64::new(0),
tasks_inlined: AtomicU64::new(0),
local_spawns: AtomicU64::new(0),
blocking_tasks: AtomicU64::new(0),
};
pub fn runtime_stats() -> &'static RuntimeStats {
&RUNTIME_STATS
}
fn num_cpus() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_runtime_config_default() {
let config = RuntimeConfig::default();
assert!(!config.current_thread);
assert!(config.enable_io);
assert!(config.enable_time);
}
#[test]
fn test_runtime_config_throughput() {
let config = RuntimeConfig::throughput();
assert!(config.global_queue_interval.unwrap() > 61);
assert!(config.max_blocking_threads.unwrap() > 512);
}
#[test]
fn test_runtime_config_low_latency() {
let config = RuntimeConfig::low_latency();
assert!(config.global_queue_interval.unwrap() < 61);
assert!(config.worker_threads.unwrap() <= 4);
}
#[test]
fn test_runtime_config_single_threaded() {
let config = RuntimeConfig::single_threaded();
assert!(config.current_thread);
assert!(config.use_local_set);
}
#[test]
fn test_runtime_config_builder() {
let config = RuntimeConfig::new()
.worker_threads(8)
.thread_name("test")
.global_queue_interval(100);
assert_eq!(config.worker_threads, Some(8));
assert_eq!(config.thread_name, "test");
assert_eq!(config.global_queue_interval, Some(100));
}
#[test]
fn test_spawn_policy() {
assert_eq!(SpawnPolicy::default(), SpawnPolicy::Adaptive);
}
#[test]
fn test_spawn_config() {
let config = SpawnConfig::inline_all();
assert_eq!(config.policy, SpawnPolicy::Never);
let config = SpawnConfig::always_spawn();
assert_eq!(config.policy, SpawnPolicy::Always);
}
#[test]
fn test_handler_metrics() {
let metrics = HandlerMetrics::new();
assert_eq!(metrics.avg_duration_us(), 0);
metrics.record_duration(100);
assert_eq!(metrics.avg_duration_us(), 100);
metrics.record_duration(200);
let avg = metrics.avg_duration_us();
assert!(avg > 100 && avg < 200);
}
#[test]
fn test_smart_spawner_always() {
let spawner = SmartSpawner::new(SpawnConfig::always_spawn());
assert!(spawner.should_spawn(None));
assert!(spawner.should_spawn(Some(1)));
}
#[test]
fn test_smart_spawner_never() {
let spawner = SmartSpawner::new(SpawnConfig::inline_all());
assert!(!spawner.should_spawn(None));
assert!(!spawner.should_spawn(Some(1000)));
}
#[test]
fn test_smart_spawner_adaptive() {
let config = SpawnConfig::new().duration_threshold(50);
let spawner = SmartSpawner::new(config);
assert!(!spawner.should_spawn(Some(10))); assert!(spawner.should_spawn(Some(100))); }
#[test]
fn test_work_stealing_config() {
let aggressive = WorkStealingConfig::aggressive();
assert!(aggressive.global_queue_interval < 61);
let minimal = WorkStealingConfig::minimal();
assert!(minimal.global_queue_interval > 61);
assert!(minimal.prefer_local);
}
#[test]
fn test_local_runner() {
let runner = LocalRunner::new();
assert!(runner.is_enabled());
runner.set_enabled(false);
assert!(!runner.is_enabled());
}
#[test]
fn test_runtime_stats() {
let stats = runtime_stats();
let _ = stats.tasks_spawned();
let _ = stats.tasks_inlined();
let _ = stats.spawn_ratio();
}
#[test]
fn test_managed_runtime_spawn() {
let runtime = ManagedRuntime::default_runtime().unwrap();
let result = runtime.block_on(async { 42 });
assert_eq!(result, 42);
}
#[test]
fn test_build_runtime() {
let config = RuntimeConfig::default();
let runtime = config.build();
assert!(runtime.is_ok());
}
}