use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use event_listener::Event;
use futures::FutureExt;
#[derive(Debug)]
pub struct Semaphore {
inner: async_lock::Semaphore,
closed: AtomicBool,
close_event: Event,
available: AtomicUsize,
}
impl Semaphore {
#[cfg(not(loom))]
#[must_use]
pub const fn new(permits: usize) -> Self {
Self {
inner: async_lock::Semaphore::new(permits),
closed: AtomicBool::new(false),
close_event: Event::new(),
available: AtomicUsize::new(permits),
}
}
#[cfg(loom)]
#[must_use]
pub fn new(permits: usize) -> Self {
Self {
inner: async_lock::Semaphore::new(permits),
closed: AtomicBool::new(false),
close_event: Event::new(),
available: AtomicUsize::new(permits),
}
}
pub async fn acquire(&self) -> Result<SemaphorePermit<'_>, AcquireError> {
if self.is_closed() {
return Err(AcquireError(()));
}
match self.try_acquire() {
Ok(permit) => {
if self.is_closed() {
drop(permit);
return Err(AcquireError(()));
}
return Ok(permit);
}
Err(TryAcquireError::Closed) => return Err(AcquireError(())),
Err(TryAcquireError::NoPermits) => {}
}
let listener = self.close_event.listen();
if self.is_closed() {
return Err(AcquireError(()));
}
let acquire_future = self.inner.acquire().fuse();
let mut close_future = listener.fuse();
futures::pin_mut!(acquire_future);
futures::select_biased! {
_ = close_future => Err(AcquireError(())),
guard = acquire_future => {
self.available.fetch_sub(1, Ordering::Release);
Ok(SemaphorePermit { guard: Some(guard), owner: self })
}
}
}
pub fn try_acquire(&self) -> Result<SemaphorePermit<'_>, TryAcquireError> {
if self.is_closed() {
return Err(TryAcquireError::Closed);
}
match self.inner.try_acquire() {
Some(guard) => {
self.available.fetch_sub(1, Ordering::Release);
Ok(SemaphorePermit {
guard: Some(guard),
owner: self,
})
}
None => Err(TryAcquireError::NoPermits),
}
}
pub fn add_permits(&self, n: usize) {
self.inner.add_permits(n);
self.available.fetch_add(n, Ordering::Release);
}
#[must_use]
pub fn available_permits(&self) -> usize {
self.available.load(Ordering::Acquire)
}
pub fn close(&self) {
self.closed.store(true, Ordering::SeqCst);
self.close_event.notify(usize::MAX);
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.closed.load(Ordering::SeqCst)
}
}
#[derive(Debug)]
pub struct SemaphorePermit<'a> {
guard: Option<async_lock::SemaphoreGuard<'a>>,
owner: &'a Semaphore,
}
impl SemaphorePermit<'_> {
pub fn forget(mut self) {
if let Some(guard) = self.guard.take() {
guard.forget();
}
}
}
impl Drop for SemaphorePermit<'_> {
fn drop(&mut self) {
if self.guard.is_some() {
self.owner.available.fetch_add(1, Ordering::Release);
}
}
}
#[derive(Debug, thiserror::Error)]
#[error("semaphore closed")]
pub struct AcquireError(());
#[derive(Debug, thiserror::Error)]
pub enum TryAcquireError {
#[error("semaphore closed")]
Closed,
#[error("no permits available")]
NoPermits,
}
pub use async_lock::{SemaphoreGuard, SemaphoreGuardArc};
#[cfg(all(test, not(loom)))]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::field_reassign_with_default,
clippy::type_complexity,
clippy::useless_vec,
clippy::needless_range_loop,
clippy::default_constructed_unit_structs
)]
use alloc::sync::Arc;
use alloc::task::Wake;
use core::future::Future;
use core::task::{Context, Poll, Waker};
use super::*;
#[derive(Default)]
struct WakeCounter(AtomicUsize);
impl Wake for WakeCounter {
fn wake(self: Arc<Self>) {
self.0.fetch_add(1, Ordering::Relaxed);
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
#[proxima::test]
async fn acquire_returns_ok_when_permit_available() {
let semaphore = Semaphore::new(2);
assert_eq!(semaphore.available_permits(), 2);
let permit = semaphore.acquire().await.expect("permit");
assert_eq!(semaphore.available_permits(), 1);
drop(permit);
assert_eq!(semaphore.available_permits(), 2);
}
#[proxima::test]
async fn try_acquire_returns_no_permits_when_exhausted() {
let semaphore = Semaphore::new(1);
let _held = semaphore.acquire().await.expect("first permit");
let result = semaphore.try_acquire();
assert!(matches!(result, Err(TryAcquireError::NoPermits)));
}
#[proxima::test]
async fn close_then_acquire_returns_acquire_error() {
let semaphore = Semaphore::new(1);
semaphore.close();
assert!(semaphore.is_closed());
let result = semaphore.acquire().await;
assert!(matches!(result, Err(AcquireError(_))));
}
#[proxima::test]
async fn close_wakes_pending_acquires() {
let semaphore = Semaphore::new(1);
let held = semaphore.acquire().await.expect("first permit");
let wake_counter = Arc::new(WakeCounter::default());
let waker = Waker::from(wake_counter.clone());
let mut context = Context::from_waker(&waker);
let mut pending_acquire = core::pin::pin!(semaphore.acquire());
assert!(matches!(
Future::poll(pending_acquire.as_mut(), &mut context),
Poll::Pending
));
assert_eq!(wake_counter.0.load(Ordering::Relaxed), 0);
semaphore.close();
assert_eq!(wake_counter.0.load(Ordering::Relaxed), 1);
assert!(matches!(
Future::poll(pending_acquire.as_mut(), &mut context),
Poll::Ready(Err(_))
));
wake_counter.0.store(0, Ordering::Relaxed);
drop(held);
assert_eq!(wake_counter.0.load(Ordering::Relaxed), 0);
}
#[proxima::test]
async fn release_wakes_one_pending_acquire() {
let semaphore = Semaphore::new(1);
let held = semaphore.acquire().await.expect("first permit");
let wake_counter = Arc::new(WakeCounter::default());
let waker = Waker::from(wake_counter.clone());
let mut context = Context::from_waker(&waker);
let mut pending_acquire = core::pin::pin!(semaphore.acquire());
assert!(matches!(
Future::poll(pending_acquire.as_mut(), &mut context),
Poll::Pending
));
assert_eq!(wake_counter.0.load(Ordering::Relaxed), 0);
drop(held);
assert_eq!(wake_counter.0.load(Ordering::Relaxed), 1);
assert!(matches!(
Future::poll(pending_acquire.as_mut(), &mut context),
Poll::Ready(Ok(_))
));
assert_eq!(semaphore.available_permits(), 1);
}
#[proxima::test]
async fn add_permits_increases_available_count() {
let semaphore = Semaphore::new(1);
semaphore.add_permits(3);
assert_eq!(semaphore.available_permits(), 4);
}
}