use core::cell::Cell;
use core::fmt;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use super::waiters::{Key, State, Waiters, wake_closed};
use crate::sync::{AcquireError, TryAcquireError};
pub struct Semaphore {
permits: Cell<isize>,
closed: Cell<bool>,
waiters: Waiters,
}
impl Semaphore {
pub const MAX_PERMITS: usize = usize::MAX >> 3;
pub const fn new(permits: usize) -> Self {
assert!(
permits <= Self::MAX_PERMITS,
"a semaphore cannot hold more than Semaphore::MAX_PERMITS"
);
Self {
permits: Cell::new(permits as isize),
closed: Cell::new(false),
waiters: Waiters::new(),
}
}
pub fn available_permits(&self) -> usize {
self.permits.get().max(0) as usize
}
#[track_caller]
pub fn add_permits(&self, n: usize) {
let total = self
.permits
.get()
.saturating_add(n.min(Self::MAX_PERMITS + 1) as isize);
assert!(
total <= Self::MAX_PERMITS as isize,
"a semaphore cannot hold more than Semaphore::MAX_PERMITS"
);
self.permits.set(total);
self.serve();
}
pub fn forget_permits(&self, n: usize) -> usize {
let removed = n.min(self.available_permits());
self.permits.set(self.permits.get() - removed as isize);
removed
}
pub fn acquire(&self) -> Acquire<'_> {
self.acquire_many(1)
}
#[track_caller]
pub fn acquire_many(&self, n: u32) -> Acquire<'_> {
Acquire {
sem: self,
n: checked_permits(n),
key: None,
}
}
pub fn try_acquire(&self) -> Result<SemaphorePermit<'_>, TryAcquireError> {
self.try_acquire_many(1)
}
#[track_caller]
pub fn try_acquire_many(&self, n: u32) -> Result<SemaphorePermit<'_>, TryAcquireError> {
let permits = checked_permits(n);
if self.closed.get() {
return Err(TryAcquireError::Closed);
}
if !self.waiters.is_empty() || self.permits.get() < permits as isize {
return Err(TryAcquireError::NoPermits);
}
self.permits.set(self.permits.get() - permits as isize);
Ok(SemaphorePermit { sem: self, permits })
}
pub fn close(&self) {
if self.closed.replace(true) {
return;
}
wake_closed(&self.waiters);
}
pub fn is_closed(&self) -> bool {
self.closed.get()
}
pub(crate) fn release(&self, permits: usize) {
self.permits
.set(self.permits.get().saturating_add(permits as isize));
self.serve();
}
pub(crate) fn debit(&self, n: usize) {
self.permits.set(self.permits.get() - n as isize);
}
#[inline]
pub(crate) fn holds(&self, n: usize) -> bool {
self.permits.get() == n as isize
}
#[inline]
fn serve(&self) {
if self.closed.get() {
return;
}
if let Ok(available) = usize::try_from(self.permits.get())
&& self.waiters.front_fits(available)
{
self.serve_waiters();
}
}
#[inline(never)]
fn serve_waiters(&self) {
let rest = ServeRest(self);
while let Ok(available) = usize::try_from(self.permits.get())
&& let Some((need, waker)) = self.waiters.grant_front_if(available)
{
self.permits.set(self.permits.get() - need as isize);
if let Some(waker) = waker {
waker.wake();
}
}
core::mem::forget(rest);
}
}
struct ServeRest<'a>(&'a Semaphore);
impl Drop for ServeRest<'_> {
fn drop(&mut self) {
crate::sync::internal::during_unwind(|| self.0.serve());
}
}
#[expect(
clippy::panic,
reason = "documented under `# Panics` of the calls that take permits"
)]
#[track_caller]
fn checked_permits(n: u32) -> usize {
match usize::try_from(n) {
Ok(permits) if permits <= Semaphore::MAX_PERMITS => permits,
_ => panic!("a request exceeds Semaphore::MAX_PERMITS"),
}
}
impl fmt::Debug for Semaphore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Semaphore")
.field("available_permits", &self.available_permits())
.field("closed", &self.is_closed())
.finish()
}
}
#[must_use = "dropping a permit returns it at once"]
pub struct SemaphorePermit<'a> {
sem: &'a Semaphore,
permits: usize,
}
impl<'a> SemaphorePermit<'a> {
pub(crate) fn new(sem: &'a Semaphore, permits: usize) -> Self {
Self { sem, permits }
}
pub fn forget(mut self) {
self.permits = 0;
}
pub fn num_permits(&self) -> usize {
self.permits
}
#[track_caller]
pub fn merge(&mut self, mut other: Self) {
assert!(
core::ptr::eq(self.sem, other.sem),
"merging permits from different semaphore instances"
);
self.permits += other.permits;
other.permits = 0;
}
}
impl Drop for SemaphorePermit<'_> {
fn drop(&mut self) {
if self.permits > 0 {
self.sem.release(self.permits);
}
}
}
impl fmt::Debug for SemaphorePermit<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SemaphorePermit")
.field("permits", &self.permits)
.finish()
}
}
#[must_use = "futures do nothing unless polled"]
pub struct Acquire<'a> {
sem: &'a Semaphore,
n: usize,
key: Option<Key>,
}
impl Acquire<'_> {
#[inline]
pub(crate) fn cancel(&mut self) {
if self.key.is_some() {
self.leave_line();
}
}
#[inline(never)]
fn leave_line(&mut self) {
let Some(key) = self.key.take() else {
return;
};
let (state, was_front, waker) = self.sem.waiters.cancel(key);
drop(waker);
match state {
State::Waiting if was_front => self.sem.serve(),
State::Granted => self.sem.release(self.n),
_ => {}
}
}
}
impl<'a> Acquire<'a> {
#[cold]
#[inline(never)]
fn queue(&mut self, cx: &mut Context<'_>) -> Poll<<Self as Future>::Output> {
let this = self;
let sem = this.sem;
let waker = cx.waker().clone();
if sem.closed.get() {
crate::coop::spent_one();
drop(waker);
return Poll::Ready(Err(AcquireError::closed()));
}
if sem.waiters.is_empty() && sem.permits.get() >= this.n as isize {
sem.permits.set(sem.permits.get() - this.n as isize);
crate::coop::spent_one();
let permit = SemaphorePermit::new(sem, this.n);
drop(waker);
return Poll::Ready(Ok(permit));
}
this.key = Some(sem.waiters.push(this.n, waker));
Poll::Pending
}
}
impl<'a> Future for Acquire<'a> {
type Output = Result<SemaphorePermit<'a>, AcquireError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
let sem = this.sem;
if let Some(key) = this.key {
match sem.waiters.refresh(key, cx.waker()) {
State::Waiting => return Poll::Pending,
State::Granted => {
sem.waiters.take(key);
this.key = None;
return Poll::Ready(Ok(SemaphorePermit::new(sem, this.n)));
}
State::Closed => {
sem.waiters.take(key);
this.key = None;
return Poll::Ready(Err(AcquireError::closed()));
}
State::Free => this.key = None,
}
}
if crate::coop::exhausted(cx.waker()) {
return Poll::Pending;
}
if sem.closed.get() {
crate::coop::spent_one();
return Poll::Ready(Err(AcquireError::closed()));
}
if sem.waiters.is_empty() && sem.permits.get() >= this.n as isize {
sem.permits.set(sem.permits.get() - this.n as isize);
crate::coop::spent_one();
return Poll::Ready(Ok(SemaphorePermit::new(sem, this.n)));
}
this.queue(cx)
}
}
impl Drop for Acquire<'_> {
fn drop(&mut self) {
self.cancel();
}
}
impl fmt::Debug for Acquire<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Acquire")
.field("permits", &self.n)
.field("waiting", &self.key.is_some())
.finish()
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use core::cell::RefCell;
use core::task::Waker;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::rc::Rc;
use super::*;
use crate::sync::test_support::{Polled, hooked};
struct Leaked(core::ptr::NonNull<Semaphore>);
impl Leaked {
fn sem(&self) -> &'static Semaphore {
unsafe { self.0.as_ref() }
}
}
impl Drop for Leaked {
fn drop(&mut self) {
drop(unsafe { Box::from_raw(self.0.as_ptr()) });
}
}
fn leaked(permits: usize) -> Leaked {
let sem = Box::into_raw(Box::new(Semaphore::new(permits)));
Leaked(unsafe { core::ptr::NonNull::new_unchecked(sem) })
}
fn boom() -> Waker {
hooked(
None,
Some(Box::new(|| panic!("a waker panicked as it was woken"))),
)
}
fn release(held: &Rc<RefCell<Option<SemaphorePermit<'static>>>>) -> Waker {
let held = Rc::clone(held);
hooked(Some(Box::new(move || drop(held.borrow_mut().take()))), None)
}
fn held(sem: &'static Semaphore) -> Rc<RefCell<Option<SemaphorePermit<'static>>>> {
Rc::new(RefCell::new(Some(sem.try_acquire().unwrap())))
}
#[test]
fn an_acquire_whose_clone_releases_a_permit_takes_it() {
let owner = leaked(1);
let sem = owner.sem();
let held = held(sem);
let mut acquire = Box::pin(sem.acquire());
let polled = acquire
.as_mut()
.poll(&mut Context::from_waker(&release(&held)));
let Poll::Ready(Ok(permit)) = polled else {
panic!("the acquire slept beside a free permit");
};
assert_eq!(permit.num_permits(), 1);
assert_eq!(sem.available_permits(), 0);
}
#[test]
fn an_acquire_whose_clone_closes_the_semaphore_fails() {
let owner = leaked(0);
let sem = owner.sem();
let closer = hooked(Some(Box::new(move || sem.close())), None);
let mut acquire = Box::pin(sem.acquire());
let polled = acquire.as_mut().poll(&mut Context::from_waker(&closer));
assert!(matches!(polled, Poll::Ready(Err(_))));
}
#[test]
fn a_refresh_whose_clone_causes_the_grant_reports_it() {
let owner = leaked(1);
let sem = owner.sem();
let held = held(sem);
let first = Polled::new(async {});
let mut acquire = Box::pin(sem.acquire());
let queued = acquire
.as_mut()
.poll(&mut Context::from_waker(&first.waker()));
assert!(queued.is_pending());
let polled = acquire
.as_mut()
.poll(&mut Context::from_waker(&release(&held)));
assert!(matches!(polled, Poll::Ready(Ok(_))), "the grant was missed");
}
#[test]
fn a_close_whose_first_waker_panics_still_ends_every_other_wait() {
let owner = leaked(0);
let sem = owner.sem();
let mut first = Box::pin(sem.acquire());
assert!(
first
.as_mut()
.poll(&mut Context::from_waker(&boom()))
.is_pending()
);
let mut second = Polled::new(sem.acquire());
second.pending();
assert!(catch_unwind(AssertUnwindSafe(|| sem.close())).is_err());
assert!(sem.is_closed());
assert!(second.ready().is_err());
let polled = first.as_mut().poll(&mut Context::from_waker(Waker::noop()));
assert!(matches!(polled, Poll::Ready(Err(_))));
}
#[test]
fn a_serve_whose_first_waker_panics_still_serves_the_next_waiter() {
let owner = leaked(0);
let sem = owner.sem();
let mut first = Box::pin(sem.acquire());
assert!(
first
.as_mut()
.poll(&mut Context::from_waker(&boom()))
.is_pending()
);
let mut second = Polled::new(sem.acquire());
second.pending();
assert!(catch_unwind(AssertUnwindSafe(|| sem.add_permits(2))).is_err());
assert_eq!(second.wakes(), 1);
let permit = second.ready().unwrap();
assert_eq!(permit.num_permits(), 1);
let polled = first.as_mut().poll(&mut Context::from_waker(Waker::noop()));
assert!(matches!(polled, Poll::Ready(Ok(_))));
}
#[test]
fn a_debt_never_shows_as_negative_permits() {
let sem = Semaphore::new(2);
sem.debit(5);
assert_eq!(sem.available_permits(), 0);
assert_eq!(sem.try_acquire().unwrap_err(), TryAcquireError::NoPermits);
sem.add_permits(3);
assert_eq!(sem.available_permits(), 0);
assert!(sem.try_acquire().is_err());
sem.add_permits(1);
assert_eq!(sem.available_permits(), 1);
assert_eq!(sem.forget_permits(5), 1);
assert_eq!(sem.available_permits(), 0);
}
}