Skip to main content

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}