use std::sync::Arc;
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
use crate::error::{AgError, AgResult};
pub(crate) fn default_blocking_permits() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
.max(1)
}
#[derive(Clone, Debug)]
pub struct CpuLimiter(Arc<Semaphore>);
impl CpuLimiter {
pub fn new(permits: usize) -> Self {
Self(Arc::new(Semaphore::new(
permits.clamp(1, Semaphore::MAX_PERMITS),
)))
}
pub(crate) async fn acquire(&self) -> AgResult<OwnedSemaphorePermit> {
Arc::clone(&self.0)
.acquire_owned()
.await
.map_err(|_| AgError::Warp("cpu limiter closed".into()))
}
pub fn available_permits(&self) -> usize {
self.0.available_permits()
}
}
impl Default for CpuLimiter {
fn default() -> Self {
Self::new(default_blocking_permits())
}
}
#[cfg(test)]
mod tests {
use super::CpuLimiter;
#[test]
fn new_clamps_zero_permits_to_one() {
assert_eq!(CpuLimiter::new(0).available_permits(), 1);
assert_eq!(CpuLimiter::new(4).available_permits(), 4);
}
#[test]
fn oversized_cpu_limit_does_not_panic() {
assert_eq!(
CpuLimiter::new(usize::MAX).available_permits(),
tokio::sync::Semaphore::MAX_PERMITS
);
}
#[tokio::test]
async fn clones_share_one_permit_pool() {
let a = CpuLimiter::new(1);
let b = a.clone();
let permit = a.acquire().await.unwrap();
assert_eq!(b.available_permits(), 0);
drop(permit);
assert_eq!(b.available_permits(), 1);
}
}