use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};
pub(crate) struct Semaphore {
inner: Mutex<Inner>,
}
struct Inner {
permits: usize,
waking: usize,
waiters: VecDeque<Waker>,
}
impl Semaphore {
pub(crate) fn shared(permits: usize) -> Arc<Self> {
Arc::new(Self {
inner: Mutex::new(Inner {
permits,
waking: 0,
waiters: VecDeque::new(),
}),
})
}
pub(crate) fn acquire_owned(self: Arc<Self>) -> Acquire {
Acquire { sem: self }
}
pub(crate) fn try_acquire_owned(self: &Arc<Self>) -> Option<OwnedPermit> {
let mut inner = self.inner.lock().unwrap();
if inner.permits > 0 {
inner.permits -= 1;
} else if inner.waking > 0 {
inner.waking -= 1;
} else {
return None;
}
Some(OwnedPermit {
sem: Arc::clone(self),
})
}
}
pub(crate) struct Acquire {
sem: Arc<Semaphore>,
}
impl Future for Acquire {
type Output = OwnedPermit;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut inner = self.sem.inner.lock().unwrap();
if inner.permits > 0 {
inner.permits -= 1;
return Poll::Ready(OwnedPermit {
sem: Arc::clone(&self.sem),
});
}
if inner.waking > 0 {
inner.waking -= 1;
return Poll::Ready(OwnedPermit {
sem: Arc::clone(&self.sem),
});
}
if !inner
.waiters
.back()
.is_some_and(|waker| waker.will_wake(cx.waker()))
{
inner.waiters.push_back(cx.waker().clone());
}
Poll::Pending
}
}
pub(crate) struct OwnedPermit {
sem: Arc<Semaphore>,
}
impl Drop for OwnedPermit {
fn drop(&mut self) {
let mut inner = self.sem.inner.lock().unwrap();
if let Some(waker) = inner.waiters.pop_front() {
inner.waking += 1;
waker.wake();
} else {
inner.permits += 1;
}
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{Context, Poll, Wake, Waker};
use tokio::time::{Duration, timeout};
use super::Semaphore;
#[derive(Default)]
struct FlagWaker(Arc<AtomicBool>);
impl Wake for FlagWaker {
fn wake(self: Arc<Self>) {
self.0.store(true, Ordering::SeqCst);
}
}
impl FlagWaker {
fn new() -> (Self, Arc<AtomicBool>) {
let flag = Arc::new(AtomicBool::new(false));
(Self(Arc::clone(&flag)), flag)
}
fn poll_once<F: Future>(&self, future: &mut Pin<Box<F>>) -> Poll<F::Output> {
let waker = Waker::from(Arc::new(FlagWaker(Arc::clone(&self.0))));
let mut cx = Context::from_waker(&waker);
future.as_mut().poll(&mut cx)
}
}
#[tokio::test]
async fn a_dropped_permit_frees_its_slot() {
let semaphore = Semaphore::shared(1);
let permit = semaphore.clone().acquire_owned().await;
drop(permit);
timeout(Duration::from_secs(5), semaphore.clone().acquire_owned())
.await
.expect("the freed slot is immediately re-acquirable");
}
#[test]
fn try_acquire_never_waits_and_a_drop_restores_the_slot() {
let semaphore = Semaphore::shared(1);
let permit = semaphore
.try_acquire_owned()
.expect("the initial slot is available");
assert!(
semaphore.try_acquire_owned().is_none(),
"an exhausted semaphore rejects immediately"
);
drop(permit);
assert!(
semaphore.try_acquire_owned().is_some(),
"dropping the owned permit restores the slot"
);
}
#[tokio::test]
async fn acquisition_waits_while_the_limit_is_exhausted() {
let semaphore = Semaphore::shared(1);
let held = semaphore.clone().acquire_owned().await;
let (done_tx, mut done_rx) = tokio::sync::oneshot::channel();
let contender = semaphore.clone();
tokio::spawn(async move {
let _permit = contender.acquire_owned().await;
let _ = done_tx.send(());
});
assert!(
timeout(Duration::from_millis(50), &mut done_rx)
.await
.is_err(),
"no slot is free while the one permit is held"
);
drop(held);
let _ = timeout(Duration::from_secs(5), done_rx)
.await
.expect("releasing the held permit unblocks the waiter");
}
#[test]
fn a_release_wakes_only_the_longest_waiting_acquirer() {
let semaphore = Semaphore::shared(1);
let poller = FlagWaker::default();
let mut held = Box::pin(semaphore.clone().acquire_owned());
let held_permit = match poller.poll_once(&mut held) {
Poll::Ready(permit) => permit,
Poll::Pending => panic!("the first acquisition takes the one free slot"),
};
let mut waiters: Vec<(Pin<Box<_>>, Arc<AtomicBool>)> = Vec::new();
for _ in 0..3 {
let (waker, flag) = FlagWaker::new();
let mut acquire = Box::pin(semaphore.clone().acquire_owned());
assert!(waker.poll_once(&mut acquire).is_pending());
assert!(!flag.load(Ordering::SeqCst), "no slot frees yet");
waiters.push((acquire, flag));
}
drop(held_permit);
assert!(
waiters[0].1.load(Ordering::SeqCst),
"the longest-waiting acquirer is woken first"
);
assert!(
!waiters[1].1.load(Ordering::SeqCst) && !waiters[2].1.load(Ordering::SeqCst),
"one release wakes exactly one waiter"
);
let (mut first, _) = waiters.remove(0);
let permit = match poller.poll_once(&mut first) {
Poll::Ready(permit) => permit,
Poll::Pending => panic!("the woken waiter acquires the freed slot"),
};
drop(permit);
assert!(waiters[0].1.load(Ordering::SeqCst));
assert!(!waiters[1].1.load(Ordering::SeqCst));
}
#[test]
fn a_cancelled_waiter_cannot_lose_a_released_slot() {
let semaphore = Semaphore::shared(1);
let poller = FlagWaker::default();
let mut held = Box::pin(semaphore.clone().acquire_owned());
let held_permit = match poller.poll_once(&mut held) {
Poll::Ready(permit) => permit,
Poll::Pending => panic!("the first acquisition takes the one free slot"),
};
let (waker_a, flag_a) = FlagWaker::new();
let mut a = Box::pin(semaphore.clone().acquire_owned());
assert!(waker_a.poll_once(&mut a).is_pending());
drop(a);
let (waker_b, flag_b) = FlagWaker::new();
let mut b = Box::pin(semaphore.clone().acquire_owned());
assert!(waker_b.poll_once(&mut b).is_pending());
assert!(!flag_b.load(Ordering::SeqCst), "no slot frees yet");
drop(held_permit);
assert!(
flag_a.load(Ordering::SeqCst),
"the release wakes the longest-waiting waker, stale or not"
);
assert!(!flag_b.load(Ordering::SeqCst));
match poller.poll_once(&mut b) {
Poll::Ready(_permit) => {}
Poll::Pending => panic!("the released slot must reach the live waiter"),
}
}
}