Skip to main content

priority_semaphore/
semaphore.rs

1//! Core implementation of [`PrioritySemaphore`].
2
3use crate::{
4    error::{TryAcquireError, TryAcquireError::*},
5    lock::Lock,
6    permit::Permit,
7    queue::{WaitKey, WaitQueue},
8    waiter::{AcquireFuture, Waiter},
9};
10use alloc::sync::Arc;
11use core::{
12    sync::atomic::{AtomicUsize, Ordering},
13    task::Waker,
14};
15
16/// Priority value used by the semaphore.
17///
18/// Larger numbers represent higher priority. Waiters with an equal priority
19/// are served in first-in, first-out order.
20pub type Priority = i32;
21
22// Available permits and coordination flags share one atomic word. This closes
23// the check-then-enqueue race without putting the uncontended path behind a
24// mutex.
25const CLOSED: usize = 1 << (usize::BITS - 1);
26const HAS_WAITERS: usize = 1 << (usize::BITS - 2);
27const PERMIT_MASK: usize = HAS_WAITERS - 1;
28
29pub(crate) enum RegisterResult {
30    Acquired,
31    Queued { key: WaitKey, waiter: Arc<Waiter> },
32    Closed,
33}
34
35/// A runtime-independent, priority-aware asynchronous semaphore.
36///
37/// Acquiring an immediately available permit is lock-free. Under contention,
38/// returned permits are reserved directly for the highest-priority waiter, so
39/// a newly arriving task cannot steal a wake-up.
40pub struct PrioritySemaphore {
41    state: AtomicUsize,
42    pub(crate) waiters: Lock<WaitQueue>,
43    max_permits: usize,
44}
45
46impl core::fmt::Debug for PrioritySemaphore {
47    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48        f.debug_struct("PrioritySemaphore")
49            .field("available", &self.available_permits())
50            .field("queued", &self.queued())
51            .field("max_permits", &self.max_permits)
52            .field("closed", &self.is_closed())
53            .finish()
54    }
55}
56
57impl PrioritySemaphore {
58    /// Creates a semaphore with `permits` concurrent permits.
59    ///
60    /// # Panics
61    ///
62    /// Panics when `permits` is larger than [`PrioritySemaphore::MAX_PERMITS`].
63    pub const fn new(permits: usize) -> Self {
64        assert!(permits <= Self::MAX_PERMITS, "too many semaphore permits");
65        Self {
66            state: AtomicUsize::new(permits),
67            waiters: Lock::new(WaitQueue::new()),
68            max_permits: permits,
69        }
70    }
71
72    /// Largest supported initial permit count.
73    pub const MAX_PERMITS: usize = PERMIT_MASK;
74
75    /// Acquires one permit at `priority`.
76    ///
77    /// The returned future is cancellation-safe. If it is cancelled after a
78    /// permit has already been assigned, that permit is immediately passed to
79    /// the next waiter or returned to the semaphore.
80    pub fn acquire(self: &Arc<Self>, priority: Priority) -> AcquireFuture {
81        AcquireFuture::new(self.clone(), priority)
82    }
83
84    /// Attempts to acquire one immediately available permit.
85    ///
86    /// This method never bypasses already queued waiters. `priority` is
87    /// accepted for API symmetry, but only affects queued acquisitions.
88    pub fn try_acquire(self: &Arc<Self>, _priority: Priority) -> Result<Permit, TryAcquireError> {
89        self.try_take()?;
90        Ok(Permit::new(self.clone()))
91    }
92
93    /// Closes the semaphore and wakes every queued waiter.
94    ///
95    /// Closing is idempotent. Permits acquired before the close remain valid,
96    /// while all subsequent acquisition attempts fail.
97    pub fn close(&self) {
98        let entries = {
99            // The lock makes close and direct handoff linearisable with each
100            // other. Wakers are deliberately invoked after it is released.
101            let mut queue = self.waiters.lock();
102            let previous = self.state.fetch_or(CLOSED, Ordering::AcqRel);
103            if previous & CLOSED != 0 {
104                return;
105            }
106            let entries = queue.drain();
107            for entry in &entries {
108                entry.waiter.close();
109            }
110            self.state.fetch_and(!HAS_WAITERS, Ordering::Release);
111            entries
112        };
113
114        for entry in entries {
115            entry.waker.wake();
116        }
117    }
118
119    /// Returns the number of permits that can be acquired immediately.
120    pub fn available_permits(&self) -> usize {
121        self.state.load(Ordering::Acquire) & PERMIT_MASK
122    }
123
124    /// Returns the number of futures currently waiting in the priority queue.
125    pub fn queued(&self) -> usize {
126        self.waiters.lock().len()
127    }
128
129    /// Returns `true` after [`PrioritySemaphore::close`] has been called.
130    pub fn is_closed(&self) -> bool {
131        self.state.load(Ordering::Acquire) & CLOSED != 0
132    }
133
134    pub(crate) fn register(&self, priority: Priority, waker: &Waker) -> RegisterResult {
135        let mut queue = self.waiters.lock();
136        let previous = self.state.fetch_or(HAS_WAITERS, Ordering::AcqRel);
137        if previous & CLOSED != 0 {
138            if queue.is_empty() {
139                self.state.fetch_and(!HAS_WAITERS, Ordering::Release);
140            }
141            return RegisterResult::Closed;
142        }
143
144        // Only the first waiter can consume a permit that raced with queue
145        // registration. Existing queued waiters must retain strict priority.
146        if queue.is_empty() {
147            let state = self.state.load(Ordering::Acquire);
148            debug_assert_eq!(state & CLOSED, 0);
149            if state & PERMIT_MASK != 0 {
150                match self.state.compare_exchange(
151                    state,
152                    state - 1,
153                    Ordering::AcqRel,
154                    Ordering::Acquire,
155                ) {
156                    Ok(_) => {
157                        self.state.fetch_and(!HAS_WAITERS, Ordering::Release);
158                        return RegisterResult::Acquired;
159                    }
160                    Err(actual) => {
161                        // A release that began before HAS_WAITERS was set may
162                        // have changed the count once. It cannot keep changing
163                        // while this queue lock is held.
164                        if actual & PERMIT_MASK != 0 {
165                            let taken = self.state.compare_exchange(
166                                actual,
167                                actual - 1,
168                                Ordering::AcqRel,
169                                Ordering::Acquire,
170                            );
171                            if taken.is_ok() {
172                                self.state.fetch_and(!HAS_WAITERS, Ordering::Release);
173                                return RegisterResult::Acquired;
174                            }
175                        }
176                    }
177                }
178            }
179        }
180
181        let waiter = Arc::new(Waiter::new());
182        let key = queue.push(priority, waiter.clone(), waker.clone());
183        RegisterResult::Queued { key, waiter }
184    }
185
186    pub(crate) fn refresh_waker(&self, key: WaitKey, waiter: &Waiter, waker: &Waker) {
187        let mut queue = self.waiters.lock();
188        if waiter.is_waiting() {
189            queue.update_waker(key, waker);
190        }
191    }
192
193    pub(crate) fn cancel_waiter(&self, key: WaitKey, waiter: &Waiter) {
194        let assigned = {
195            let mut queue = self.waiters.lock();
196            if waiter.is_waiting() {
197                let removed = queue.remove(key);
198                if removed.is_some() && queue.is_empty() {
199                    self.state.fetch_and(!HAS_WAITERS, Ordering::Release);
200                }
201                false
202            } else {
203                waiter.is_assigned()
204            }
205        };
206
207        if assigned {
208            self.release_one();
209        }
210    }
211
212    pub(crate) fn release_one(&self) {
213        let mut state = self.state.load(Ordering::Acquire);
214        loop {
215            if state & HAS_WAITERS != 0 {
216                self.release_slow();
217                return;
218            }
219            debug_assert!((state & PERMIT_MASK) < self.max_permits);
220            match self.state.compare_exchange_weak(
221                state,
222                state + 1,
223                Ordering::Release,
224                Ordering::Acquire,
225            ) {
226                Ok(_) => return,
227                Err(actual) => state = actual,
228            }
229        }
230    }
231
232    pub(crate) fn try_take(&self) -> Result<(), TryAcquireError> {
233        let mut state = self.state.load(Ordering::Acquire);
234        loop {
235            if state & CLOSED != 0 {
236                return Err(Closed);
237            }
238            if state & HAS_WAITERS != 0 || state & PERMIT_MASK == 0 {
239                return Err(NoPermits);
240            }
241            match self.state.compare_exchange_weak(
242                state,
243                state - 1,
244                Ordering::Acquire,
245                Ordering::Relaxed,
246            ) {
247                Ok(_) => return Ok(()),
248                Err(actual) => state = actual,
249            }
250        }
251    }
252
253    fn release_slow(&self) {
254        let wake = {
255            let mut queue = self.waiters.lock();
256            let state = self.state.load(Ordering::Acquire);
257            if state & CLOSED == 0 {
258                if let Some(entry) = queue.pop() {
259                    entry.waiter.assign();
260                    if queue.is_empty() {
261                        self.state.fetch_and(!HAS_WAITERS, Ordering::Release);
262                    }
263                    Some(entry.waker)
264                } else {
265                    self.return_to_pool(&queue);
266                    None
267                }
268            } else {
269                // Close normally drained the queue before we could acquire the
270                // lock. Keep this branch defensive for unusual interleavings.
271                let entries = queue.drain();
272                for entry in &entries {
273                    entry.waiter.close();
274                }
275                self.return_to_pool(&queue);
276                drop(queue);
277                for entry in entries {
278                    entry.waker.wake();
279                }
280                return;
281            }
282        };
283        if let Some(waker) = wake {
284            waker.wake();
285        }
286    }
287
288    fn return_to_pool(&self, queue: &WaitQueue) {
289        debug_assert!(queue.is_empty());
290        self.state.fetch_and(!HAS_WAITERS, Ordering::Release);
291        let mut state = self.state.load(Ordering::Acquire);
292        loop {
293            debug_assert!((state & PERMIT_MASK) < self.max_permits);
294            match self.state.compare_exchange_weak(
295                state,
296                state + 1,
297                Ordering::Release,
298                Ordering::Acquire,
299            ) {
300                Ok(_) => return,
301                Err(actual) => state = actual,
302            }
303        }
304    }
305}