#![allow(unsafe_code)]
use core::task::{Poll, Waker};
use super::contend::{CLOSED_GRANT, Closed};
use super::internal::{Ordering, UnsafeCell};
use super::list::List;
use super::queue::{Arrivals, Policy, QUEUED, WaitQueue, wants_drain};
use super::raw_semaphore::{MAX, SHIFT, close_step, closed_in, observed_closed};
use super::waiter::{Cancel, Wait, WakeList};
struct Waiting {
list: List,
set_aside: usize,
}
pub(super) struct FairSemaphore {
queue: WaitQueue,
waiting: UnsafeCell<Waiting>,
}
unsafe impl Send for FairSemaphore {}
unsafe impl Sync for FairSemaphore {}
impl FairSemaphore {
pub(super) fn new(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(Waiting {
list: List::new(),
set_aside: 0,
}),
}
}
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))
}
#[inline]
pub(super) fn try_acquire(&self, n: usize) -> Result<(), usize> {
let state = &self.queue.state;
let mut current = state.load(Ordering::Relaxed);
loop {
if closed_in(current) {
return Err(current);
}
if n == 0 {
return Ok(());
}
if current & QUEUED != 0 || current >> SHIFT < n {
return Err(current);
}
match state.compare_exchange_weak(
current,
current - (n << SHIFT),
Ordering::Acquire,
Ordering::Relaxed,
) {
Ok(_) => return Ok(()),
Err(actual) => current = actual,
}
}
}
#[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();
}
pub(super) fn close(&self) {
self.transition(close_step);
}
fn take_up_to(&self, want: usize) -> usize {
let state = &self.queue.state;
let mut current = state.load(Ordering::Acquire);
loop {
let take = (current >> SHIFT).min(want);
if take == 0 || closed_in(current) {
return 0;
}
match state.compare_exchange_weak(
current,
current - (take << SHIFT),
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return take,
Err(actual) => current = actual,
}
}
}
fn return_set_aside(&self, set_aside: &mut usize) {
if *set_aside != 0 {
let back = probe::handed_on(probe::Site::ReturnedToWord, *set_aside);
self.queue.state.fetch_add(back << SHIFT, Ordering::AcqRel);
*set_aside = 0;
}
}
}
impl Policy for FairSemaphore {
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 { list, set_aside } = &mut *waiting;
list.absorb(arrivals, queue);
list.tidy(queue);
if self.is_closed() {
while let Some(node) = list.front(queue) {
list.pop_front();
node.as_ref().set_payload(CLOSED_GRANT);
woken.grant(node);
}
} else {
while let Some(node) = list.front(queue) {
let need = node.as_ref().payload();
if *set_aside < need {
*set_aside += self.take_up_to(need - *set_aside);
if *set_aside < need {
return 0;
}
}
list.pop_front();
if woken.grant(node) {
*set_aside -= need;
} else {
*set_aside -= need - probe::handed_on(probe::Site::WithdrewAtGrant, need);
}
}
}
self.return_set_aside(set_aside);
QUEUED
})
}
}
impl Drop for FairSemaphore {
fn drop(&mut self) {
let queue = &self.queue;
self.waiting
.with_mut(|waiting| unsafe { (*waiting).list.clear(queue) });
}
}
pub(super) struct FairWait {
wait: Wait,
}
impl FairWait {
pub(super) const fn new() -> Self {
Self { wait: Wait::new() }
}
pub(super) fn poll(
&mut self,
sem: &FairSemaphore,
n: usize,
waker: &Waker,
give_back: impl FnOnce(usize),
) -> Poll<Result<(), Closed>> {
self.poll_admitted(sem, n, waker, crate::coop::try_spend, give_back)
}
pub(super) fn poll_admitted(
&mut self,
sem: &FairSemaphore,
n: usize,
waker: &Waker,
admit: impl FnOnce() -> bool,
give_back: impl FnOnce(usize),
) -> Poll<Result<(), Closed>> {
let granted = if self.wait.is_queued() {
self.wait.poll(&sem.queue, Some(waker))
} else {
match sem.try_acquire(n) {
Ok(()) => {
if admit() {
return Poll::Ready(Ok(()));
}
give_back(n);
crate::coop::refused(waker);
return Poll::Pending;
}
Err(seen) if observed_closed(seen) => {
return if admit() {
Poll::Ready(Err(Closed))
} else {
crate::coop::refused(waker);
Poll::Pending
};
}
Err(_) => {}
}
sem.enqueue(&mut self.wait, n, waker);
match self.wait.poll(&sem.queue, None) {
Poll::Ready(payload) if !admit() => {
if payload != CLOSED_GRANT {
give_back(n);
}
crate::coop::refused(waker);
return Poll::Pending;
}
granted => granted,
}
};
granted.map(|payload| {
if payload == CLOSED_GRANT {
Err(Closed)
} else {
Ok(())
}
})
}
pub(super) fn cancel(&mut self, sem: &FairSemaphore, n: usize) {
match self.wait.cancel(&sem.queue) {
Cancel::Idle => {}
Cancel::Withdrawn(_, _shelved) => sem.withdrawn(false),
Cancel::Granted(payload) if payload == CLOSED_GRANT => {}
Cancel::Granted(_) => {
sem.release(probe::handed_on(probe::Site::GrantedThenDropped, n));
}
}
}
}
#[cfg(not(all(loom, test)))]
mod probe {
pub(super) enum Site {
GrantedThenDropped,
WithdrewAtGrant,
ReturnedToWord,
}
#[inline(always)]
pub(super) fn handed_on(_site: Site, share: usize) -> usize {
share
}
}
#[cfg(all(loom, test))]
pub(super) mod probe {
use core::cell::Cell;
pub(super) enum Site {
GrantedThenDropped,
WithdrewAtGrant,
ReturnedToWord,
}
std::thread_local! {
static COUNTS: Cell<[usize; 3]> = const { Cell::new([0; 3]) };
static DROP_PASS_ON: Cell<bool> = const { Cell::new(false) };
}
pub(super) fn handed_on(site: Site, share: usize) -> usize {
COUNTS.with(|counts| {
let mut seen = counts.get();
seen[site as usize] += 1;
counts.set(seen);
});
if DROP_PASS_ON.with(Cell::get) {
0
} else {
share
}
}
pub(in crate::sync) fn take() -> [usize; 3] {
COUNTS.with(|counts| counts.replace([0; 3]))
}
pub(in crate::sync) fn drop_pass_on(on: bool) {
DROP_PASS_ON.with(|c| c.set(on));
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use core::future::Future;
use core::pin::Pin;
use core::task::Context;
use super::*;
use crate::sync::test_support::Polled;
struct Acquire<'a> {
sem: &'a FairSemaphore,
n: usize,
wait: FairWait,
}
impl Future for Acquire<'_> {
type Output = Result<(), Closed>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
let sem = this.sem;
this.wait.poll(sem, this.n, cx.waker(), |n| sem.release(n))
}
}
impl Drop for Acquire<'_> {
fn drop(&mut self) {
self.wait.cancel(self.sem, self.n);
}
}
impl FairSemaphore {
fn acquire(&self, n: usize) -> Acquire<'_> {
Acquire {
sem: self,
n,
wait: FairWait::new(),
}
}
}
fn set_aside(sem: &FairSemaphore) -> usize {
sem.waiting.with(|waiting| unsafe { (*waiting).set_aside })
}
#[test]
fn an_attempt_takes_what_is_free_while_nobody_waits() {
let sem = FairSemaphore::new(3);
assert!(sem.try_acquire(2).is_ok());
assert_eq!(sem.available(), 1);
assert!(sem.try_acquire(2).is_err());
assert!(
sem.try_acquire(0).is_ok(),
"nothing asked, nothing to wait for"
);
sem.release(2);
assert_eq!(sem.available(), 3);
}
#[test]
fn waiters_are_granted_in_the_order_they_queued() {
let sem = FairSemaphore::new(1);
assert!(sem.try_acquire(1).is_ok());
let mut first = Polled::new(sem.acquire(1));
let mut second = Polled::new(sem.acquire(1));
first.pending();
second.pending();
sem.release(1);
assert_eq!((first.wakes(), second.wakes()), (1, 0));
assert_eq!(first.ready(), Ok(()));
second.pending();
sem.release(1);
assert_eq!(second.wakes(), 1);
assert_eq!(second.ready(), Ok(()));
sem.release(1);
assert_eq!(sem.available(), 1);
}
#[test]
fn a_later_small_request_does_not_pass_an_earlier_large_one() {
let sem = FairSemaphore::new(3);
assert!(sem.try_acquire(3).is_ok());
let mut large = Polled::new(sem.acquire(3));
large.pending();
let mut small = Polled::new(sem.acquire(1));
small.pending();
sem.release(1);
assert_eq!(
sem.available(),
0,
"the free permit is set aside for the front"
);
assert_eq!(set_aside(&sem), 1);
assert!(
sem.try_acquire(1).is_err(),
"an attempt does not pass a waiter"
);
assert_eq!(small.wakes(), 0, "the small request waits behind the large");
sem.release(2);
assert_eq!(large.wakes(), 1);
assert_eq!(large.ready(), Ok(()));
assert_eq!(small.wakes(), 0);
sem.release(3);
assert_eq!(small.ready(), Ok(()));
assert_eq!(sem.available(), 2);
sem.release(1);
assert_eq!(sem.available(), 3);
}
#[test]
fn a_front_waiter_that_leaves_passes_its_share_to_the_next() {
let sem = FairSemaphore::new(3);
assert!(sem.try_acquire(3).is_ok());
let mut large = Polled::new(sem.acquire(3));
large.pending();
let mut next = Polled::new(sem.acquire(2));
next.pending();
sem.release(2);
assert_eq!(set_aside(&sem), 2);
drop(large);
assert_eq!(next.wakes(), 1, "the share went on to the next waiter");
assert_eq!(next.ready(), Ok(()));
assert_eq!(set_aside(&sem), 0);
assert_eq!(sem.available(), 0);
sem.release(3);
assert_eq!(sem.available(), 3);
}
#[test]
fn a_waiter_granted_then_dropped_gives_its_permits_on() {
let sem = FairSemaphore::new(1);
assert!(sem.try_acquire(1).is_ok());
let mut first = Polled::new(sem.acquire(1));
let mut second = Polled::new(sem.acquire(1));
first.pending();
second.pending();
sem.release(1);
assert_eq!(first.wakes(), 1);
drop(first);
assert_eq!(second.wakes(), 1);
assert_eq!(second.ready(), Ok(()));
sem.release(1);
assert_eq!(sem.available(), 1);
}
#[test]
fn the_last_waiter_leaving_returns_the_set_aside() {
let sem = FairSemaphore::new(4);
assert!(sem.try_acquire(4).is_ok());
let mut waiting = Polled::new(sem.acquire(4));
waiting.pending();
sem.release(3);
assert_eq!(sem.available(), 0);
drop(waiting);
assert_eq!(sem.available(), 3);
assert_eq!(set_aside(&sem), 0);
assert!(sem.try_acquire(3).is_ok(), "nobody waits any more");
sem.release(4);
}
#[test]
fn closing_ends_every_wait_and_returns_the_set_aside() {
let sem = FairSemaphore::new(2);
assert!(sem.try_acquire(2).is_ok());
let mut large = Polled::new(sem.acquire(2));
let mut small = Polled::new(sem.acquire(1));
large.pending();
small.pending();
sem.release(1);
assert_eq!(sem.available(), 0);
sem.close();
assert_eq!(large.ready(), Err(Closed));
assert_eq!(small.ready(), Err(Closed));
assert_eq!(sem.available(), 1, "the set-aside came back");
assert!(observed_closed(sem.try_acquire(1).unwrap_err()));
assert!(matches!(Polled::new(sem.acquire(1)).ready(), Err(Closed)));
sem.release(1);
assert_eq!(sem.available(), 2);
}
#[test]
fn many_cancelled_waits_leave_the_count_whole() {
let sem = FairSemaphore::new(4);
assert!(sem.try_acquire(4).is_ok());
for round in 0..200 {
let mut waits: Vec<_> = (0..5)
.map(|i| Polled::new(sem.acquire(1 + (i + round) % 4)))
.collect();
for wait in &mut waits {
wait.pending();
}
sem.release(1);
match round % 3 {
0 => drop(waits),
1 => {
let back = waits.split_off(2);
drop(back);
drop(waits);
}
_ => {
waits.reverse();
drop(waits);
}
}
assert_eq!(sem.available(), 1, "round {round}");
assert_eq!(set_aside(&sem), 0, "round {round}");
assert!(sem.try_acquire(1).is_ok(), "round {round}: nobody waits");
}
assert_eq!(sem.linked(), 0, "every cancelled node was let go");
sem.release(4);
assert_eq!(sem.available(), 4);
}
}
#[cfg(all(test, not(loom)))]
impl FairSemaphore {
fn linked(&self) -> usize {
self.waiting
.with(|waiting| unsafe { (*waiting).list.len() })
}
}