use super::*;
use core::sync::atomic::{AtomicU64, Ordering};
use event_listener::Event;
#[derive(Debug)]
pub struct AsyncWeightedSemaphore {
in_flight: AtomicU64,
limit: AtomicU64,
event: Event,
}
impl AsyncWeightedSemaphore {
#[must_use]
pub fn new(limit: u64) -> Arc<Self> {
Arc::new(Self {
in_flight: AtomicU64::new(0),
limit: AtomicU64::new(limit),
event: Event::new(),
})
}
pub fn limit(&self) -> u64 {
self.limit.load(Ordering::Acquire)
}
pub fn in_flight(&self) -> u64 {
self.in_flight.load(Ordering::Acquire)
}
pub fn set_limit(&self, new_limit: u64) {
let old = self.limit.swap(new_limit, Ordering::AcqRel);
if new_limit > old {
self.event.notify(usize::MAX);
}
}
pub fn raise_limit(&self, candidate: u64) -> u64 {
let prev = self.limit.fetch_max(candidate, Ordering::AcqRel);
if candidate > prev {
self.event.notify(usize::MAX);
}
prev
}
#[must_use]
pub fn try_acquire(self: &Arc<Self>, weight: u64) -> Option<AsyncWeightedSemaphoreGuard> {
if weight == 0 {
return Some(AsyncWeightedSemaphoreGuard {
sem: self.clone(),
weight: 0,
});
}
loop {
let cur = self.in_flight.load(Ordering::Acquire);
let lim = self.limit.load(Ordering::Acquire);
if cur != 0 && cur.saturating_add(weight) > lim {
return None;
}
if self
.in_flight
.compare_exchange_weak(
cur,
cur.saturating_add(weight),
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
return Some(AsyncWeightedSemaphoreGuard {
sem: self.clone(),
weight,
});
}
}
}
#[must_use]
pub async fn acquire(self: &Arc<Self>, weight: u64) -> AsyncWeightedSemaphoreGuard {
loop {
if let Some(g) = self.try_acquire(weight) {
return g;
}
let listener = self.event.listen();
if let Some(g) = self.try_acquire(weight) {
return g;
}
listener.await;
}
}
fn release(&self, weight: u64) {
if weight == 0 {
return;
}
self.in_flight.fetch_sub(weight, Ordering::AcqRel);
self.event.notify(usize::MAX);
}
}
#[clippy::has_significant_drop]
#[derive(Debug)]
pub struct AsyncWeightedSemaphoreGuard {
sem: Arc<AsyncWeightedSemaphore>,
weight: u64,
}
impl Drop for AsyncWeightedSemaphoreGuard {
fn drop(&mut self) {
self.sem.release(self.weight);
}
}