#![no_std]
#![warn(
unsafe_op_in_unsafe_fn,
clippy::missing_safety_doc,
clippy::multiple_unsafe_ops_per_block,
clippy::undocumented_unsafe_blocks
)]
#[cfg(test)]
extern crate std;
use core::{hint::unreachable_unchecked, task::Waker};
use closeable::{Closeable, IsCloseable};
use flag_bearer_core::SemaphoreState;
use pin_list::PinList;
pub mod acquire;
pub mod closeable;
mod loom;
pub struct SemaphoreQueue<
S: SemaphoreState<Params = Params, Permit = Permit> + ?Sized,
C: IsCloseable,
Params = <S as SemaphoreState>::Params,
Permit = <S as SemaphoreState>::Permit,
> {
#[allow(clippy::type_complexity)]
queue: Result<PinList<PinQueue<Params, Permit, C>>, C::Closed<()>>,
poisoned: bool,
state: S,
}
pub(crate) struct PoisonOnUnwind<'a>(pub(crate) &'a mut bool);
impl Drop for PoisonOnUnwind<'_> {
fn drop(&mut self) {
*self.0 = true;
}
}
impl<S: SemaphoreState + core::fmt::Debug, C: IsCloseable> core::fmt::Debug
for SemaphoreQueue<S, C>
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut d = f.debug_struct("SemaphoreQueue");
d.field("state", &self.state);
d.finish_non_exhaustive()
}
}
type PinQueue<Params, Permit, C> = dyn pin_list::Types<
Id = pin_list::id::DebugChecked,
Protected = (
// Some(params) -> Pending
// None -> the leader's params have been taken: transiently while
// check() acquires, or left behind if that acquire panicked
// (in which case the queue is also poisoned).
Option<Params>,
Waker,
),
Removed = Result<
Permit,
<C as closeable::private::Sealed>::Closed<Option<Params>>,
>,
Unprotected = (),
>;
impl<S: SemaphoreState, C: IsCloseable> SemaphoreQueue<S, C> {
pub fn new(state: S) -> Self {
Self {
state,
poisoned: false,
queue: Ok(PinList::new(unsafe { pin_list::id::DebugChecked::new() })),
}
}
}
impl<S: SemaphoreState + ?Sized, C: IsCloseable> SemaphoreQueue<S, C> {
pub fn with_state<T>(&mut self, f: impl FnOnce(&mut S) -> T) -> T {
let guard = PoisonOnUnwind(&mut self.poisoned);
let res = f(&mut self.state);
core::mem::forget(guard);
self.check();
res
}
#[inline]
fn check(&mut self) {
if self.poisoned {
return;
}
let Ok(queue) = &mut self.queue else { return };
let mut leader = queue.cursor_front_mut();
while let Some(p) = leader.protected_mut() {
let Some(params) = p.0.take() else {
leader.move_next();
continue;
};
let guard = PoisonOnUnwind(&mut self.poisoned);
let result = self.state.acquire(params);
core::mem::forget(guard);
match result {
Ok(permit) => match leader.remove_current(Ok(permit)) {
Ok((_, waker)) => waker.wake(),
Err(_) => unsafe { unreachable_unchecked() },
},
Err(params) => {
p.0 = Some(params);
break;
}
}
}
}
pub fn is_closed(&self) -> bool {
self.queue.is_err()
}
pub fn is_poisoned(&self) -> bool {
self.poisoned
}
pub fn clear_poison(&mut self) {
self.poisoned = false;
}
}
impl<S: SemaphoreState + ?Sized> SemaphoreQueue<S, Closeable> {
pub fn close(&mut self) {
let Ok(queue) = &mut self.queue else {
return;
};
let mut cursor = queue.cursor_front_mut();
while cursor.remove_current_with_or(
|(params, waker)| {
waker.wake();
Err(params)
},
|| Err(None),
) {}
debug_assert!(queue.is_empty());
self.queue = Err(());
}
}
#[cfg(all(test, loom))]
mod loom_tests {
use crate::{SemaphoreQueue, closeable::Closeable};
#[derive(Debug)]
struct NeverSucceeds;
impl crate::SemaphoreState for NeverSucceeds {
type Params = ();
type Permit = ();
fn acquire(&mut self, _params: Self::Params) -> Result<Self::Permit, Self::Params> {
Err(())
}
fn release(&mut self, _permit: Self::Permit) {}
}
#[test]
fn concurrent_closed() {
loom::model(|| {
use std::sync::Arc;
let s = Arc::new(crate::loom::Mutex::<parking_lot::RawMutex, _>::new(
SemaphoreQueue::<NeverSucceeds, Closeable>::new(NeverSucceeds),
));
let s2 = s.clone();
let handle = loom::thread::spawn(move || {
loom::future::block_on(async move {
SemaphoreQueue::acquire(&s2, (), crate::acquire::FairOrder::Fifo)
.await
.unwrap_err()
})
});
s.lock().close();
handle.join().unwrap();
});
}
}