use async_trait::async_trait;
use parking_lot::{Condvar, Mutex};
use std::sync::Arc;
#[async_trait]
pub trait BytePermits: Send + Sync {
async fn acquire(&self, n_bytes: usize) -> Permit;
}
struct SemInner {
available: usize,
max_bytes: usize,
}
pub struct Permit {
inner: Option<PermitInner>,
}
enum PermitInner {
ByteSem(Arc<(Mutex<SemInner>, Condvar)>, usize),
NoOp,
}
impl Drop for Permit {
fn drop(&mut self) {
match self.inner.take() {
Some(PermitInner::ByteSem(inner, n_bytes)) => {
let (mutex, condvar) = &*inner;
let mut guard = mutex.lock();
guard.available += n_bytes;
drop(guard);
condvar.notify_all();
}
Some(PermitInner::NoOp) | None => {}
}
}
}
impl Permit {
pub(crate) const fn noop() -> Self {
Self {
inner: Some(PermitInner::NoOp),
}
}
fn byte_sem(inner: Arc<(Mutex<SemInner>, Condvar)>, n_bytes: usize) -> Self {
Self {
inner: Some(PermitInner::ByteSem(inner, n_bytes)),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoOpPermits;
#[async_trait]
impl BytePermits for NoOpPermits {
async fn acquire(&self, _n_bytes: usize) -> Permit {
Permit::noop()
}
}
#[derive(Clone)]
pub struct SemaphorePermits {
inner: Arc<(Mutex<SemInner>, Condvar)>,
}
impl SemaphorePermits {
#[must_use]
pub fn new(max_bytes: usize) -> Self {
Self {
inner: Arc::new((
Mutex::new(SemInner {
available: max_bytes,
max_bytes,
}),
Condvar::new(),
)),
}
}
}
#[async_trait]
impl BytePermits for SemaphorePermits {
async fn acquire(&self, n_bytes: usize) -> Permit {
if n_bytes == 0 {
return Permit::noop();
}
{
let (mutex, _condvar) = &*self.inner;
if let Some(mut guard) = mutex.try_lock() {
let claim = n_bytes.min(guard.max_bytes);
if guard.available >= claim {
guard.available -= claim;
drop(guard);
return Permit::byte_sem(self.inner.clone(), claim);
}
}
}
#[cfg(test)]
SLOW_PATH_ENTRIES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let inner = self.inner.clone();
let actual = crate::rt::spawn_blocking(move || {
let (mutex, condvar) = &*inner;
let mut guard = mutex.lock();
let claim = n_bytes.min(guard.max_bytes);
while guard.available < claim {
condvar.wait(&mut guard);
}
guard.available -= claim;
claim
})
.await;
Permit::byte_sem(self.inner.clone(), actual)
}
}
#[cfg(test)]
static SLOW_PATH_ENTRIES: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::Ordering;
#[test]
fn uncontended_acquire_takes_fast_path() {
let permits = SemaphorePermits::new(1024 * 1024);
let rt = crate::rt::LocalRuntime::new().unwrap();
SLOW_PATH_ENTRIES.store(0, Ordering::Relaxed);
rt.block_on(async {
for _ in 0..100 {
let permit = permits.acquire(1024).await;
drop(permit);
}
});
assert_eq!(
SLOW_PATH_ENTRIES.load(Ordering::Relaxed),
0,
"uncontended acquires must not hit the spawn_blocking slow path"
);
}
#[test]
fn insufficient_capacity_uses_slow_path() {
let permits = SemaphorePermits::new(1024);
let rt = crate::rt::LocalRuntime::new().unwrap();
SLOW_PATH_ENTRIES.store(0, Ordering::Relaxed);
rt.block_on(async {
let p1 = permits.acquire(1024).await; drop(p1);
let _p2 = permits.acquire(1024).await;
});
}
#[test]
fn noop_permits_always_succeed() {
let permits = NoOpPermits;
let rt = crate::rt::LocalRuntime::new().unwrap();
rt.block_on(async {
let _p1 = permits.acquire(1024).await;
let _p2 = permits.acquire(1_000_000).await;
});
}
#[test]
fn semaphore_permits_enforce_limit() {
let permits = SemaphorePermits::new(1024);
let rt = crate::rt::LocalRuntime::new().unwrap();
rt.block_on(async {
let p1 = permits.acquire(1024).await;
drop(p1);
let _p2 = permits.acquire(512).await;
let _p3 = permits.acquire(512).await;
});
}
#[test]
fn semaphore_permits_release_on_drop() {
let permits = SemaphorePermits::new(1000);
let rt = crate::rt::LocalRuntime::new().unwrap();
rt.block_on(async {
{
let _p1 = permits.acquire(500).await;
let _p2 = permits.acquire(500).await;
}
let _p3 = permits.acquire(1000).await;
});
}
#[test]
fn semaphore_permits_oversized_acquire_does_not_deadlock() {
let permits = SemaphorePermits::new(1024);
let rt = crate::rt::LocalRuntime::new().unwrap();
rt.block_on(async {
let permit = permits.acquire(2048).await; drop(permit);
let _p = permits.acquire(1024).await;
});
}
#[test]
fn semaphore_permits_single_atomic_acquire() {
let permits = SemaphorePermits::new(1024 * 1024); let rt = crate::rt::LocalRuntime::new().unwrap();
rt.block_on(async {
let permit = permits.acquire(512 * 1024).await; drop(permit);
});
}
}