Skip to main content

shuttle_engine/future/
batch_semaphore.rs

1//! A counting semaphore supporting both async and sync operations.
2use crate::runtime::execution::ExecutionState;
3use crate::runtime::task::{clock::VectorClock, TaskId};
4use crate::runtime::thread;
5use crate::sync_types::{ResourceSignature, ResourceType};
6use crate::{backtrace_enabled, current};
7use std::cell::RefCell;
8use std::collections::VecDeque;
9use std::fmt;
10use std::future::Future;
11use std::pin::Pin;
12use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
13use std::sync::Arc;
14use std::sync::Mutex;
15use std::task::{Context, Poll, Waker};
16use tracing::trace;
17
18struct Waiter {
19    /// The task waiting on this waiter's `Acquire`.
20    ///
21    /// Refreshed on every poll (like `waker`) rather than frozen at creation
22    /// time. An `Acquire` future is not necessarily owned by the task that
23    /// created it: it can be cached inside a longer-lived object and later
24    /// polled by a different task (tokio's `poll_recv(&mut self, cx)` is the
25    /// motivating example — the in-flight acquire lives in the `Receiver`, and
26    /// a `Receiver` may be moved between tasks). The semaphore must unblock
27    /// whoever is actually waiting now, so this follows the poller. This
28    /// mirrors tokio's own `batch_semaphore`, which refreshes its waiter's
29    /// `Waker` under a `will_wake` check.
30    ///
31    /// Stored as an atomic rather than a `Cell` to keep `Waiter` (and hence
32    /// `Acquire`) `Sync`.
33    task_id: AtomicUsize,
34    num_permits: usize,
35    is_queued: AtomicBool,
36    has_permits: AtomicBool,
37    /// Clock of the task that created this waiter. Note this is *not* refreshed
38    /// when `task_id` is: it is only used to seed the causality of the acquired
39    /// permits, and keeping the original enqueue clock is conservative (it can
40    /// only add happens-before edges, never remove them).
41    clock: VectorClock,
42    waker: Mutex<Option<Waker>>,
43}
44
45// Implement debug in order to not output the `VectorClock`
46impl fmt::Debug for Waiter {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        f.debug_struct("Waiter")
49            .field("task_id", &self.task_id())
50            .field("num_permits", &self.num_permits)
51            .field("is_queued", &self.is_queued)
52            .field("has_permits", &self.has_permits)
53            .field("waker", &self.waker)
54            .finish()
55    }
56}
57
58impl Waiter {
59    /// A `Waiter` is the part of an acquire that a *releasing* task can see and
60    /// mutate, so it only needs to exist once an acquire actually blocks.
61    ///
62    /// `clock` is passed in rather than read from the ambient execution state,
63    /// because it must be snapshotted when the `Acquire` was created, not when it
64    /// later blocks: it feeds the happens-before edge recorded in
65    /// `unblock_waiters_from_front`, and a scheduling point sits between those two
66    /// moments. `task_id`, in contrast, tracks the current poller (see
67    /// [`Waiter::task_id`]), so it is read here and refreshed on later polls.
68    fn new(num_permits: usize, clock: VectorClock) -> Self {
69        Self {
70            task_id: AtomicUsize::new(ExecutionState::me().into()),
71            num_permits,
72            is_queued: AtomicBool::new(false),
73            has_permits: AtomicBool::new(false),
74            clock,
75            waker: Mutex::new(None),
76        }
77    }
78
79    /// The task currently waiting on this waiter. See [`Waiter::task_id`].
80    fn task_id(&self) -> TaskId {
81        TaskId::from(self.task_id.load(Ordering::SeqCst))
82    }
83
84    /// Point this waiter at the task that is polling it now, so that a later
85    /// `release` unblocks the current poller rather than whoever polled first.
86    fn set_task_id(&self, task_id: TaskId) {
87        self.task_id.store(task_id.into(), Ordering::SeqCst);
88    }
89}
90
91/// Number of permits (`num_available`) available to be acquired. The permits
92/// are grouped into batches in the `permit_clocks` deque, such that batches
93/// farther back correspond to later `release` calls. Each batch is a tuple
94/// of the permits remaining in that batch and the clock of the event whence
95/// the permits originate.
96struct PermitsAvailable {
97    // Invariant: the number of permits available is equal to the sum of the
98    // batch sizes in the queue.
99    num_available: usize,
100
101    /// Batches of permits with associated clocks (corresponding to the
102    /// `release` events that created them). This is an `Option` because the
103    /// deque is lazily initialized; see `const_new`.
104    permit_clocks: Option<VecDeque<(usize, VectorClock)>>,
105
106    /// The clock of the last successful acquire event. Used for causal
107    /// dependence in `try_acquire` failures.
108    last_acquire: VectorClock,
109}
110
111// Implement debug in order to not output the `VectorClock`s
112impl fmt::Debug for PermitsAvailable {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        f.debug_struct("PermitsAvailable")
115            .field("num_available", &self.num_available)
116            .finish()
117    }
118}
119
120impl PermitsAvailable {
121    fn new(num_permits: usize) -> Self {
122        let mut permit_clocks = VecDeque::new();
123        if num_permits > 0 {
124            permit_clocks.push_back((num_permits, current::clock()));
125        }
126        Self {
127            num_available: num_permits,
128            permit_clocks: Some(permit_clocks),
129            last_acquire: VectorClock::new(),
130        }
131    }
132
133    const fn const_new(num_permits: usize) -> Self {
134        // A `VecDeque` cannot be populated in a const fn, due to allocation.
135        // Instead, we set `permit_clocks` to `None`, and initialize it lazily
136        // when it is needed for the first time, to contain one batch of size
137        // `num_permits`.
138        Self {
139            num_available: num_permits,
140            permit_clocks: None,
141            last_acquire: VectorClock::new(),
142        }
143    }
144
145    fn available(&self) -> usize {
146        self.num_available
147    }
148
149    fn init_permit_clocks(&mut self) {
150        if self.permit_clocks.is_none() {
151            let mut permit_clocks = VecDeque::new();
152            if self.num_available > 0 {
153                permit_clocks.push_back((self.num_available, VectorClock::new()));
154            }
155            self.permit_clocks = Some(permit_clocks);
156        }
157    }
158
159    fn acquire(&mut self, mut num_permits: usize, acquire_clock: VectorClock) -> Result<VectorClock, TryAcquireError> {
160        // Acquiring zero permits is always possible, and is not causally
161        // dependent on any event.
162        if num_permits == 0 {
163            return Ok(VectorClock::new());
164        }
165
166        if num_permits <= self.num_available {
167            self.init_permit_clocks();
168            self.last_acquire.update(&acquire_clock);
169            self.num_available -= num_permits;
170
171            // Acquire `num_permits` from the available batches. This may
172            // consume one or more batches from the queue. The resulting clock
173            // is the join of all the batches used (fully or partially), since
174            // the acquiry causally depends on the releases that created those
175            // batches.
176            let mut clock = VectorClock::new();
177            let permit_clocks = self.permit_clocks.as_mut().unwrap();
178            while let Some((batch_size, batch_clock)) = permit_clocks.front_mut() {
179                clock.update(batch_clock);
180
181                if num_permits < *batch_size {
182                    // The current batch is larger than the number of permits
183                    // requested: diminish batch, finish loop.
184                    *batch_size -= num_permits;
185                    num_permits = 0;
186                } else {
187                    // The current batch is fully consumed by the request.
188                    // Remove it from the queue.
189                    num_permits -= *batch_size;
190                    permit_clocks.pop_front();
191                }
192
193                // Break early to avoid causally depending on the next batch.
194                if num_permits == 0 {
195                    break;
196                }
197            }
198
199            assert_eq!(num_permits, 0);
200            Ok(clock)
201        } else {
202            // There are not enough permits to fulfill the request.
203            Err(TryAcquireError::NoPermits)
204        }
205    }
206
207    fn release(&mut self, num_permits: usize, clock: VectorClock) {
208        self.init_permit_clocks();
209        self.num_available += num_permits;
210        self.permit_clocks.as_mut().unwrap().push_back((num_permits, clock));
211    }
212}
213
214/// Fairness mode for the semaphore. Determines which threads are woken when
215/// permits are released.
216#[derive(Clone, Copy, Debug, PartialEq, Eq)]
217pub enum Fairness {
218    /// The semaphore is strictly fair, so earlier requesters always get
219    /// priority over later ones.
220    StrictlyFair,
221
222    /// The semaphore makes no guarantees about fairness. In particular,
223    /// a waiter can be starved by other threads.
224    Unfair,
225}
226
227/// Where an acquire request sits relative to waiters that are already queued on
228/// a [`Fairness::StrictlyFair`] semaphore. Ignored by an unfair semaphore, which
229/// has no queue order to speak of.
230#[derive(Clone, Copy, Debug, PartialEq, Eq)]
231enum Priority {
232    /// The default: queue behind existing waiters, and do not take available
233    /// permits while any waiter is queued.
234    Back,
235
236    /// Overtake every queued waiter: take available permits even when others are
237    /// waiting, and if there still aren't enough, queue at the *front*.
238    ///
239    /// This is only correct for a requester that already holds permits of this
240    /// semaphore and is escalating its own claim (see [`BatchSemaphore::upgrade`]).
241    /// Such a request cannot be satisfied by making the queue wait its turn --
242    /// queued waiters hold no permits, so they can never release what the
243    /// requester is missing, and the requester will not release what it holds.
244    /// Deadlock is avoided precisely by letting it overtake them.
245    Front,
246}
247
248/// A counting semaphore which permits waiting on multiple permits at once,
249/// and supports both asychronous and synchronous blocking operations.
250#[derive(Debug)]
251struct BatchSemaphoreState {
252    id: Option<crate::annotations::ObjectId>,
253
254    // Key invariants:
255    //
256    // (1) if `waiters` is nonempty and the head waiter is `H`,
257    // then `H.num_permits > permits_available.available()`.  (In other words,
258    // we are never in a state where there are enough permits available for the
259    // first waiter.  This invariant is ensured by the `drop` handler below.)
260    //
261    // (2) W is in waiters iff W.is_queued
262    //
263    // (3) W.is_queued ==> !W.has_permits
264    // Note: the converse is not true.  We can have !W.has_permits && !W.is_queued
265    // when the Acquire is created but not yet polled.
266    //
267    // (4) closed ==> waiters.is_empty()
268    waiters: VecDeque<Arc<Waiter>>,
269    permits_available: PermitsAvailable,
270    // TODO: should there be a clock for the close event?
271    closed: bool,
272}
273
274impl BatchSemaphoreState {
275    fn acquire_permits(
276        &mut self,
277        num_permits: usize,
278        fairness: Fairness,
279        priority: Priority,
280    ) -> Result<(), TryAcquireError> {
281        assert!(num_permits > 0);
282        if self.closed {
283            Err(TryAcquireError::Closed)
284        } else if self.waiters.is_empty() || matches!(fairness, Fairness::Unfair) || priority == Priority::Front {
285            // Permits here can be acquired in one of three scenarios:
286            // - The waiter queue is empty; nobody else is waiting for permits,
287            //   so if there are enough available, immediately succeed.
288            // - The semaphore is operating in an unfair mode; the current
289            //   thread is either requesting permits for the first time, or it
290            //   was woken and selected by the scheduler. In either case, the
291            //   thread may succeed, as long as there are enough permits.
292            // - The request has `Priority::Front`, so it deliberately overtakes
293            //   the queue (see `BatchSemaphore::upgrade`). Queued waiters hold
294            //   no permits, so they cannot prevent this request from succeeding.
295
296            let clock = self.permits_available.acquire(num_permits, current::clock())?;
297
298            // If successful, the acquiry is causally dependent on the event
299            // which released the acquired permits.
300            ExecutionState::with(|s| {
301                s.update_clock(&clock);
302            });
303
304            Ok(())
305        } else {
306            Err(TryAcquireError::NoPermits)
307        }
308    }
309
310    fn unblock_waiters_from_front(&mut self) {
311        while let Some(front) = self.waiters.front() {
312            // A waiter whose task has already finished is stale: its `Acquire`
313            // future was cancelled (e.g. a `select!` branch lost, or a
314            // `poll_recv`-style API cached the `Acquire` inside a longer-lived
315            // object) and the registering task then exited. There is nobody to
316            // unblock, so discard the waiter without consuming permits; if the
317            // `Acquire` is still alive and some other task polls it, it will
318            // re-acquire from the (still available) permits.
319            //
320            // `remove_waiter` can reach this during execution cleanup, when the
321            // task list is gone, so probe defensively and treat "can't tell" as
322            // not stale (i.e. preserve the old behaviour).
323            let front_is_stale = ExecutionState::try_with(|s| {
324                !s.in_cleanup() && s.try_get(front.task_id()).is_some_and(|t| t.finished())
325            })
326            .unwrap_or(false);
327            if front_is_stale {
328                let waiter = self.waiters.pop_front().unwrap();
329                waiter.is_queued.store(false, Ordering::SeqCst);
330                // Preserve the "queued <=> waker registered" invariant asserted
331                // in `Acquire::poll`; waking a finished task's waker is a no-op.
332                waiter.waker.lock().unwrap().take();
333                trace!("dropping stale waiter {:?} for finished task", waiter);
334                continue;
335            }
336            if front.num_permits <= self.permits_available.available() {
337                let waiter = self.waiters.pop_front().unwrap();
338
339                crate::annotations::record_semaphore_acquire_unblocked(
340                    self.id.unwrap(),
341                    waiter.task_id(),
342                    waiter.num_permits,
343                );
344
345                // The clock we pass into the semaphore is the clock of the
346                // waiter, corresponding to the point at which the waiter was
347                // enqueued. The clock we get in return corresponds to the
348                // join of the clocks of the acquired permits, used to update
349                // the waiter's clock to causally depend on the release events.
350                let clock = self
351                    .permits_available
352                    .acquire(waiter.num_permits, waiter.clock.clone())
353                    .unwrap();
354                trace!("granted {:?} permits to waiter {:?}", waiter.num_permits, waiter);
355
356                // Update waiter state as it is no longer in the queue
357                assert!(waiter.is_queued.swap(false, Ordering::SeqCst));
358                assert!(!waiter.has_permits.swap(true, Ordering::SeqCst));
359                ExecutionState::with(|s| {
360                    let task = s.get_mut(waiter.task_id());
361                    assert!(!task.finished());
362                    // The acquiry is causally dependent on the event
363                    // which released the acquired permits.
364                    task.clock.update(&clock);
365                    task.unblock();
366                });
367                let mut maybe_waker = waiter.waker.lock().unwrap();
368                if let Some(waker) = maybe_waker.take() {
369                    waker.wake();
370                }
371            } else {
372                return;
373            }
374        }
375    }
376}
377
378/// Counting semaphore
379#[derive(Debug)]
380pub struct BatchSemaphore {
381    state: RefCell<BatchSemaphoreState>,
382    fairness: Fairness,
383    #[allow(unused)]
384    signature: ResourceSignature,
385}
386
387/// Error returned from the [`BatchSemaphore::try_acquire`] function.
388#[derive(Debug, PartialEq, Eq)]
389pub enum TryAcquireError {
390    /// The semaphore has been closed and cannot issue new permits.
391    Closed,
392
393    /// The semaphore has no available permits.
394    NoPermits,
395}
396
397/// Error returned from the [`BatchSemaphore::acquire`] function.
398///
399/// An `acquire*` operation can only fail if the semaphore has been
400/// closed.
401#[derive(Debug)]
402pub struct AcquireError(());
403
404impl AcquireError {
405    fn closed() -> AcquireError {
406        AcquireError(())
407    }
408}
409
410impl fmt::Display for AcquireError {
411    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
412        write!(fmt, "semaphore closed")
413    }
414}
415
416impl std::error::Error for AcquireError {}
417
418impl BatchSemaphore {
419    /// Creates a new semaphore with the initial number of permits.
420    #[track_caller]
421    pub fn new(num_permits: usize, fairness: Fairness) -> Self {
422        Self::new_with_signature(
423            num_permits,
424            fairness,
425            ExecutionState::new_resource_signature(ResourceType::BatchSemaphore),
426        )
427    }
428
429    pub fn new_with_signature(num_permits: usize, fairness: Fairness, signature: ResourceSignature) -> Self {
430        let state = RefCell::new(BatchSemaphoreState {
431            id: Some(crate::annotations::record_semaphore_created()),
432            waiters: VecDeque::new(),
433            permits_available: PermitsAvailable::new(num_permits),
434            closed: false,
435        });
436        Self {
437            state,
438            fairness,
439            signature,
440        }
441    }
442
443    /// Creates a new semaphore with the initial number of permits.
444    #[track_caller]
445    pub const fn const_new(num_permits: usize, fairness: Fairness) -> Self {
446        Self::const_new_with_signature(
447            num_permits,
448            fairness,
449            ResourceSignature::new_const(ResourceType::BatchSemaphore),
450        )
451    }
452
453    pub const fn const_new_with_signature(
454        num_permits: usize,
455        fairness: Fairness,
456        signature: ResourceSignature,
457    ) -> Self {
458        let state = RefCell::new(BatchSemaphoreState {
459            id: None,
460            waiters: VecDeque::new(),
461            permits_available: PermitsAvailable::const_new(num_permits),
462            closed: false,
463        });
464        Self {
465            state,
466            fairness,
467            signature,
468        }
469    }
470
471    /// Returns the current number of available permits.
472    pub fn available_permits(&self) -> usize {
473        let state = self.state.borrow();
474        state.permits_available.available()
475    }
476
477    fn init_object_id(&self) {
478        let mut state = self.state.borrow_mut();
479        if state.id.is_none() {
480            state.id = Some(crate::annotations::record_semaphore_created());
481        }
482    }
483
484    /// Closes the semaphore. This prevents the semaphore from issuing new
485    /// permits and notifies all pending waiters.
486    pub fn close(&self) {
487        thread::switch();
488        self.close_no_scheduling_point();
489    }
490
491    /// Closes the semaphore without invoking `thread::switch`
492    pub fn close_no_scheduling_point(&self) {
493        self.init_object_id();
494        let mut state = self.state.borrow_mut();
495        if state.closed {
496            return;
497        }
498        crate::annotations::record_semaphore_closed(state.id.unwrap());
499        state.closed = true;
500
501        // Wake up all the waiters.  Since we've marked the state as closed, they
502        // will all return `AcquireError::closed` from their acquire calls.
503        let ptr = &*state as *const BatchSemaphoreState;
504        for waiter in state.waiters.drain(..) {
505            trace!(
506                "semaphore {:p} removing and waking up waiter {:?} on close",
507                ptr,
508                waiter,
509            );
510            assert!(waiter.is_queued.swap(false, Ordering::SeqCst));
511            assert!(!waiter.has_permits.load(Ordering::SeqCst)); // sanity check
512            ExecutionState::with(|exec_state| {
513                // A waiter whose task has finished is stale (its `Acquire` was
514                // cancelled and the task exited); there is nothing to unblock.
515                if !exec_state.in_cleanup() && !exec_state.get(waiter.task_id()).finished() {
516                    exec_state.get_mut(waiter.task_id()).unblock();
517                }
518            });
519            let mut maybe_waker = waiter.waker.lock().unwrap();
520            if let Some(waker) = maybe_waker.take() {
521                waker.wake();
522            }
523        }
524    }
525
526    /// Returns true iff the semaphore is closed.
527    pub fn is_closed(&self) -> bool {
528        let state = self.state.borrow();
529        state.closed
530    }
531
532    /// Try to acquire the specified number of permits from the Semaphore.
533    /// If the permits are available, returns Ok(())
534    /// If the semaphore is closed, returns `Err(TryAcquireError::Closed)`
535    /// If there aren't enough permits, returns `Err(TryAcquireError::NoPermits)`
536    pub fn try_acquire(&self, num_permits: usize) -> Result<(), TryAcquireError> {
537        thread::switch();
538
539        self.init_object_id();
540        let mut state = self.state.borrow_mut();
541        let id = state.id.unwrap();
542        let res = state
543            .acquire_permits(num_permits, self.fairness, Priority::Back)
544            .inspect_err(|_err| {
545                // Conservatively, the requester causally depends on the
546                // last successful acquire.
547                // TODO: This is not precise, but `try_acquire` causal dependency
548                // TODO: is both hard to define, and is most likely not worth the
549                // TODO: effort. The cases where causality would be tracked
550                // TODO: "imprecisely" do not correspond to commonly used sync.
551                // TODO: primitives, such as mutexes, mutexes, or condvars.
552                // TODO: An example would be a counting semaphore used to guard
553                // TODO: access to N homogenous resources (as opposed to FIFO,
554                // TODO: heterogenous resources).
555                // TODO: More precision could be gained by tracking clocks for all
556                // TODO: current permit holders, with a data structure similar to
557                // TODO: `permits_available`.
558                ExecutionState::with(|s| {
559                    s.update_clock(&state.permits_available.last_acquire);
560                });
561            });
562        drop(state);
563
564        // If we won the race for permits of an unfair semaphore, re-block
565        // other waiting threads that can no longer succeed.
566        if res.is_ok() {
567            self.reblock_if_unfair();
568        }
569
570        crate::annotations::record_semaphore_try_acquire(id, num_permits, res.is_ok());
571
572        res
573    }
574
575    /// Clean-up method used when a thread succeeds in acquiring permits. If
576    /// the semaphore is unfair, a preceding `release` may have unblocked a
577    /// number of threads, some of which may no longer be able to succeed with
578    /// the permits remaining in the semaphore.
579    fn reblock_if_unfair(&self) {
580        if self.fairness == Fairness::Unfair {
581            let state = self.state.borrow_mut();
582            ExecutionState::with(|s| {
583                for waiter in &state.waiters {
584                    let available = state.permits_available.available();
585                    // Skip stale waiters: the task that registered the waiter
586                    // has finished, so there is nothing to block.
587                    if available < waiter.num_permits && s.try_get(waiter.task_id()).is_some_and(|t| !t.finished()) {
588                        // Block this waiter: it cannot succeed (there are not
589                        // enough permits available); its `poll` would return
590                        // without resolving.
591                        s.get_mut(waiter.task_id()).block(false);
592                    }
593                }
594            });
595        }
596    }
597
598    fn enqueue_waiter(&self, waiter: &Arc<Waiter>, priority: Priority) {
599        let mut state = self.state.borrow_mut();
600
601        trace!(
602            "enqueuing waiter {:?} ({priority:?}) for semaphore {:p}",
603            waiter,
604            &self.state
605        );
606        match priority {
607            Priority::Back => state.waiters.push_back(waiter.clone()),
608            // Overtakes the queue rather than joining its tail. Key invariant (1)
609            // still holds: we only get here because the acquire failed, and a
610            // `Priority::Front` acquire only fails when there really aren't
611            // enough permits available, so the new head cannot be grantable.
612            Priority::Front => state.waiters.push_front(waiter.clone()),
613        }
614
615        assert!(!waiter.has_permits.load(Ordering::SeqCst));
616        assert!(!waiter.is_queued.swap(true, Ordering::SeqCst));
617    }
618
619    fn remove_waiter(&self, waiter: &Arc<Waiter>) {
620        let mut state = self.state.borrow_mut();
621
622        trace!(waiters = ?state.waiters, "removing waiter {:?} from semaphore {:p}", waiter, &self.state);
623
624        // sanity checks
625        assert!(!state.closed);
626        assert!(!waiter.has_permits.load(Ordering::SeqCst));
627
628        let index = state
629            .waiters
630            .iter()
631            .position(|x| Arc::ptr_eq(x, waiter))
632            .expect("did not find waiter");
633
634        state.waiters.remove(index).unwrap();
635        assert!(waiter.is_queued.swap(false, Ordering::SeqCst));
636
637        match self.fairness {
638            Fairness::StrictlyFair => {
639                if index == 0 {
640                    // If the semaphore is strictly fair, and we removed the first waiter, check if its
641                    // removal unblocks remaining waiters.  This can happen in the following situation:
642                    // - the semahore has 1 permit available
643                    // - there are 2 waiters W1 and W2 where W1 wants 2 permits, and W2 wants 1 permit
644                    // - if W1 gives up and drops out, we want to ensure W2 is granted the semaphore
645                    state.unblock_waiters_from_front();
646                }
647            }
648            Fairness::Unfair => {}
649        }
650    }
651
652    /// Acquire the specified number of permits (async API)
653    pub fn acquire(&self, num_permits: usize) -> Acquire<'_> {
654        // No switch here; switch should be triggered on polling future
655        self.init_object_id();
656        Acquire::new(self, num_permits, Priority::Back)
657    }
658
659    /// Acquire the specified number of permits (blocking API)
660    pub fn acquire_blocking(&self, num_permits: usize) -> Result<(), AcquireError> {
661        crate::future::block_on(self.acquire(num_permits))
662    }
663
664    /// Release `num_permits` back to the Semaphore
665    pub fn release(&self, num_permits: usize) {
666        thread::switch();
667
668        self.init_object_id();
669        if num_permits == 0 {
670            return;
671        }
672
673        let mut state = self.state.borrow_mut();
674
675        crate::annotations::record_semaphore_release(state.id.unwrap(), num_permits);
676
677        if ExecutionState::should_stop() {
678            // In case we are panicking, we release permits, but also clear
679            // the waiters queue: we should not unblock the threads at this
680            // point. However, the permits are released such that future
681            // acquires may succeed, as long as the requesters were not
682            // blocking on the semaphore at the time of the panic. This is
683            // used to correctly model lock poisoning.
684            state.permits_available.release(num_permits, VectorClock::new());
685            for waiter in &state.waiters {
686                waiter.is_queued.swap(false, Ordering::SeqCst);
687            }
688            state.waiters.clear();
689            state.closed = true;
690            return;
691        }
692
693        // Permits released into the semaphore reflect the releasing thread's
694        // clock; future acquires of those permits are causally dependent on
695        // this event.
696        ExecutionState::with(|s| {
697            let clock = s.increment_clock();
698            state.permits_available.release(num_permits, clock.clone());
699        });
700
701        // `ExecutionState::me()` is only wanted for this trace, so let the macro's
702        // level check decide whether to pay for it. Computing it eagerly cost an
703        // `ExecutionState::with` on every release even with tracing disabled.
704        trace!(task = ?ExecutionState::me(), avail = ?state.permits_available, waiters = ?state.waiters, "released {} permits for semaphore {:p}", num_permits, &self.state);
705
706        match self.fairness {
707            Fairness::StrictlyFair => {
708                // in a strictly fair mode we will grant permits to waiters from the front
709                // of the queue, as long as there are enough permits available
710                state.unblock_waiters_from_front();
711            }
712            Fairness::Unfair => {
713                // in an unfair mode, we will unblock all the waiters for which
714                // there are enough permits available, then let them race
715                let num_available = state.permits_available.available();
716                for waiter in &mut state.waiters {
717                    if waiter.num_permits <= num_available {
718                        // A waiter whose task has already finished is stale: its
719                        // `Acquire` was cancelled (and possibly cached in a
720                        // longer-lived object) and the task then exited. Unlike
721                        // the strictly fair case there is nothing to clean up —
722                        // an unfair waiter holds no permits, so it blocks
723                        // nobody — but there is also nobody to unblock.
724                        let stale = ExecutionState::with(|s| {
725                            let task = s.get_mut(waiter.task_id());
726                            if task.finished() {
727                                true
728                            } else {
729                                task.unblock();
730                                false
731                            }
732                        });
733                        if stale {
734                            continue;
735                        }
736                        let maybe_waker = waiter.waker.lock().unwrap();
737                        if let Some(waker) = maybe_waker.as_ref() {
738                            waker.wake_by_ref();
739                        }
740                    }
741                }
742            }
743        }
744        drop(state);
745    }
746
747    /// Atomically `upgrade` from holding `permits_currently_held` permits to holding
748    /// `permits_to_be_held`, without ever dropping below `permits_currently_held` in between.
749    /// The motivating use case is `parking_lot`'s `RwLockUpgradableReadGuard::upgrade`, which must
750    /// take a read guard to a write guard without letting any writer in along the way.
751    ///
752    /// This is implemented by acquiring only the *missing* permits
753    /// (`permits_to_be_held - permits_currently_held`), with priority over any waiter already
754    /// queued, so that the request overtakes the queue. Both halves of that matter:
755    ///
756    /// * Keeping the held permits means no other task can claim the resource mid-upgrade. Releasing
757    ///   them first (even for an instant) would hand the resource to a queued waiter, which for an
758    ///   `RwLock` means a writer mutating the data an upgradable reader had already observed.
759    /// * Overtaking the queue is what makes that safe rather than deadlock-prone. Since we hold
760    ///   permits we will not release, a queued waiter ahead of us may be unsatisfiable (an `RwLock`
761    ///   writer wants *all* permits), so waiting our turn behind it could deadlock. Queued waiters
762    ///   hold no permits, so overtaking them costs nothing but their place in line -- which is
763    ///   exactly the priority a real upgradable read lock gives an upgrade.
764    ///
765    /// The upgrade therefore blocks only on tasks that *currently hold* permits, and is granted as
766    /// soon as they release. The returned future must be driven to completion; if it is dropped
767    /// first, the caller still holds `permits_currently_held`.
768    ///
769    /// At most one `upgrade` may be in flight on a semaphore at a time. Two concurrent upgraders
770    /// could each be waiting for permits the other holds, which no queue discipline can resolve.
771    /// Callers are expected to enforce this (an `RwLock` does: there is only ever one upgradable
772    /// reader).
773    pub fn upgrade(&self, permits_currently_held: usize, permits_to_be_held: usize) -> Acquire<'_> {
774        assert!(permits_currently_held > 0);
775        assert!(permits_to_be_held > permits_currently_held);
776
777        self.init_object_id();
778        Acquire::new(self, permits_to_be_held - permits_currently_held, Priority::Front)
779    }
780
781    /// The non-blocking analogue of [`BatchSemaphore::upgrade`]: succeeds only if the missing
782    /// permits are available right now, and never blocks or queues.
783    ///
784    /// Like `upgrade`, this ignores queued waiters (they hold no permits, so they cannot be the
785    /// reason the upgrade is short of permits). A `try_upgrade` therefore fails only when some
786    /// other task actually *holds* permits the upgrade needs.
787    pub fn try_upgrade(&self, permits_currently_held: usize, permits_to_be_held: usize) -> Result<(), TryAcquireError> {
788        assert!(permits_currently_held > 0);
789        assert!(permits_to_be_held > permits_currently_held);
790
791        thread::switch();
792
793        self.init_object_id();
794        let num_permits = permits_to_be_held - permits_currently_held;
795        let mut state = self.state.borrow_mut();
796        let id = state.id.unwrap();
797        let res = state
798            .acquire_permits(num_permits, self.fairness, Priority::Front)
799            .inspect_err(|_err| {
800                // Conservatively, the requester causally depends on the last successful acquire;
801                // see the equivalent reasoning in `try_acquire`.
802                ExecutionState::with(|s| {
803                    s.update_clock(&state.permits_available.last_acquire);
804                });
805            });
806        drop(state);
807
808        // If we took permits from an unfair semaphore, re-block waiting threads that can no longer
809        // succeed.
810        if res.is_ok() {
811            self.reblock_if_unfair();
812        }
813
814        crate::annotations::record_semaphore_try_acquire(id, num_permits, res.is_ok());
815
816        res
817    }
818}
819
820// Safety: Semaphore is never actually passed across true threads, only across continuations. The
821// RefCell<_> type therefore can't be preempted mid-bookkeeping-operation.
822// TODO we shouldn't need to do this, but RefCell is not Send, and anything we put within a Semaphore
823// TODO needs to be Send.
824unsafe impl Send for BatchSemaphore {}
825unsafe impl Sync for BatchSemaphore {}
826
827impl Default for BatchSemaphore {
828    #[track_caller]
829    fn default() -> Self {
830        Self::new(Default::default(), Fairness::StrictlyFair)
831    }
832}
833
834/// The future that results from async calls to `acquire*`.
835/// Callers must `await` on this future to obtain the necessary permits.
836pub struct Acquire<'a> {
837    semaphore: &'a BatchSemaphore,
838    num_permits: usize,
839
840    /// Where this acquire sits relative to waiters already queued on a fair
841    /// semaphore. Only [`BatchSemaphore::upgrade`] uses [`Priority::Front`]; see
842    /// there for why an upgrade must overtake the queue.
843    priority: Priority,
844
845    /// Snapshotted when this `Acquire` is created, and moved into the `Waiter` if
846    /// this acquire ends up blocking. See `Waiter::new` for why the snapshot must
847    /// happen here rather than at enqueue time.
848    clock: VectorClock,
849
850    /// The shared part of this acquire, allocated only once the acquire has to
851    /// block. An acquire that gets its permits immediately is never visible to
852    /// any other task, so it needs no shared state and no allocation. While this
853    /// is `None`, `has_permits` below is authoritative.
854    waiter: Option<Arc<Waiter>>,
855
856    /// Whether permits have been granted, for the case where no `Waiter` exists.
857    /// Once one does, the releasing task writes `Waiter::has_permits` instead and
858    /// this field is unused; read through `Acquire::has_permits`.
859    has_permits: bool,
860
861    completed: bool, // Has the future completed yet?
862    never_polled: bool,
863}
864
865// Implement Debug in order to not output the `VectorClock`, matching `Waiter`.
866impl fmt::Debug for Acquire<'_> {
867    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
868        f.debug_struct("Acquire")
869            .field("num_permits", &self.num_permits)
870            .field("priority", &self.priority)
871            .field("waiter", &self.waiter)
872            .field("has_permits", &self.has_permits())
873            .field("completed", &self.completed)
874            .finish()
875    }
876}
877
878impl<'a> Acquire<'a> {
879    fn new(semaphore: &'a BatchSemaphore, num_permits: usize, priority: Priority) -> Self {
880        Self {
881            semaphore,
882            num_permits,
883            priority,
884            clock: current::clock(),
885            waiter: None,
886            has_permits: false,
887            completed: false,
888            never_polled: true,
889        }
890    }
891
892    /// Have permits been granted to this acquire? Once a `Waiter` exists the
893    /// releasing task owns that flag, so the shared copy is authoritative.
894    fn has_permits(&self) -> bool {
895        match &self.waiter {
896            Some(waiter) => waiter.has_permits.load(Ordering::SeqCst),
897            None => self.has_permits,
898        }
899    }
900
901    /// Is this acquire in the semaphore's waiter queue? Only possible once a
902    /// `Waiter` has been allocated, since the queue holds `Arc<Waiter>`.
903    fn is_queued(&self) -> bool {
904        match &self.waiter {
905            Some(waiter) => waiter.is_queued.load(Ordering::SeqCst),
906            None => false,
907        }
908    }
909
910    fn grant_permits(&mut self) {
911        match &self.waiter {
912            Some(waiter) => waiter.has_permits.store(true, Ordering::SeqCst),
913            None => self.has_permits = true,
914        }
915    }
916
917    /// The shared `Waiter` for this acquire, allocating it if this is the first
918    /// time the acquire has had to block. Returns an owned handle so callers can
919    /// still use `self.semaphore` without holding a borrow of `self`.
920    fn waiter_for_blocking(&mut self) -> Arc<Waiter> {
921        if let Some(waiter) = &self.waiter {
922            return Arc::clone(waiter);
923        }
924        let waiter = Arc::new(Waiter::new(self.num_permits, self.clock.clone()));
925        self.waiter = Some(Arc::clone(&waiter));
926        waiter
927    }
928}
929
930impl Future for Acquire<'_> {
931    type Output = Result<(), AcquireError>;
932
933    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
934        assert!(!self.completed);
935
936        // One borrow of the semaphore state rather than two (`is_closed` and
937        // `available_permits` each took their own). Both reads describe the same
938        // instant, before the scheduling point below, so merging them is sound.
939        // Reads *after* the switch must stay separate and fresh, because other
940        // tasks may have run in between.
941        let will_succeed = self.has_permits() || {
942            let state = self.semaphore.state.borrow();
943            state.closed || state.permits_available.available() >= self.num_permits
944        };
945
946        // If the acquire will succeed on the first try, we need to context switch once to allow the previous
947        // event to become visible. If we won't succeed, then we still need to context switch if the act of
948        // blocking does not commute with other operations on `batch_semaphore` (double-yield optimization,
949        // reasoning below).
950        //
951        // Fair Semaphores: blocking adds the current task to an *ordered* waiter queue. Two blocking acquires
952        // *do not commute* because in one ordering the queue will be [T1 T2] and in the other ordering [T2 T1].
953        // Thus we cannot apply the double-yield optimization for fair semaphores.
954        //
955        // Unfair Semaphores: blocking adds the current task to an *unordered set* of waiters. To check if the
956        // double-yield is valid we check if each operation (Z) on the semaphore commutes with a blocking acquire (Y1):
957        //
958        //     - Blocking Acquire: in both orderings `Z Y1` and `Y1 Z`, the waiter set has the same members, thus
959        //       the operations commute.
960        //     - Try Acquire: the try-acquire will fail in both orderings without changing the state of the semaphore
961        //     - Release: if the release unblocks Y1, then the optimization is not applicable. Otherwise, it must
962        //       unblock another task in the waiter set. As waiter-set insertion and removal for disjoint elements
963        //       commutes, release operations also commute in this case.
964        //
965        // Thus we apply the double-yield optimization for *unfair* semaphores only
966        let blocking_is_not_commutative = self.semaphore.fairness == Fairness::StrictlyFair;
967
968        if self.never_polled && (will_succeed || blocking_is_not_commutative) {
969            thread::switch();
970        }
971        self.never_polled = false;
972
973        let out = if self.has_permits() {
974            assert!(!self.is_queued());
975            self.completed = true;
976            trace!("Acquire::poll for {:?} with permits", self);
977            Poll::Ready(Ok(()))
978        } else if self.semaphore.is_closed() {
979            assert!(!self.is_queued());
980            self.completed = true;
981            trace!("Acquire::poll for {:?} with closed", self);
982            Poll::Ready(Err(AcquireError::closed()))
983        } else {
984            let is_queued = self.is_queued();
985            trace!("Acquire::poll for {:?}; is queued: {is_queued:?}", self);
986
987            // Sanity check: there should be a waker if the waiter is in
988            // the queue. Also true for unfair semaphores, which wake by ref.
989            //
990            // `debug_assert` rather than `assert`: this takes a `std::sync::Mutex`
991            // on every poll, including the uncontended fast path, purely to check
992            // an internal invariant.
993            debug_assert_eq!(
994                is_queued,
995                self.waiter
996                    .as_ref()
997                    .is_some_and(|waiter| waiter.waker.lock().unwrap().is_some())
998            );
999
1000            // Should the waiter try to acquire permits here? Four cases:
1001            // 1. unfair semaphore, waiter not yet enqueued;
1002            // 2. fair semaphore, waiter not yet enqueued;
1003            // 3. unfair semaphore, waiter already enqueued.
1004            // 4. fair semaphore, waiter already enqueued;
1005            //
1006            // 1. and 2. are similar: the future was polled for the first time,
1007            // so the waiter will try to acquire some permits. If successful,
1008            // the waiter need not be enqueued, and the future is resolved.
1009            // Otherwise, the waiter is added to the queue.
1010            //
1011            // 3. is slightly different: the future was polled, even though the
1012            // waiter was already in the queue. This can happen either because
1013            // the semaphore just received some permits and woke the waiter up,
1014            // or because the future itself was polled manually. Either way,
1015            // the semaphore is queried.
1016            //
1017            // 4. is a case where we do not try to acquire permits. The request
1018            // would always fail, and the waiter should remain suspended until
1019            // the semaphore has explicitly unblocked it and given it permits
1020            // during a `release` call.
1021            let try_to_acquire = match (self.semaphore.fairness, is_queued) {
1022                // written this way to mirror the cases described above
1023                (Fairness::Unfair, false) | (Fairness::StrictlyFair, false) | (Fairness::Unfair, true) => true,
1024                (Fairness::StrictlyFair, true) => false,
1025            };
1026
1027            if try_to_acquire {
1028                // Access the semaphore state directly instead of `try_acquire`,
1029                // because in case of `NoPermits`, we do not want to update the
1030                // clock, as this thread will be blocked below.
1031                let mut state = self.semaphore.state.borrow_mut();
1032                let id = state.id.unwrap();
1033                let acquire_result = state.acquire_permits(self.num_permits, self.semaphore.fairness, self.priority);
1034                drop(state);
1035
1036                match acquire_result {
1037                    Ok(()) => {
1038                        if is_queued {
1039                            let waiter = self
1040                                .waiter
1041                                .clone()
1042                                .expect("a queued acquire must have an allocated waiter");
1043                            crate::annotations::record_semaphore_acquire_unblocked(
1044                                id,
1045                                waiter.task_id(),
1046                                waiter.num_permits,
1047                            );
1048                            self.semaphore.remove_waiter(&waiter);
1049                        } else {
1050                            crate::annotations::record_semaphore_acquire_fast(id, self.num_permits);
1051                        }
1052                        self.grant_permits();
1053                        self.completed = true;
1054                        trace!("Acquire::poll for {:?} that got permits", self);
1055
1056                        // If the semaphore is unfair, re-block other waiting
1057                        // threads that can no longer succeed.
1058                        self.semaphore.reblock_if_unfair();
1059
1060                        Poll::Ready(Ok(()))
1061                    }
1062                    Err(TryAcquireError::NoPermits) => {
1063                        // This acquire has to block, so it now becomes visible to
1064                        // whichever task releases permits. That is the first point
1065                        // at which shared state is needed, so it is where the
1066                        // `Waiter` gets allocated.
1067                        let waiter = self.waiter_for_blocking();
1068
1069                        let mut maybe_waker = waiter.waker.lock().unwrap();
1070                        *maybe_waker = Some(cx.waker().clone());
1071                        drop(maybe_waker);
1072
1073                        // Point the waiter at whoever is polling now: this future
1074                        // may have been created by a different task.
1075                        waiter.set_task_id(ExecutionState::me());
1076
1077                        if !is_queued {
1078                            crate::annotations::record_semaphore_acquire_blocked(id, self.num_permits);
1079                            // `enqueue_waiter` sets `is_queued` itself.
1080                            self.semaphore.enqueue_waiter(&waiter, self.priority);
1081                        }
1082                        trace!("Acquire::poll for {:?} that is enqueued", self);
1083                        Poll::Pending
1084                    }
1085                    Err(TryAcquireError::Closed) => unreachable!(),
1086                }
1087            } else {
1088                // No progress made, future is still pending. The waiter stays in
1089                // the queue, but re-point it at the current poller and refresh
1090                // its waker: this future may have been created by (or last
1091                // polled by) another task, and `release` must wake whoever is
1092                // waiting now. Without this, a permit granted to this waiter
1093                // would unblock a task that is no longer interested, and the
1094                // actual poller would never be woken.
1095                let waiter = self
1096                    .waiter
1097                    .as_ref()
1098                    .expect("a queued acquire must have an allocated waiter");
1099                *waiter.waker.lock().unwrap() = Some(cx.waker().clone());
1100                waiter.set_task_id(ExecutionState::me());
1101                Poll::Pending
1102            }
1103        };
1104        if matches!(out, Poll::Pending) {
1105            // `Backtrace::capture()` is a noop (it returns the constant `disabled()`) if `RUST_BACKTRACE`/`RUST_LIB_BACKTRACE` is not set.
1106            ExecutionState::with(|state| {
1107                state.current_mut().backtrace = if backtrace_enabled() {
1108                    Some(std::backtrace::Backtrace::force_capture())
1109                } else {
1110                    None
1111                }
1112            })
1113        }
1114        out
1115    }
1116}
1117
1118impl Drop for Acquire<'_> {
1119    fn drop(&mut self) {
1120        trace!("Acquire::drop for {:?}", self);
1121        if self.is_queued() {
1122            // If the associated waiter is in the wait list, remove it
1123            let waiter = self
1124                .waiter
1125                .clone()
1126                .expect("a queued acquire must have an allocated waiter");
1127            self.semaphore.remove_waiter(&waiter);
1128        } else if self.has_permits() && !self.completed {
1129            // If the waiter was granted permits, release them. Note this must also
1130            // fire for an acquire that got its permits without ever allocating a
1131            // waiter, otherwise the semaphore leaks permits.
1132            self.semaphore.release(self.num_permits);
1133        }
1134    }
1135}
1136
1137impl crate::annotations::WithName for &BatchSemaphore {
1138    fn with_name_and_kind(self, name: Option<&str>, kind: Option<&str>) -> Self {
1139        self.init_object_id();
1140        crate::annotations::record_name_for_object(self.state.borrow().id.unwrap(), name, kind);
1141        self
1142    }
1143}
1144
1145impl crate::annotations::WithName for BatchSemaphore {
1146    fn with_name_and_kind(self, name: Option<&str>, kind: Option<&str>) -> Self {
1147        (&self).with_name_and_kind(name, kind);
1148        self
1149    }
1150}
1151
1152impl BatchSemaphore {
1153    /// Returns a reference to this semaphore's resource signature.
1154    pub fn signature(&self) -> &ResourceSignature {
1155        &self.signature
1156    }
1157}