#![allow(unsafe_code)]
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll, Waker};
use super::contend::{self, CLOSED_GRANT, Closed, Contend, Lock, taken_at_once};
use super::internal::{Ordering, UnsafeCell, fence};
use super::list::List;
use super::queue::{
Arrivals, FIRST_BIT, HANDOFF, Nudges, Policy, QUEUED, Step, WAKE_FRONT, WaitQueue, wants_drain,
};
use super::waiter::WakeList;
const CLOSED: usize = 1 << FIRST_BIT;
pub(super) const SHIFT: u32 = FIRST_BIT + 1;
const FLAGS: usize = (1 << SHIFT) - 1;
pub(super) const ONE: usize = 1 << SHIFT;
pub(super) const MAX: usize = usize::MAX >> SHIFT;
pub(super) fn closed_in(state: usize) -> bool {
state & CLOSED != 0
}
pub(super) fn close_step(state: usize) -> Step {
if closed_in(state) {
Step::Keep
} else {
Step::Drain(state | CLOSED)
}
}
pub(super) fn observed_closed(state: usize) -> bool {
let closed = closed_in(state);
if closed {
fence(Ordering::Acquire);
}
closed
}
pub(super) struct RawSemaphore {
queue: WaitQueue,
waiting: UnsafeCell<List>,
}
unsafe impl Send for RawSemaphore {}
unsafe impl Sync for RawSemaphore {}
impl RawSemaphore {
loom_const_fn! {
pub(super) fn new(permits: usize) -> Self {
assert!(permits <= MAX, "a semaphore holds at most Semaphore::MAX_PERMITS permits");
Self {
queue: WaitQueue::new(permits << SHIFT),
waiting: UnsafeCell::new(List::new()),
}
}
}
loom_const_fn! {
pub(super) fn without_spin(permits: usize) -> Self {
assert!(permits <= MAX, "a semaphore holds at most Semaphore::MAX_PERMITS permits");
Self {
queue: WaitQueue::without_spin(permits << SHIFT),
waiting: UnsafeCell::new(List::new()),
}
}
}
loom_const_fn! {
pub(super) fn new_closed() -> Self {
Self {
queue: WaitQueue::without_spin(CLOSED),
waiting: UnsafeCell::new(List::new()),
}
}
}
pub(super) fn available(&self) -> usize {
self.queue.state.load(Ordering::Acquire) >> SHIFT
}
pub(super) fn is_closed(&self) -> bool {
closed_in(self.queue.state.load(Ordering::Acquire))
}
pub(super) fn try_acquire(&self, n: usize) -> bool {
self.try_take(n).is_ok()
}
pub(super) fn try_acquire_permit(&self, n: usize) -> Result<RawPermit<'_>, usize> {
self.try_take(n).map(|()| RawPermit { sem: self, n })
}
#[inline]
pub(super) fn take_one(&self) -> Result<(), usize> {
let state = &self.queue.state;
let seen = state.load(Ordering::Relaxed);
if seen == ONE {
return state
.compare_exchange(ONE, 0, Ordering::Acquire, Ordering::Relaxed)
.map(drop);
}
if seen >= ONE && seen & (HANDOFF | CLOSED) == 0 {
state
.compare_exchange(seen, seen - ONE, Ordering::Acquire, Ordering::Relaxed)
.map(drop)
} else {
Err(seen)
}
}
#[inline(never)]
pub(super) fn try_acquire_one_from(&self, seen: usize) -> bool {
contend::take_from(self, 1, false, seen).is_ok()
}
#[inline]
pub(super) fn release(&self, n: usize) {
if n == 0 {
return;
}
let before = self.queue.state.fetch_add(n << SHIFT, Ordering::AcqRel);
if wants_drain(before) {
self.release_wakes();
}
}
#[inline(never)]
fn release_wakes(&self) {
self.released();
}
#[expect(
clippy::panic,
reason = "documented under `# Panics` of the calls that add permits"
)]
pub(super) fn add(&self, n: usize) {
if n == 0 {
return;
}
self.transition(|state| {
let permits = match (state >> SHIFT).checked_add(n) {
Some(permits) if permits <= MAX => permits,
_ => panic!("a semaphore holds at most Semaphore::MAX_PERMITS permits"),
};
let next = (state & FLAGS) | (permits << SHIFT);
if state & QUEUED != 0 {
Step::Wake(next | WAKE_FRONT)
} else {
Step::Set(next)
}
});
}
pub(super) fn forget(&self, n: usize) -> usize {
if n == 0 {
return 0;
}
let state = &self.queue.state;
let mut current = state.load(Ordering::Acquire);
loop {
let free = current >> SHIFT;
let taken = free.min(n);
match state.compare_exchange_weak(
current,
current - (taken << SHIFT),
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return taken,
Err(actual) => current = actual,
}
}
}
pub(super) fn close(&self) {
self.transition(close_step);
}
}
impl Lock for RawSemaphore {
fn admit(&self, state: usize, need: usize) -> Option<usize> {
(state >> SHIFT >= need && state & CLOSED == 0).then(|| state - (need << SHIFT))
}
fn give_back(&self, need: usize) {
self.release(need);
}
fn closed(&self, state: usize) -> bool {
observed_closed(state)
}
fn try_take(&self, need: usize) -> Result<(), usize> {
if need == 0 {
let seen = self.queue.state.load(Ordering::Relaxed);
return if closed_in(seen) { Err(seen) } else { Ok(()) };
}
contend::take(self, need, false)
}
}
impl Policy for RawSemaphore {
fn queue(&self) -> &WaitQueue {
&self.queue
}
fn pass(&self, arrivals: Arrivals, woken: &mut WakeList<'_>, wake_front: bool) -> usize {
let queue = &self.queue;
self.waiting.with_mut(|waiting| unsafe {
let waiting = &mut *waiting;
waiting.absorb(arrivals, queue);
waiting.tidy(queue);
if self.is_closed() {
while let Some(node) = waiting.front(queue) {
waiting.pop_front();
node.as_ref().set_payload(CLOSED_GRANT);
woken.grant(node);
}
return QUEUED;
}
if queue.state.load(Ordering::Acquire) & HANDOFF != 0 {
while let Some(node) = waiting.front(queue) {
let need = node.as_ref().payload();
if !self.take_for_waiter(need) {
break;
}
waiting.pop_front();
if !woken.grant(node) {
queue.state.fetch_add(need << SHIFT, Ordering::AcqRel);
}
}
}
if !queue.hands_off() && waiting.front(queue).is_some() {
let mut budget = queue.state.load(Ordering::Acquire) >> SHIFT;
let mut nudges = Nudges::new(woken, queue);
for (at, node) in waiting.iter().enumerate() {
let need = node.as_ref().payload();
if need > budget && !(at == 0 && wake_front) {
break;
}
nudges.nudge(node);
budget = budget.saturating_sub(need);
}
nudges.finish();
}
if waiting.front(queue).is_none() {
QUEUED
} else {
0
}
})
}
}
impl Drop for RawSemaphore {
fn drop(&mut self) {
let queue = &self.queue;
self.waiting
.with_mut(|waiting| unsafe { (*waiting).clear(queue) });
}
}
#[cfg(all(test, not(loom)))]
impl RawSemaphore {
pub(super) fn linked(&self) -> usize {
self.waiting.with(|waiting| unsafe { (*waiting).len() })
}
}
pub(super) struct PermitWait {
contend: Contend,
}
impl PermitWait {
pub(super) const fn new() -> Self {
Self {
contend: Contend::new(),
}
}
pub(super) fn is_queued(&self) -> bool {
self.contend.is_queued()
}
pub(super) fn poll(&mut self, sem: &RawSemaphore, n: usize, waker: &Waker) -> Poll<()> {
self.contend.poll(sem, n, waker)
}
pub(super) fn poll_closable(
&mut self,
sem: &RawSemaphore,
n: usize,
waker: &Waker,
) -> Poll<Result<(), Closed>> {
self.contend.poll_closable(sem, n, waker)
}
#[inline]
pub(super) fn poll_one(&mut self, sem: &RawSemaphore, waker: &Waker) -> Poll<()> {
let seen = if self.contend.is_queued() {
None
} else {
match sem.take_one() {
Ok(()) => {
return taken_at_once(sem, 1, waker)
.map(|taken| debug_assert!(taken.is_ok(), "a taken permit is no close"));
}
Err(seen) => Some(seen),
}
};
self.poll_contended(sem, waker, seen)
}
#[inline(never)]
fn poll_contended(
&mut self,
sem: &RawSemaphore,
waker: &Waker,
seen: Option<usize>,
) -> Poll<()> {
self.contend.poll_from(sem, 1, waker, seen)
}
#[inline]
pub(super) fn cancel(&mut self, sem: &RawSemaphore, n: usize) {
if self.contend.is_queued() {
self.cancel_queued(sem, n);
}
}
#[inline(never)]
fn cancel_queued(&mut self, sem: &RawSemaphore, n: usize) {
self.contend.cancel(sem, n);
}
}
pub(super) struct RawPermit<'a> {
sem: &'a RawSemaphore,
n: usize,
}
impl RawPermit<'_> {
pub(super) fn forget(mut self) {
self.n = 0;
}
}
impl Drop for RawPermit<'_> {
fn drop(&mut self) {
self.sem.release(self.n);
}
}
pub(super) struct RawAcquire<'a> {
sem: &'a RawSemaphore,
n: usize,
wait: PermitWait,
}
impl RawSemaphore {
pub(super) fn acquire(&self, n: usize) -> RawAcquire<'_> {
RawAcquire {
sem: self,
n,
wait: PermitWait::new(),
}
}
}
impl<'a> Future for RawAcquire<'a> {
type Output = Result<RawPermit<'a>, Closed>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
this.wait
.poll_closable(this.sem, this.n, cx.waker())
.map(|taken| {
taken.map(|()| RawPermit {
sem: this.sem,
n: this.n,
})
})
}
}
impl Drop for RawAcquire<'_> {
fn drop(&mut self) {
self.wait.cancel(self.sem, self.n);
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
fn every_state() -> impl Iterator<Item = usize> {
(0..=3).flat_map(|permits| (0..=FLAGS).map(move |flags| (permits << SHIFT) | flags))
}
#[test]
fn the_inline_attempt_takes_exactly_what_try_take_takes() {
for state in every_state() {
let inline = RawSemaphore::new(0);
inline.queue.state.store(state, Ordering::Relaxed);
let full = RawSemaphore::new(0);
full.queue.state.store(state, Ordering::Relaxed);
let took = inline.take_one();
assert_eq!(
took.is_ok(),
full.try_take(1).is_ok(),
"state {state:#x}: the inline attempt and try_take disagree"
);
assert_eq!(
inline.queue.state.load(Ordering::Relaxed),
full.queue.state.load(Ordering::Relaxed),
"state {state:#x}: the two leave different words"
);
if let Err(seen) = took {
assert_eq!(seen, state, "a failed attempt reports the state it saw");
}
assert_eq!(
took.is_ok(),
state >> SHIFT >= 1 && state & (HANDOFF | CLOSED) == 0,
"state {state:#x}: a free permit is taken unless a handoff is owed or it is closed"
);
}
}
}