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};
use crate::sync::{AcquireError, TryAcquireError};
pub struct Semaphore {
permits: Cell<usize>,
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),
closed: Cell::new(false),
waiters: Waiters::new(),
}
}
pub fn available_permits(&self) -> usize {
self.permits.get()
}
#[track_caller]
pub fn add_permits(&self, n: usize) {
let total = self.permits.get().saturating_add(n);
assert!(
total <= Self::MAX_PERMITS,
"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.permits.get());
self.permits.set(self.permits.get() - removed);
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 {
return Err(TryAcquireError::NoPermits);
}
self.permits.set(self.permits.get() - permits);
Ok(SemaphorePermit { sem: self, permits })
}
pub fn close(&self) {
if self.closed.replace(true) {
return;
}
while let Some(waker) = self.waiters.close_front() {
if let Some(waker) = waker {
waker.wake();
}
}
}
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));
self.serve();
}
fn serve(&self) {
if self.closed.get() {
return;
}
while let Some((need, waker)) = self.waiters.grant_front_if(self.permits.get()) {
self.permits.set(self.permits.get() - need);
if let Some(waker) = waker {
waker.wake();
}
}
}
}
#[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> 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 {
sem.permits.set(sem.permits.get() - this.n);
crate::coop::spent_one();
return Poll::Ready(Ok(SemaphorePermit::new(sem, this.n)));
}
this.key = Some(sem.waiters.push(this.n, cx.waker().clone()));
Poll::Pending
}
}
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()
}
}