use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct CoalescingConfig {
pub max_delay: Duration,
pub max_coalesce_count: u32,
pub enabled: bool,
}
impl Default for CoalescingConfig {
fn default() -> Self {
Self {
max_delay: Duration::from_micros(50),
max_coalesce_count: 64,
enabled: true,
}
}
}
impl CoalescingConfig {
#[must_use]
pub fn for_net() -> Self {
Self {
max_delay: Duration::from_micros(50),
max_coalesce_count: 64,
enabled: true,
}
}
#[must_use]
pub fn for_block() -> Self {
Self {
max_delay: Duration::from_micros(25),
max_coalesce_count: 32,
enabled: true,
}
}
#[must_use]
pub fn for_fs() -> Self {
Self {
max_delay: Duration::from_micros(50),
max_coalesce_count: 64,
enabled: true,
}
}
#[must_use]
pub fn for_vsock() -> Self {
Self {
max_delay: Duration::from_micros(100),
max_coalesce_count: 128,
enabled: true,
}
}
#[must_use]
pub fn disabled() -> Self {
Self {
enabled: false,
..Self::default()
}
}
}
pub struct CoalescingState {
pub pending_count: AtomicU32,
pub timer_armed: AtomicBool,
pub last_armed: Mutex<Option<Instant>>,
pub config: CoalescingConfig,
}
impl CoalescingState {
#[must_use]
pub fn new(config: CoalescingConfig) -> Self {
Self {
pending_count: AtomicU32::new(0),
timer_armed: AtomicBool::new(false),
last_armed: Mutex::new(None),
config,
}
}
pub fn record(&self) -> bool {
let count = self.pending_count.fetch_add(1, Ordering::Relaxed);
if count + 1 >= self.config.max_coalesce_count {
return true;
}
if count == 0 {
self.timer_armed.store(true, Ordering::Release);
*self.last_armed.lock().unwrap_or_else(|e| e.into_inner()) = Some(Instant::now());
}
false
}
pub fn flush(&self) -> u32 {
let count = self.pending_count.swap(0, Ordering::SeqCst);
self.timer_armed.store(false, Ordering::Release);
*self.last_armed.lock().unwrap_or_else(|e| e.into_inner()) = None;
count
}
#[must_use]
pub fn timer_expired(&self) -> bool {
if !self.timer_armed.load(Ordering::Acquire) {
return false;
}
let guard = self.last_armed.lock().unwrap_or_else(|e| e.into_inner());
guard.is_some_and(|armed_at| armed_at.elapsed() >= self.config.max_delay)
}
}