flag_bearer_queue/acquire.rs
1use core::{
2 fmt,
3 pin::Pin,
4 task::{Context, Poll},
5};
6
7use lock_api::RawMutex;
8use pin_list::{Node, NodeData};
9
10use crate::closeable::{IsCloseable, Uncloseable};
11use crate::{SemaphoreQueue, SemaphoreState};
12
13use super::PinQueue;
14
15use crate::loom::Mutex;
16
17pin_project_lite::pin_project! {
18 /// A [`Future`] that acquires a permit from a [`SemaphoreQueue`].
19 pub struct Acquire<'a, S, C, R>
20 where
21 S: ?Sized,
22 S: SemaphoreState,
23 C: IsCloseable,
24 R: RawMutex,
25 {
26 #[pin]
27 node: Node<PinQueue<S::Params, S::Permit, C>>,
28 order: FairOrder,
29 state: &'a Mutex<R, SemaphoreQueue<S, C>>,
30 params: Option<S::Params>,
31 }
32
33 impl<S, C, R> PinnedDrop for Acquire<'_, S, C, R>
34 where
35 S: ?Sized,
36 S: SemaphoreState,
37 C: IsCloseable,
38 R: RawMutex,
39 {
40 fn drop(this: Pin<&mut Self>) {
41 let this = this.project();
42 let Some(node) = this.node.initialized_mut() else {
43 return;
44 };
45 let mut state = this.state.lock();
46 match &mut state.queue {
47 Ok(queue) => {
48 match node.reset(queue).0 {
49 // We were granted a permit but never claimed it; return it.
50 NodeData::Removed(Ok(permit)) => {
51 state.state.release(permit);
52 state.check();
53 }
54 // We were still queued and have now left the queue. If we were
55 // a head-of-line blocker, the waiters behind us may now be
56 // serviceable, so re-check.
57 NodeData::Linked(_) => state.check(),
58 NodeData::Removed(Err(_closed)) => {}
59 }
60 }
61 Err(_closed) => {
62 // Safety: If the semaphore is closed (meaning we have no queue)
63 // then there's no way this node could be queued in the queue,
64 // therefore it must be removed.
65 let (permit, ()) = unsafe { node.take_removed_unchecked() };
66 if let Ok(permit) = permit {
67 state.state.release(permit);
68 }
69 }
70 }
71 }
72 }
73}
74
75impl<S: SemaphoreState + ?Sized, C: IsCloseable, R: RawMutex> Future for Acquire<'_, S, C, R> {
76 type Output = Result<S::Permit, C::AcquireError<S::Params>>;
77
78 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
79 let mut this = self.project();
80 let mut state = this.state.lock();
81
82 let Some(init) = this.node.as_mut().initialized_mut() else {
83 // first time polling.
84 let params = this.params.take().unwrap();
85 let node = this.node.as_mut();
86
87 match state.try_acquire(params, Fairness::Fair(*this.order)) {
88 Ok(permit) => return Poll::Ready(Ok(permit)),
89 Err(TryAcquireError::Closed(params)) => return Poll::Ready(Err(params)),
90 // The async acquire's error type can't carry poison (it is
91 // uninhabited for uncloseable semaphores, keeping `must_acquire`
92 // infallible), so poison is a panic on this path. Callers that
93 // want to observe poison should use `try_acquire`.
94 Err(TryAcquireError::Poisoned(_params)) => {
95 panic!(
96 "the semaphore is poisoned: a previous SemaphoreState::acquire call panicked"
97 )
98 }
99 Err(TryAcquireError::NoPermits(params)) => {
100 let queue = match &mut state.queue {
101 Ok(queue) => queue,
102 // Safety: if the queue was closed, we would get a `Closed` error type.
103 // It was not closed, thus it still isn't closed.
104 Err(_closed) => unsafe { core::hint::unreachable_unchecked() },
105 };
106
107 // no permit or we are not the leader, so we register into the queue.
108 let waker = cx.waker().clone();
109 match *this.order {
110 FairOrder::Lifo => queue.push_front(node, (Some(params), waker), ()),
111 FairOrder::Fifo => queue.push_back(node, (Some(params), waker), ()),
112 };
113 return Poll::Pending;
114 }
115 }
116 };
117
118 if let Ok(queue) = &mut state.queue
119 && let Some((_, waker)) = init.protected_mut(queue)
120 {
121 // spurious wakeup
122 waker.clone_from(cx.waker());
123 return Poll::Pending;
124 }
125
126 // Safety: Either there is no queue, then we are guaranteed to be removed from it
127 // Or there was a queue, but we were removed from it anyway (protected_mut returned None).
128 let (permit, ()) = unsafe { init.take_removed_unchecked() };
129 let permit = permit.map_err(|params| {
130 C::map_err(params, |params| {
131 params.expect(
132 "params should be set. likely the SemaphoreState::acquire method panicked",
133 )
134 })
135 });
136 Poll::Ready(permit)
137 }
138}
139
140#[derive(Debug, Clone, Copy)]
141#[non_exhaustive]
142/// The order of which [`Acquire`] should enter the queue.
143pub enum FairOrder {
144 /// Last in, first out.
145 /// Increases tail latencies, but can have better average performance.
146 Lifo,
147 /// First in, first out.
148 /// Fairer option, but can have cascading failures if queue processing is slow.
149 Fifo,
150}
151
152#[derive(Debug, Clone, Copy)]
153#[non_exhaustive]
154/// Which fairness property [`SemaphoreQueue::try_acquire`] should respect
155pub enum Fairness {
156 Fair(FairOrder),
157 Unfair,
158}
159
160impl<S: SemaphoreState + ?Sized, C: IsCloseable> SemaphoreQueue<S, C> {
161 /// Acquire a permit, or join the queue if not currently available.
162 ///
163 /// * If the order is [`FairOrder::Lifo`], then we enqueue at the front of the queue.
164 /// * If the order is [`FairOrder::Fifo`], then we enqueue at the back of the queue.
165 #[inline]
166 pub fn acquire<R: RawMutex>(
167 this: &Mutex<R, Self>,
168 params: S::Params,
169 order: FairOrder,
170 ) -> Acquire<'_, S, C, R> {
171 Acquire {
172 node: Node::new(),
173 order,
174 state: this,
175 params: Some(params),
176 }
177 }
178
179 /// Try acquire a permit without joining the queue.
180 ///
181 /// * If the fairness is [`Fairness::Unfair`], or [`Fairness::Fair(FairOrder::Lifo)`](FairOrder::Lifo), then we always try acquire a permit.
182 /// * If the fairness is [`Fairness::Fair(FairOrder::Fifo)`](FairOrder::Fifo), then we only try acquire a permit if the queue is empty.
183 #[inline]
184 pub fn try_acquire(
185 &mut self,
186 params: S::Params,
187 fairness: Fairness,
188 ) -> Result<S::Permit, TryAcquireError<S::Params, C>> {
189 if self.is_poisoned() {
190 return Err(TryAcquireError::Poisoned(params));
191 }
192
193 let queue = match &mut self.queue {
194 Ok(queue) => queue,
195 Err(_closed) => {
196 return Err(TryAcquireError::Closed(C::new_err(params)));
197 }
198 };
199
200 let is_leader = match fairness {
201 // if first-in-first-out, we are only the leader if the queue is empty.
202 Fairness::Fair(FairOrder::Fifo) => queue.is_empty(),
203 // if last-in-first-out, we are the last in and thus the leader.
204 Fairness::Fair(FairOrder::Lifo) => true,
205 // if unfair, then we don't care who the leader is.
206 Fairness::Unfair => true,
207 };
208
209 if !is_leader {
210 return Err(TryAcquireError::NoPermits(params));
211 }
212
213 // A panic in `acquire` may leave `state` half-updated (no node is
214 // involved here, so this is the only record), so poison if it unwinds.
215 let guard = crate::PoisonOnUnwind(&mut self.poisoned);
216 let result = self.state.acquire(params);
217 core::mem::forget(guard);
218
219 match result {
220 Ok(permit) => Ok(permit),
221 Err(p) => Err(TryAcquireError::NoPermits(p)),
222 }
223 }
224}
225
226#[derive(Debug, PartialEq, Eq)]
227pub enum TryAcquireError<P, C: IsCloseable> {
228 /// The semaphore had no permits to give out right now.
229 NoPermits(P),
230 /// The semaphore is closed.
231 Closed(C::AcquireError<P>),
232 /// The semaphore is poisoned: a previous [`SemaphoreState::acquire`](crate::SemaphoreState::acquire)
233 /// call panicked. The params are handed back unused.
234 Poisoned(P),
235}
236
237impl<P, C: IsCloseable> fmt::Display for TryAcquireError<P, C> {
238 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
239 match self {
240 TryAcquireError::Closed(_) => write!(fmt, "semaphore closed"),
241 TryAcquireError::NoPermits(_) => write!(fmt, "no permits available"),
242 TryAcquireError::Poisoned(_) => write!(fmt, "semaphore poisoned"),
243 }
244 }
245}
246
247/// The error returned by [`Acquire`] if the semaphore queue was closed.
248///
249/// ```
250/// struct Counter(usize);
251///
252/// impl flag_bearer::SemaphoreState for Counter {
253/// type Params = ();
254/// type Permit = ();
255///
256/// fn acquire(&mut self, _: Self::Params) -> Result<Self::Permit, Self::Params> {
257/// if self.0 > 0 {
258/// self.0 -= 1;
259/// Ok(())
260/// } else {
261/// Err(())
262/// }
263/// }
264///
265/// fn release(&mut self, _: Self::Permit) {
266/// self.0 += 1;
267/// }
268/// }
269///
270/// # pollster::block_on(async move {
271/// let s = flag_bearer::Builder::fifo().closeable().with_state(Counter(1));
272///
273/// // closing the semaphore makes all current and new acquire() calls return an error.
274/// s.close();
275///
276/// let _err = s.acquire(()).await.unwrap_err();
277/// # });
278/// ```
279#[non_exhaustive]
280#[derive(Debug, PartialEq, Eq)]
281pub struct AcquireError<P> {
282 /// The params that was used in the acquire request
283 pub params: P,
284}
285
286impl AcquireError<Uncloseable> {
287 /// Since the [`SemaphoreQueue`] is [`Uncloseable`], there can
288 /// never be an acquire error. This allows for unwrapping with type-safety.
289 pub fn never(self) -> ! {
290 match self.params {}
291 }
292}
293
294impl<P> fmt::Display for AcquireError<P> {
295 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
296 write!(fmt, "semaphore closed")
297 }
298}