Skip to main content

nuclei/sync/
contend.rs

1//! The acquire every lock-like primitive shares: take what is free at
2//! once, otherwise spin briefly, then wait in the queue with a bounded
3//! bypass.
4//!
5//! A caller takes what it asks for whenever it is free and no handoff is
6//! owed, even past queued waiters: the thread that has just released a
7//! lock and asks again takes it straight back, with no context switch.
8//! When that one attempt fails, the caller spins on the state word for up
9//! to the lock's budget (see `spin`), taking what is freed meanwhile, and
10//! stops at once when a handoff is owed or waiters sit queued with nobody
11//! changing the queue; then it queues and returns `Pending`. A release
12//! wakes the front waiter, whatever it asks for, and the waiters behind it
13//! the free permits could serve; a woken waiter tries again when polled.
14//! Each time it cannot take its whole request it counts one bypass, and
15//! after a loss it spins on its own node before it sleeps, so a release
16//! that comes meanwhile marks it without a wake. The attempt that would be
17//! its [`MAX_BYPASS`]th loss is owed a handoff in the same
18//! compare-and-swap that finds it short, so barging stops before anything
19//! more is freed, and released permits gather for it until its request is
20//! met. No clock is read: fairness is counted, not timed, and a spin is
21//! counted in rounds.
22//!
23//! Why the spin looks seldom and stops behind a settled queue, measured
24//! with `benches/sync.rs`: a spin that loaded the word on every
25//! round pulled the line from the holder on every round, and two tasks ran
26//! at under half the throughput they reach with loads spaced by doubling
27//! gaps; a spin that went on past queued waiters doubled two tasks' gain
28//! again but halved eight and thirty-two tasks' throughput, where every
29//! newcomer then spun on the shared line instead of waiting on its own
30//! node. Queue-at-once (the design before) measured within 6% of a short
31//! spin at eight and thirty-two tasks, and two tasks ran at 40% of
32//! `std::sync::Mutex` with it.
33
34use core::task::{Poll, Waker};
35
36use super::internal::Ordering;
37use super::queue::{HANDOFF, Policy, stops_spin};
38use super::spin::Gaps;
39use super::waiter::{Cancel, Park, Wait};
40
41/// How many times a waiter can be passed over before it is owed a
42/// handoff: a woken waiter that loses its retry this many times is handed
43/// the next release directly, and nobody barges past it meanwhile.
44#[cfg(not(all(loom, test)))]
45pub const MAX_BYPASS: u32 = 16;
46/// Small under loom, so a model with a handful of operations reaches the
47/// handoff.
48#[cfg(all(loom, test))]
49pub const MAX_BYPASS: u32 = 2;
50
51/// The payload a drain pass leaves on a waiter it granted because the
52/// primitive closed: the grant carries nothing to give back, and the
53/// waiter's wait ends in [`Closed`]. No request, owner name or kind ever
54/// reaches this value.
55pub(super) const CLOSED_GRANT: usize = usize::MAX;
56
57/// How a wait on a primitive that can close ended without taking anything.
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub(super) struct Closed;
60
61/// A primitive whose waits take something and give it back.
62pub(super) trait Lock: Policy {
63    /// Whether a contended acquire spins before it queues, and after a
64    /// lost retry before it sleeps (see `spin`). A primitive whose holders
65    /// keep it across long waits of their own opts out.
66    const SPINS: bool = true;
67
68    /// `state` with `need` taken, when `state` has room for it; the owed
69    /// bit aside.
70    fn admit(&self, state: usize, need: usize) -> Option<usize>;
71
72    /// Gives back what a waiter held, handing it on.
73    fn give_back(&self, need: usize);
74
75    /// Whether a failed attempt that saw `state` can never succeed, because
76    /// the primitive closed: the contender then ends in [`Closed`] without
77    /// queueing. A primitive that never closes keeps the default.
78    fn closed(&self, _state: usize) -> bool {
79        false
80    }
81
82    /// One attempt to take `need` the way a fresh caller would: whenever it
83    /// is free, but never while a handoff is owed. On failure, the state it
84    /// saw.
85    fn try_take(&self, need: usize) -> Result<(), usize> {
86        take(self, need, false)
87    }
88
89    /// Takes `need` for a handoff, which may pass the owed bit because it
90    /// serves the queue.
91    fn take_for_waiter(&self, need: usize) -> bool {
92        take(self, need, true).is_ok()
93    }
94
95    /// A queued waiter's attempt that would be its last counted loss: it
96    /// takes `need` as [`try_take`](Self::try_take) would, or in the same
97    /// compare-and-swap is owed a handoff (see `Policy::owe_or_take`).
98    fn take_or_owe(&self, wait: &Wait, need: usize) -> Result<(), usize> {
99        self.owe_or_take(wait, |state| self.admit(state, need))
100    }
101}
102
103/// One compare-and-swap loop taking `need` through [`Lock::admit`]; past
104/// an owed handoff only for the drain that hands it out.
105pub(super) fn take<L: Lock + ?Sized>(lock: &L, need: usize, for_waiter: bool) -> Result<(), usize> {
106    let failure = if for_waiter {
107        Ordering::Acquire
108    } else {
109        Ordering::Relaxed
110    };
111    take_from(lock, need, for_waiter, lock.queue().state.load(failure))
112}
113
114/// [`take`] from a state the caller has just read, saving the load: the
115/// first compare-and-swap expects `current`, and a stale value only fails it
116/// once.
117pub(super) fn take_from<L: Lock + ?Sized>(
118    lock: &L,
119    need: usize,
120    for_waiter: bool,
121    mut current: usize,
122) -> Result<(), usize> {
123    let (success, failure) = if for_waiter {
124        (Ordering::AcqRel, Ordering::Acquire)
125    } else {
126        (Ordering::Acquire, Ordering::Relaxed)
127    };
128    let state = &lock.queue().state;
129    loop {
130        let next = match lock.admit(current, need) {
131            Some(next) if for_waiter || current & HANDOFF == 0 => next,
132            _ => return Err(current),
133        };
134        match state.compare_exchange_weak(current, next, success, failure) {
135            Ok(_) => return Ok(()),
136            Err(actual) => current = actual,
137        }
138    }
139}
140
141/// The spin before queueing: read the state word in gaps that double from
142/// one pause (see `spin::Gaps`), for up to the lock's budget, and try the
143/// compare-and-swap only when a read shows room. `seen` is the state the
144/// caller's failed attempt saw. Stops at once when a handoff is owed, or
145/// when waiters are queued and nobody holds the drain role
146/// (`queue::stops_spin`): the caller then queues behind them, and the
147/// budget is left as it was. A spin that runs out without ever seeing room
148/// halves the budget; one that saw room but lost every swap keeps it. The
149/// swap expects exactly the state it read, so it takes nothing a fresh
150/// caller's attempt would not.
151fn spin_take<L: Lock>(lock: &L, need: usize, mut seen: usize) -> bool {
152    let queue = lock.queue();
153    let rounds = queue.spin.rounds();
154    if rounds == 0 {
155        return false;
156    }
157    let mut gaps = Gaps::new(rounds);
158    let mut freed = false;
159    while gaps.wait() {
160        if stops_spin(seen) {
161            return false;
162        }
163        seen = queue.state.load(Ordering::Relaxed);
164        if !stops_spin(seen)
165            && let Some(next) = lock.admit(seen, need)
166        {
167            freed = true;
168            match queue
169                .state
170                .compare_exchange(seen, next, Ordering::Acquire, Ordering::Relaxed)
171            {
172                Ok(_) => {
173                    queue.spin.won(rounds);
174                    return true;
175                }
176                Err(actual) => seen = actual,
177            }
178        }
179    }
180    if !freed {
181        queue.spin.ran_out(rounds);
182    }
183    false
184}
185
186/// A first poll took `need` without waiting: it spends one operation of the
187/// poll's budget (see `coop`), or, with the budget spent, gives `need` back
188/// and yields. A grant to a queued waiter spends nothing: that wait yielded,
189/// and giving the grant back would cost it its place.
190#[inline]
191pub(super) fn taken_at_once<L: Lock + ?Sized>(
192    lock: &L,
193    need: usize,
194    waker: &Waker,
195) -> Poll<Result<(), Closed>> {
196    admitted(lock, need, crate::coop::spend(waker))
197}
198
199/// What a first attempt that took `need` comes to once the budget answered:
200/// complete when `admit`, else `need` goes back (serving whoever queued for it
201/// meanwhile) and the caller yields.
202#[inline]
203pub(super) fn admitted<L: Lock + ?Sized>(
204    lock: &L,
205    need: usize,
206    admit: bool,
207) -> Poll<Result<(), Closed>> {
208    if admit {
209        Poll::Ready(Ok(()))
210    } else {
211        lock.give_back(need);
212        Poll::Pending
213    }
214}
215
216/// A future's contended acquire.
217pub(super) struct Contend {
218    wait: Wait,
219    bypass: u32,
220}
221
222impl Contend {
223    pub(super) const fn new() -> Self {
224        Self {
225            wait: Wait::new(),
226            bypass: 0,
227        }
228    }
229
230    #[inline]
231    pub(super) fn is_queued(&self) -> bool {
232        self.wait.is_queued()
233    }
234
235    /// Ready once `need` is taken, by the caller's own attempt or by a
236    /// handoff. For a primitive that never closes.
237    #[inline]
238    pub(super) fn poll<L: Lock>(&mut self, lock: &L, need: usize, waker: &Waker) -> Poll<()> {
239        self.poll_from(lock, need, waker, None)
240    }
241
242    /// [`poll`](Self::poll) for a caller that has just read the state word
243    /// itself and found it `seen` (a Mutex's inline attempt): a first poll
244    /// starts its attempt from that state instead of loading it again.
245    #[inline]
246    pub(super) fn poll_from<L: Lock>(
247        &mut self,
248        lock: &L,
249        need: usize,
250        waker: &Waker,
251        seen: Option<usize>,
252    ) -> Poll<()> {
253        self.poll_closable_from(lock, need, waker, seen)
254            .map(|taken| debug_assert!(taken.is_ok(), "a lock that never closes was closed"))
255    }
256
257    /// [`poll`](Self::poll) for a primitive that can close: ready with
258    /// [`Closed`] when the primitive closed before the wait took anything.
259    pub(super) fn poll_closable<L: Lock>(
260        &mut self,
261        lock: &L,
262        need: usize,
263        waker: &Waker,
264    ) -> Poll<Result<(), Closed>> {
265        self.poll_closable_from(lock, need, waker, None)
266    }
267
268    fn poll_closable_from<L: Lock>(
269        &mut self,
270        lock: &L,
271        need: usize,
272        waker: &Waker,
273        seen: Option<usize>,
274    ) -> Poll<Result<(), Closed>> {
275        let queue = lock.queue();
276        let mut park = if self.wait.is_queued() {
277            self.wait.park(queue, Some(waker))
278        } else {
279            // Losing this first attempt is not a bypass: nobody has passed
280            // the caller yet, the holder was simply there first. Nor is
281            // losing the spin that follows it, before the caller queues.
282            let first = match seen {
283                Some(seen) => take_from(lock, need, false, seen),
284                None => lock.try_take(need),
285            };
286            match first {
287                Ok(()) => return taken_at_once(lock, need, waker),
288                Err(seen) => {
289                    if lock.closed(seen) {
290                        return if crate::coop::spend(waker) {
291                            Poll::Ready(Err(Closed))
292                        } else {
293                            Poll::Pending
294                        };
295                    }
296                    if L::SPINS && spin_take(lock, need, seen) {
297                        return taken_at_once(lock, need, waker);
298                    }
299                }
300            }
301            lock.enqueue(&mut self.wait, need, waker);
302            self.wait.park(queue, None)
303        };
304        loop {
305            match park {
306                Park::Granted(payload) => {
307                    // A nudge may have been pending when the grant came.
308                    queue.consume_nudge();
309                    return Poll::Ready(if payload == CLOSED_GRANT {
310                        Err(Closed)
311                    } else {
312                        Ok(())
313                    });
314                }
315                Park::Pending => return Poll::Pending,
316                // A nudge means something was freed after this waiter
317                // queued, so losing the retry is a bypass even in the poll
318                // that queued it: the nudge may come from this waiter's own
319                // drain, and what others took during that drain has to be
320                // counted like any other loss.
321                Park::Nudged => {
322                    queue.consume_nudge();
323                    let attempt = if self.bypass + 1 == MAX_BYPASS {
324                        lock.take_or_owe(&self.wait, need)
325                    } else {
326                        lock.try_take(need)
327                    };
328                    match attempt {
329                        Ok(()) => {
330                            // A retry that wins after a wake says holds can
331                            // end soon after a release: a lock that stopped
332                            // spinning tries again.
333                            if L::SPINS && queue.spin.rounds() == 0 {
334                                queue.spin.won(0);
335                            }
336                            self.cancel(lock, need);
337                            return Poll::Ready(Ok(()));
338                        }
339                        // Never past the bound: a waiter that is owed, was
340                        // granted, and retries on a nudge left over from
341                        // before loses once more to the permits taken for
342                        // itself, which is no bypass.
343                        Err(seen) if seen & HANDOFF == 0 => {
344                            self.bypass = (self.bypass + 1).min(MAX_BYPASS);
345                        }
346                        Err(_) => {}
347                    }
348                    // A primitive that closed meanwhile grants every waiter
349                    // [`CLOSED_GRANT`], so the look below finds it, or the
350                    // wake that comes with it does.
351                    park = if L::SPINS {
352                        self.wait.spin_park(queue, waker)
353                    } else {
354                        self.wait.park(queue, Some(waker))
355                    };
356                }
357            }
358        }
359    }
360
361    /// Leaves the queue: the future was dropped, or took what it waits
362    /// for on its own. What a handoff gave it meanwhile is passed on.
363    pub(super) fn cancel<L: Lock>(&mut self, lock: &L, need: usize) {
364        match self.wait.cancel(lock.queue()) {
365            Cancel::Idle => {}
366            Cancel::Withdrawn(woken) => lock.withdrawn(woken),
367            Cancel::Granted(payload) => {
368                lock.queue().consume_nudge();
369                // A grant made because the primitive closed took nothing.
370                if payload != CLOSED_GRANT {
371                    lock.give_back(need);
372                }
373            }
374        }
375    }
376}
377
378#[cfg(all(test, not(loom)))]
379mod tests {
380    use core::cell::Cell;
381
382    use super::super::queue::{Arrivals, WaitQueue};
383    use super::super::raw_semaphore::RawSemaphore;
384    use super::super::test_support::Polled;
385    use super::super::waiter::WakeList;
386    use super::*;
387
388    /// A semaphore whose waiter runs `after_loss` right after an attempt
389    /// of its fails, before its poll goes on: what another thread could do
390    /// between the two.
391    struct Gap<'a> {
392        sem: &'a RawSemaphore,
393        after_loss: Cell<Option<Box<dyn FnOnce() + 'a>>>,
394    }
395
396    impl Gap<'_> {
397        fn lost(&self, attempt: Result<(), usize>) -> Result<(), usize> {
398            if attempt.is_err()
399                && let Some(between) = self.after_loss.take()
400            {
401                between();
402            }
403            attempt
404        }
405    }
406
407    impl Policy for Gap<'_> {
408        fn queue(&self) -> &WaitQueue {
409            self.sem.queue()
410        }
411
412        fn pass(&self, arrivals: Arrivals, woken: &mut WakeList<'_>, wake_front: bool) -> usize {
413            self.sem.pass(arrivals, woken, wake_front)
414        }
415    }
416
417    impl Lock for Gap<'_> {
418        fn admit(&self, state: usize, need: usize) -> Option<usize> {
419            self.sem.admit(state, need)
420        }
421
422        fn give_back(&self, need: usize) {
423            self.sem.give_back(need);
424        }
425
426        fn try_take(&self, need: usize) -> Result<(), usize> {
427            self.lost(self.sem.try_take(need))
428        }
429
430        fn take_or_owe(&self, wait: &Wait, need: usize) -> Result<(), usize> {
431            self.lost(self.sem.take_or_owe(wait, need))
432        }
433    }
434
435    #[test]
436    fn nothing_barges_between_a_waiters_last_loss_and_its_handoff() {
437        let sem = RawSemaphore::new(1);
438        let barged = Cell::new(false);
439        let gap = Gap {
440            sem: &sem,
441            after_loss: Cell::new(None),
442        };
443        assert!(sem.try_acquire(1));
444        let mut contend = Contend::new();
445        let mut waiter = Polled::new(core::future::poll_fn(|cx| {
446            contend.poll(&gap, 1, cx.waker())
447        }));
448        waiter.pending();
449        for round in 1..=MAX_BYPASS {
450            sem.release(1);
451            assert_eq!(waiter.wakes() as u32, round, "a release nudges it");
452            assert!(sem.try_acquire(1), "a free permit is taken past it");
453            if round < MAX_BYPASS {
454                waiter.pending();
455            }
456        }
457        // Its last loss: the holder lets go and someone asks at once,
458        // before the waiter's poll returns.
459        gap.after_loss.set(Some(Box::new(|| {
460            sem.release(1);
461            barged.set(sem.try_acquire(1));
462        })));
463        let served = waiter.poll().is_ready();
464        assert!(
465            !barged.get(),
466            "a barger took the permit freed after the waiter's last counted loss"
467        );
468        assert!(served, "the freed permit is handed to the waiter");
469        assert!(!sem.try_acquire(1), "the waiter holds it");
470        sem.release(1);
471        assert!(sem.try_acquire(1), "barging resumes once it is served");
472    }
473
474    /// A semaphore raced by other threads at chosen instants of one
475    /// waiter's poll: as each pass of a drain that waiter runs begins and
476    /// ends (each is told the pass's number, from 1), and just before its
477    /// next attempt of each kind.
478    struct Raced<'a> {
479        sem: &'a RawSemaphore,
480        passes: Cell<u32>,
481        pass_begins: Box<dyn Fn(u32) + 'a>,
482        pass_ends: Box<dyn Fn(u32) + 'a>,
483        before_try: Cell<Option<Box<dyn FnOnce() + 'a>>>,
484        before_owe: Cell<Option<Box<dyn FnOnce() + 'a>>>,
485    }
486
487    impl<'a> Raced<'a> {
488        fn new(
489            sem: &'a RawSemaphore,
490            pass_begins: impl Fn(u32) + 'a,
491            pass_ends: impl Fn(u32) + 'a,
492        ) -> Self {
493            Self {
494                sem,
495                passes: Cell::new(0),
496                pass_begins: Box::new(pass_begins),
497                pass_ends: Box::new(pass_ends),
498                before_try: Cell::new(None),
499                before_owe: Cell::new(None),
500            }
501        }
502    }
503
504    impl Policy for Raced<'_> {
505        fn queue(&self) -> &WaitQueue {
506            self.sem.queue()
507        }
508
509        fn pass(&self, arrivals: Arrivals, woken: &mut WakeList<'_>, wake_front: bool) -> usize {
510            let pass = self.passes.get() + 1;
511            self.passes.set(pass);
512            (self.pass_begins)(pass);
513            let clear = self.sem.pass(arrivals, woken, wake_front);
514            (self.pass_ends)(pass);
515            clear
516        }
517    }
518
519    impl Lock for Raced<'_> {
520        fn admit(&self, state: usize, need: usize) -> Option<usize> {
521            self.sem.admit(state, need)
522        }
523
524        fn give_back(&self, need: usize) {
525            self.sem.give_back(need);
526        }
527
528        fn try_take(&self, need: usize) -> Result<(), usize> {
529            if let Some(first) = self.before_try.take() {
530                first();
531            }
532            self.sem.try_take(need)
533        }
534
535        fn take_or_owe(&self, wait: &Wait, need: usize) -> Result<(), usize> {
536            if let Some(first) = self.before_owe.take() {
537                first();
538            }
539            self.sem.take_or_owe(wait, need)
540        }
541    }
542
543    /// A waiter that queues takes the drain role in the same poll.
544    /// The holder lets go as its first pass begins, and from then on
545    /// another caller takes the permit and gives it back after every pass,
546    /// before the drain can give its role up. The drain must still end,
547    /// and the waiter must get its turn.
548    #[test]
549    fn a_waiter_serving_the_queue_is_not_passed_over_without_bound() {
550        let sem = RawSemaphore::new(1);
551        let barged = Cell::new(0u32);
552        let rounds = 4 * MAX_BYPASS;
553        let raced = Raced::new(
554            &sem,
555            |pass| {
556                if pass == 1 {
557                    sem.release(1);
558                }
559            },
560            |pass| {
561                if pass <= rounds && sem.try_acquire(1) {
562                    barged.set(barged.get() + 1);
563                    sem.release(1);
564                }
565            },
566        );
567        assert!(sem.try_acquire(1));
568        let mut contend = Contend::new();
569        let mut waiter = Polled::new(core::future::poll_fn(|cx| {
570            contend.poll(&raced, 1, cx.waker())
571        }));
572        let served = waiter.poll().is_ready();
573        assert!(
574            barged.get() <= MAX_BYPASS,
575            "{} callers took the permit past a queued waiter in {} passes of its own drain, \
576             and it never got to retry (the bound is {MAX_BYPASS})",
577            barged.get(),
578            raced.passes.get(),
579        );
580        assert!(
581            raced.passes.get() <= 3,
582            "the waiter's drain ran {} passes: releases that come while its nudge is out \
583             must not ask for more",
584            raced.passes.get(),
585        );
586        assert!(
587            served,
588            "the permit the last caller gave back is the waiter's"
589        );
590        assert!(!sem.try_acquire(1), "the waiter holds it");
591    }
592
593    /// A nudge in the poll that queues a waiter means a permit was freed
594    /// after it queued. Losing the retry to a caller that took that permit
595    /// is a bypass like any other, also when the nudge and the loss both
596    /// happen inside the waiter's own drain.
597    #[test]
598    fn a_retry_lost_in_the_poll_that_queues_counts_as_a_bypass() {
599        let sem = RawSemaphore::new(1);
600        let raced = Raced::new(
601            &sem,
602            |pass| {
603                if pass == 1 {
604                    sem.release(1);
605                }
606            },
607            |pass| {
608                if pass == 1 {
609                    assert!(
610                        sem.try_acquire(1),
611                        "the freed permit is taken past the waiter"
612                    );
613                }
614            },
615        );
616        assert!(sem.try_acquire(1));
617        let mut contend = Contend::new();
618        let mut waiter = Polled::new(core::future::poll_fn(|cx| {
619            contend.poll(&raced, 1, cx.waker())
620        }));
621        waiter.pending();
622        drop(waiter);
623        assert_eq!(
624            contend.bypass, 1,
625            "the loss in the queueing poll is counted"
626        );
627        contend.cancel(&raced, 1);
628        sem.release(1);
629        assert!(sem.try_acquire(1), "nothing is left held or owed");
630    }
631
632    /// A waiter that raised the owed bit, was handed the permit, and then
633    /// retries on a nudge left over from before the handoff finds nothing
634    /// free: the permit was taken for the waiter itself. That is not a
635    /// bypass, and its count stays at the bound.
636    #[test]
637    fn a_loss_to_its_own_handoff_does_not_count_past_the_bound() {
638        let sem = RawSemaphore::new(1);
639        let raced = Raced::new(&sem, |_| {}, |_| {});
640        assert!(sem.try_acquire(1));
641        let mut contend = Contend::new();
642        let mut waiter = Polled::new(core::future::poll_fn(|cx| {
643            contend.poll(&raced, 1, cx.waker())
644        }));
645        waiter.pending();
646        // One loss short of the bound.
647        for _ in 1..MAX_BYPASS {
648            sem.release(1);
649            assert!(sem.try_acquire(1), "a free permit is taken past it");
650            waiter.pending();
651        }
652        sem.release(1);
653        assert!(sem.try_acquire(1), "a free permit is taken past it");
654        // Between the waiter's look at its node and its last counted
655        // attempt, a release nudges it again and the permit is taken again:
656        // the nudge stays on the node, unused.
657        raced.before_owe.set(Some(Box::new(|| {
658            sem.release(1);
659            assert!(sem.try_acquire(1), "a free permit is taken past it");
660        })));
661        // It then retries on that nudge. Just before it does, the holder
662        // lets go: the pass hands the permit to the owed waiter.
663        raced.before_try.set(Some(Box::new(|| sem.release(1))));
664        assert!(waiter.poll().is_ready(), "the handoff reached the waiter");
665        drop(waiter);
666        assert_eq!(
667            contend.bypass, MAX_BYPASS,
668            "a loss to the permit taken for the waiter itself was counted"
669        );
670        assert!(!sem.try_acquire(1), "the waiter holds it");
671    }
672
673    /// A thread that holds the drain role with nobody queued returns after
674    /// its pass, however many releases other threads make meanwhile: a
675    /// release that finds the role taken leaves only its request to wake
676    /// the front, and with no front that request is dropped.
677    #[test]
678    fn a_drain_that_found_nobody_queued_does_not_pass_again_for_releases() {
679        let sem = RawSemaphore::new(2);
680        let hammering = Cell::new(false);
681        let rounds = 4 * MAX_BYPASS;
682        let raced = Raced::new(
683            &sem,
684            |_| {},
685            |pass| {
686                if hammering.get() && pass <= rounds {
687                    // Another holder lets its permit go and takes it back,
688                    // before the drain can give its role up.
689                    sem.release(1);
690                    assert!(sem.try_acquire(1), "the permit is free again");
691                }
692            },
693        );
694        assert!(sem.try_acquire(1), "the other holder's permit");
695        let mut contend = Contend::new();
696        let mut waiter = Polled::new(core::future::poll_fn(|cx| {
697            contend.poll(&raced, 2, cx.waker())
698        }));
699        waiter.pending();
700        drop(waiter);
701        // The waiter gives up. Its withdrawal takes the drain role, and the
702        // pass finds nobody queued while the other holder hammers.
703        let before = raced.passes.get();
704        hammering.set(true);
705        contend.cancel(&raced, 2);
706        hammering.set(false);
707        assert_eq!(
708            raced.passes.get() - before,
709            1,
710            "the drain passed again for releases that had nobody to wake"
711        );
712        // Nothing is left behind: a waiter that queues now is not nudged for
713        // a release that came before it, so it counts nothing. And the next
714        // release reaches it.
715        let mut next = Contend::new();
716        let mut waiter = Polled::new(core::future::poll_fn(|cx| next.poll(&raced, 2, cx.waker())));
717        waiter.pending();
718        sem.release(1);
719        assert_eq!(waiter.wakes(), 1, "the release nudges the waiter");
720        assert!(waiter.poll().is_ready(), "both permits are its");
721        drop(waiter);
722        assert_eq!(
723            next.bypass, 0,
724            "it retried on a request from before it queued"
725        );
726        sem.release(2);
727        assert_eq!(sem.available(), 2, "a permit was lost or made up");
728    }
729
730    /// A release that comes while a pass runs must still reach the waiter
731    /// that pass found, though it leaves no dirty bit: the drain passes
732    /// again for its request.
733    #[test]
734    fn a_release_during_a_pass_that_found_a_waiter_reaches_it() {
735        let sem = RawSemaphore::new(1);
736        let raced = Raced::new(
737            &sem,
738            |_| {},
739            |pass| {
740                if pass == 1 {
741                    sem.release(1);
742                }
743            },
744        );
745        assert!(sem.try_acquire(1));
746        let mut contend = Contend::new();
747        let mut waiter = Polled::new(core::future::poll_fn(|cx| {
748            contend.poll(&raced, 1, cx.waker())
749        }));
750        // The waiter queues and serves the queue; the holder lets go right
751        // after its first pass read that nothing was free.
752        assert!(
753            waiter.poll().is_ready(),
754            "a release that found the drain role taken was lost"
755        );
756        assert!(!sem.try_acquire(1), "the waiter holds it");
757    }
758
759    /// A semaphore watched by a contender: `looks` counts the times the
760    /// contender judged a state word (each first attempt, retry and spin
761    /// round does), and `at_look` runs, just after that judgement, what
762    /// another thread does at that instant.
763    struct Watched<'a> {
764        sem: &'a RawSemaphore,
765        looks: Cell<u32>,
766        at_look: Box<dyn Fn(u32) + 'a>,
767    }
768
769    impl<'a> Watched<'a> {
770        fn new(sem: &'a RawSemaphore, at_look: impl Fn(u32) + 'a) -> Self {
771            Self {
772                sem,
773                looks: Cell::new(0),
774                at_look: Box::new(at_look),
775            }
776        }
777    }
778
779    impl Policy for Watched<'_> {
780        fn queue(&self) -> &WaitQueue {
781            self.sem.queue()
782        }
783
784        fn pass(&self, arrivals: Arrivals, woken: &mut WakeList<'_>, wake_front: bool) -> usize {
785            self.sem.pass(arrivals, woken, wake_front)
786        }
787    }
788
789    impl Lock for Watched<'_> {
790        fn admit(&self, state: usize, need: usize) -> Option<usize> {
791            let admitted = self.sem.admit(state, need);
792            let look = self.looks.get() + 1;
793            self.looks.set(look);
794            (self.at_look)(look);
795            admitted
796        }
797
798        fn give_back(&self, need: usize) {
799            self.sem.give_back(need);
800        }
801    }
802
803    /// B, the spin before queueing. The holder lets go while a contender
804    /// spins (just after the contender's second look): the contender takes
805    /// the lock in its first poll, never queues, is never woken, and the
806    /// lock's budget doubles.
807    #[test]
808    fn a_contender_takes_a_lock_let_go_during_its_spin_without_queueing() {
809        let sem = RawSemaphore::new(1);
810        assert!(sem.try_acquire(1), "the holder takes it");
811        let budget = sem.queue().spin.rounds();
812        assert!(budget > 0, "a fresh lock spins");
813        let watched = Watched::new(&sem, |look| {
814            if look == 2 {
815                sem.release(1);
816            }
817        });
818        let mut contend = Contend::new();
819        let mut contender = Polled::new(core::future::poll_fn(|cx| {
820            contend.poll(&watched, 1, cx.waker())
821        }));
822        assert!(contender.poll().is_ready(), "the spin took the freed lock");
823        assert_eq!(contender.wakes(), 0, "a spinner needs no wake");
824        drop(contender);
825        assert!(!contend.is_queued(), "the contender never queued");
826        assert!(
827            watched.looks.get() >= 3,
828            "it looked again after the release"
829        );
830        assert_eq!(
831            sem.queue().spin.rounds(),
832            (2 * budget).min(u32::from(super::super::spin::MAX)),
833            "a won spin doubles the budget"
834        );
835        assert!(!sem.try_acquire(1), "the contender holds it");
836    }
837
838    /// B. A spin that never sees room runs out, halves the budget, and the
839    /// contender queues and is served by the release as before.
840    #[test]
841    fn a_spin_that_never_sees_room_halves_the_budget_and_queues() {
842        let sem = RawSemaphore::new(1);
843        assert!(sem.try_acquire(1));
844        let budget = sem.queue().spin.rounds();
845        let watched = Watched::new(&sem, |_| {});
846        let mut contend = Contend::new();
847        let mut waiter = Polled::new(core::future::poll_fn(|cx| {
848            contend.poll(&watched, 1, cx.waker())
849        }));
850        waiter.pending();
851        assert!(watched.looks.get() > 1, "it spun before queueing");
852        assert_eq!(
853            sem.queue().spin.rounds(),
854            budget / 2,
855            "a spin that ran out halves"
856        );
857        sem.release(1);
858        assert_eq!(waiter.wakes(), 1, "the release reaches the queued waiter");
859        assert!(waiter.poll().is_ready());
860    }
861
862    /// B. A spin that saw the lock free but lost every swap to a barger keeps
863    /// the budget: the holders are short, the lock is only busy.
864    #[test]
865    fn a_spin_that_saw_room_but_lost_keeps_the_budget() {
866        let sem = RawSemaphore::new(1);
867        assert!(sem.try_acquire(1));
868        let budget = sem.queue().spin.rounds();
869        let watched = Watched::new(&sem, |look| match look {
870            // The holder lets go just after the contender's second look ...
871            2 => sem.release(1),
872            // ... and a barger takes the lock right after the contender saw
873            // it free, before the contender's swap.
874            3 => assert!(sem.try_acquire(1), "the barger takes it"),
875            _ => {}
876        });
877        let mut contend = Contend::new();
878        let mut waiter = Polled::new(core::future::poll_fn(|cx| {
879            contend.poll(&watched, 1, cx.waker())
880        }));
881        waiter.pending();
882        assert_eq!(
883            sem.queue().spin.rounds(),
884            budget,
885            "a busy lock keeps its budget"
886        );
887        drop(waiter);
888        contend.cancel(&watched, 1);
889        sem.release(1);
890    }
891
892    /// B. The spin stops at once behind a settled queue (waiters queued and
893    /// nobody changing the queue): the contender looks once and queues, and
894    /// the budget is left alone. The owed handoff, the spin's other stop, is
895    /// pinned for every state word by
896    /// `the_spin_takes_only_what_a_fresh_caller_may`.
897    #[test]
898    fn a_contender_queues_at_once_behind_a_settled_queue() {
899        let sem = RawSemaphore::new(1);
900        assert!(sem.try_acquire(1));
901        let budget = sem.queue().spin.rounds();
902        let plain = Watched::new(&sem, |_| {});
903        let mut first = Contend::new();
904        let mut first_waiter = Polled::new(core::future::poll_fn(|cx| {
905            first.poll(&plain, 1, cx.waker())
906        }));
907        first_waiter.pending();
908        let after_first = sem.queue().spin.rounds();
909        let watched = Watched::new(&sem, |_| {});
910        let mut second = Contend::new();
911        let mut second_waiter = Polled::new(core::future::poll_fn(|cx| {
912            second.poll(&watched, 1, cx.waker())
913        }));
914        second_waiter.pending();
915        assert_eq!(watched.looks.get(), 1, "it looked once and did not spin");
916        assert_eq!(
917            sem.queue().spin.rounds(),
918            after_first,
919            "the budget is untouched"
920        );
921        assert!(after_first <= budget);
922        drop((first_waiter, second_waiter));
923        first.cancel(&plain, 1);
924        second.cancel(&watched, 1);
925        sem.release(1);
926        assert!(sem.try_acquire(1), "nothing is left held or owed");
927    }
928
929    /// B, the spin's rule for every state word with up to two permits and
930    /// every combination of the semaphore's low bits (the queue's six and the
931    /// closed bit): one round takes a permit exactly when a fresh caller may
932    /// (a permit free, no handoff owed, not closed) and the queue is not
933    /// settled (waiters queued with nobody holding the drain role), and
934    /// leaves the word as that take would; otherwise it changes nothing. A
935    /// spin that ignored an owed handoff fails here at the word with one
936    /// permit and HANDOFF up.
937    #[test]
938    fn the_spin_takes_only_what_a_fresh_caller_may() {
939        use super::super::raw_semaphore::SHIFT;
940        const DRAINING_BIT: usize = 1;
941        const QUEUED_BIT: usize = 1 << 2;
942        const CLOSED_BIT: usize = 1 << super::super::queue::FIRST_BIT;
943        for permits in 0..=2usize {
944            for flags in 0..(1usize << SHIFT) {
945                let state = (permits << SHIFT) | flags;
946                let sem = RawSemaphore::new(0);
947                sem.queue().state.store(state, Ordering::Relaxed);
948                let took = spin_take(&sem, 1, state);
949                let settled = flags & (QUEUED_BIT | DRAINING_BIT) == QUEUED_BIT;
950                let may = permits >= 1 && flags & (HANDOFF | CLOSED_BIT) == 0 && !settled;
951                assert_eq!(took, may, "state {state:#x}: the spin took the wrong thing");
952                let after = sem.queue().state.load(Ordering::Relaxed);
953                let expected = if took { state - (1 << SHIFT) } else { state };
954                assert_eq!(
955                    after, expected,
956                    "state {state:#x}: the spin left the wrong word"
957                );
958            }
959        }
960    }
961
962    /// B. A queued waiter whose retry wins after a nudge brings a spent
963    /// budget back to one, so a lock whose holds turned short spins again.
964    #[test]
965    fn a_won_retry_revives_a_spent_budget() {
966        let sem = RawSemaphore::new(1);
967        let spin = &sem.queue().spin;
968        while spin.rounds() > 0 {
969            spin.ran_out(spin.rounds());
970        }
971        assert!(sem.try_acquire(1));
972        let mut contend = Contend::new();
973        let mut waiter = Polled::new(core::future::poll_fn(|cx| {
974            contend.poll(&sem, 1, cx.waker())
975        }));
976        waiter.pending();
977        assert_eq!(spin.rounds(), 0, "a spent budget queues at once");
978        sem.release(1);
979        assert!(waiter.poll().is_ready(), "the retry wins");
980        assert_eq!(spin.rounds(), 1, "the won retry revives the budget");
981    }
982}