Skip to main content

ax_task/sync/
wait_queue.rs

1//! Task-context wait queues built on the generation-checked park handshake.
2
3use alloc::{collections::VecDeque, sync::Arc};
4use core::{
5    sync::atomic::{AtomicU64, AtomicUsize, Ordering, fence},
6    time::Duration,
7};
8
9use crate::{
10    runtime::{
11        lock::{PreemptScope, PreemptTicketLock},
12        task_runtime,
13    },
14    thread::{
15        TaskError, ThreadId, ThreadWakeHandle, WaitWakeClaim, WaitWakeClaimState, WaitWakeDelivery,
16        WakeIntent,
17        current::{
18            CurrentParkStart, acquire_blocking_permit, park::begin_current_park_with_permit,
19        },
20    },
21    time::MonotonicDeadline,
22};
23
24/// Sleeps the calling scheduler thread for at least `duration`.
25#[track_caller]
26pub fn sleep(duration: Duration) {
27    sleep_until(task_runtime::monotonic_now().deadline_after(duration));
28}
29
30/// Sleeps until an absolute deadline measured against the monotonic clock.
31#[track_caller]
32pub fn sleep_until(deadline: MonotonicDeadline) {
33    let queue = WaitQueue::new();
34    while !task_runtime::monotonic_now().reached(deadline) {
35        queue
36            .wait_once(Some(deadline))
37            .expect("timed sleep must satisfy scheduler invariants");
38    }
39}
40
41/// A FIFO of scheduler threads that may sleep in ordinary task context.
42///
43/// This object intentionally has no hard-IRQ notification API. IRQ producers
44/// should wake one fixed service thread through [`crate::sync::irq::IrqWaitCell`], then let
45/// that thread fan out notifications here.
46#[derive(Debug)]
47pub struct WaitQueue {
48    waiters: PreemptTicketLock<VecDeque<Waiter>>,
49    notification_generation: AtomicU64,
50    active_wait_attempts: AtomicUsize,
51}
52
53/// An exact wake capability for one externally registered task waiter.
54///
55/// Composite wait sources use this token to place task waiters and non-task
56/// callbacks in one externally ordered exclusive queue. The token contains no
57/// general thread handle and is valid only for its park generation.
58#[derive(Clone, Debug)]
59pub struct WaitQueueWakeToken {
60    waiter: Arc<WaiterWake>,
61}
62
63/// Result of selecting one exact [`WaitQueueWakeToken`].
64#[derive(Clone, Copy, Debug, Eq, PartialEq)]
65pub enum WaitQueueWakeOutcome {
66    /// The selected park generation became runnable.
67    Delivered,
68    /// Delivery could not currently reach an online scheduler owner.
69    Retry,
70    /// The park generation was cancelled, completed, or exited.
71    Stale,
72}
73
74/// Result of publishing an exact waiter into a composite notification source.
75pub enum WaitQueueRegistration<G> {
76    /// The waiter is ordered in the source and the lease must remain alive.
77    Armed(G),
78    /// A notification crossed the predicate-to-registration window.
79    Retry(G),
80}
81
82impl WaitQueueWakeToken {
83    /// Selects one exact registered waiter with ordinary task-context intent.
84    pub fn notify(&self) -> WaitQueueWakeOutcome {
85        self.notify_with_intent(WakeIntent::Normal)
86    }
87
88    /// Selects one exact registered waiter with Linux `WF_SYNC` intent.
89    pub fn notify_sync(&self) -> WaitQueueWakeOutcome {
90        self.notify_with_intent(WakeIntent::Sync)
91    }
92
93    /// Returns whether this park generation can still accept a wake delivery.
94    pub fn is_active(&self) -> bool {
95        self.waiter.claim.is_active()
96    }
97
98    fn notify_with_intent(&self, intent: WakeIntent) -> WaitQueueWakeOutcome {
99        assert_task_context_notification();
100        let claim_owner = match self.waiter.try_select() {
101            WaiterSelection::Selected(claim_owner) => claim_owner,
102            WaiterSelection::Retry => return WaitQueueWakeOutcome::Retry,
103            WaiterSelection::Stale => return WaitQueueWakeOutcome::Stale,
104        };
105        // Scheduler delivery enters `ThreadSchedulerActivity`, which pins the
106        // waker CPU exactly across Linux's try_to_wake_up-style transaction.
107        // This externally owned waiter contains no CPU-local state before then.
108        let delivery = claim_owner
109            .wake
110            .deliver_wait_claim_from_task(&claim_owner.claim, intent);
111        match delivery {
112            WaitWakeDelivery::Delivered => {
113                debug_assert_eq!(self.waiter.deactivate(), WaiterRemoval::Delivered);
114                WaitQueueWakeOutcome::Delivered
115            }
116            WaitWakeDelivery::Cancelled | WaitWakeDelivery::Exited => {
117                self.waiter.deactivate();
118                WaitQueueWakeOutcome::Stale
119            }
120            WaitWakeDelivery::Unavailable => {
121                if claim_owner.requeue_after_unavailable() {
122                    WaitQueueWakeOutcome::Retry
123                } else {
124                    self.waiter.deactivate();
125                    WaitQueueWakeOutcome::Stale
126                }
127            }
128        }
129    }
130}
131
132impl WaitQueue {
133    /// Creates an empty wait queue suitable for static initialization.
134    pub const fn new() -> Self {
135        Self {
136            waiters: PreemptTicketLock::new(VecDeque::new()),
137            notification_generation: AtomicU64::new(0),
138            active_wait_attempts: AtomicUsize::new(0),
139        }
140    }
141
142    /// Blocks the current thread until one task-context notification selects it.
143    #[track_caller]
144    pub fn wait(&self) {
145        self.wait_once(None)
146            .expect("wait queue park must satisfy scheduler invariants");
147    }
148
149    /// Blocks until `condition` observes true.
150    ///
151    /// The predicate runs in ordinary task context without the internal waiter
152    /// lock. A producer must publish the state observed by `condition` before
153    /// notifying this queue. The notification generation closes the interval
154    /// between the predicate check and waiter insertion without calling
155    /// arbitrary code from a scheduler-sensitive critical section.
156    #[track_caller]
157    pub fn wait_until<F>(&self, condition: F)
158    where
159        F: Fn() -> bool,
160    {
161        self.try_wait_until(condition)
162            .expect("conditional wait must satisfy scheduler invariants");
163    }
164
165    /// Fallible form of [`Self::wait_until`] for runtime and OS glue.
166    ///
167    /// The predicate follows the same publish-before-notify contract as
168    /// [`Self::wait_until`].
169    ///
170    /// # Errors
171    ///
172    /// Returns [`TaskError::UnsafeContext`] in hard IRQ context and propagates
173    /// scheduler, timer-capacity, and runtime capability failures.
174    pub fn try_wait_until<F>(&self, condition: F) -> Result<(), TaskError>
175    where
176        F: Fn() -> bool,
177    {
178        loop {
179            if self.wait_once_if(None, &condition)? {
180                return Ok(());
181            }
182        }
183    }
184
185    /// Blocks until notification or the relative timeout elapses.
186    ///
187    /// Returns `true` only when the timer won removal from the queue. A racing
188    /// notification that already selected this waiter wins over the deadline.
189    #[track_caller]
190    pub fn wait_timeout(&self, timeout: Duration) -> bool {
191        let deadline = task_runtime::monotonic_now().deadline_after(timeout);
192        loop {
193            if task_runtime::monotonic_now().reached(deadline) {
194                return true;
195            }
196            let outcome = self
197                .wait_once(Some(deadline))
198                .expect("timed wait must satisfy scheduler invariants");
199            if outcome == WaitOutcome::Notified {
200                return false;
201            }
202            if task_runtime::monotonic_now().reached(deadline) {
203                return true;
204            }
205        }
206    }
207
208    /// Blocks until `condition` becomes true or the relative timeout elapses.
209    ///
210    /// Returns `true` for timeout and `false` when the condition wins.
211    #[track_caller]
212    pub fn wait_timeout_until<F>(&self, timeout: Duration, condition: F) -> bool
213    where
214        F: Fn() -> bool,
215    {
216        self.wait_until_deadline(
217            task_runtime::monotonic_now().deadline_after(timeout),
218            condition,
219        )
220    }
221
222    /// Blocks until `condition` becomes true or an absolute deadline elapses.
223    ///
224    /// `deadline` is measured against the runtime monotonic clock. Unlike a
225    /// relative timeout loop, this method never rebases the deadline after a
226    /// spurious wake, so repeated notifications cannot extend the wait.
227    /// Returns `true` for timeout and `false` when the condition wins.
228    #[track_caller]
229    pub fn wait_until_deadline<F>(&self, deadline: MonotonicDeadline, condition: F) -> bool
230    where
231        F: Fn() -> bool,
232    {
233        loop {
234            if task_runtime::monotonic_now().reached(deadline) {
235                return !condition();
236            }
237            let condition_met = self
238                .wait_once_if(Some(deadline), &condition)
239                .unwrap_or_else(|error| {
240                    panic!("timed conditional wait must satisfy scheduler invariants: {error:?}")
241                });
242            if condition_met {
243                return false;
244            }
245        }
246    }
247
248    /// Selects and wakes the oldest waiter from ordinary task context.
249    ///
250    /// # Panics
251    ///
252    /// Panics in hard IRQ context. IRQ producers must use
253    /// [`crate::sync::irq::IrqWaitCell`] to wake one fixed service thread.
254    pub fn notify_one(&self) -> bool {
255        self.notify_one_with_intent(WakeIntent::Normal)
256    }
257
258    /// Selects one waiter with Linux `WF_SYNC` scheduling intent.
259    ///
260    /// The selected waiter becomes runnable immediately. The hint only tells
261    /// Fair placement and wakeup preemption that this task-context producer
262    /// expects to block shortly after publishing the condition.
263    pub fn notify_one_sync(&self) -> bool {
264        self.notify_one_with_intent(WakeIntent::Sync)
265    }
266
267    fn notify_one_with_intent(&self, intent: WakeIntent) -> bool {
268        assert_task_context_notification();
269        if !self.may_have_active_wait_attempts() {
270            return false;
271        }
272        let _preempt = PreemptScope::enter();
273        self.notify_one_preempt_disabled(intent)
274    }
275
276    fn notify_one_preempt_disabled(&self, intent: WakeIntent) -> bool {
277        let (notification_generation, mut selected) = {
278            let mut waiters = self.waiters.lock();
279            let previous_generation = self
280                .notification_generation
281                .try_update(Ordering::Release, Ordering::Relaxed, |generation| {
282                    generation.checked_add(1)
283                })
284                .unwrap_or_else(|_| panic!("wait-queue notification generation exhausted"));
285            let notification_generation = previous_generation + 1;
286            let selected = select_waiter(&mut waiters, notification_generation);
287            (notification_generation, selected)
288        };
289        loop {
290            let Some(claim_owner) = selected else {
291                return false;
292            };
293
294            let delivery = claim_owner
295                .wake
296                .deliver_wait_claim_from_task(&claim_owner.claim, intent);
297            let mut waiters = self.waiters.lock();
298            let index = waiters
299                .iter()
300                .position(|waiter| waiter.owns_claim(&claim_owner));
301            match delivery {
302                WaitWakeDelivery::Delivered => {
303                    assert_eq!(
304                        claim_owner.claim.state(),
305                        WaitWakeClaimState::Delivered,
306                        "scheduler delivery must publish the claim before returning"
307                    );
308                    if let Some(index) = index {
309                        let waiter = waiters
310                            .remove(index)
311                            .expect("located delivered waiter must remain present");
312                        assert_eq!(waiter.wake.deactivate(), WaiterRemoval::Delivered);
313                    }
314                    return true;
315                }
316                WaitWakeDelivery::Cancelled | WaitWakeDelivery::Exited => {
317                    if let Some(index) = index {
318                        let waiter = waiters
319                            .remove(index)
320                            .expect("located stale waiter must remain present");
321                        let _ = waiter.wake.deactivate();
322                    }
323                }
324                WaitWakeDelivery::Unavailable => {
325                    if let Some(index) = index {
326                        waiters[index].requeue_after_unavailable();
327                    }
328                }
329            }
330            selected = select_waiter(&mut waiters, notification_generation);
331        }
332    }
333
334    /// Wakes every waiter.
335    ///
336    /// Each direct scheduler wake runs outside the queue's preemption-disabling
337    /// publication lock. A generation-bearing selection token serializes wake
338    /// completion against timeout cleanup.
339    pub fn notify_all(&self) {
340        assert_task_context_notification();
341        if !self.may_have_active_wait_attempts() {
342            return;
343        }
344        let _preempt = PreemptScope::enter();
345        while self.notify_one_preempt_disabled(WakeIntent::Normal) {}
346    }
347
348    fn wait_once(&self, deadline: Option<MonotonicDeadline>) -> Result<WaitOutcome, TaskError> {
349        self.wait_once_inner(deadline, None)
350    }
351
352    fn wait_once_if(
353        &self,
354        deadline: Option<MonotonicDeadline>,
355        condition: &dyn Fn() -> bool,
356    ) -> Result<bool, TaskError> {
357        match self.wait_once_inner(deadline, Some(condition))? {
358            WaitOutcome::Condition => Ok(true),
359            WaitOutcome::Notified | WaitOutcome::OtherWake => Ok(false),
360        }
361    }
362
363    fn wait_once_inner(
364        &self,
365        deadline: Option<MonotonicDeadline>,
366        condition: Option<&dyn Fn() -> bool>,
367    ) -> Result<WaitOutcome, TaskError> {
368        // Validate sleepability before taking the queue's non-sleeping
369        // publication lock. This permit cannot escape the park attempt.
370        let permit = acquire_blocking_permit()?;
371        let _active_attempt = ActiveWaitAttempt::begin(&self.active_wait_attempts);
372        let observed_generation = if let Some(condition) = condition {
373            let generation = self.notification_generation.load(Ordering::Acquire);
374            if condition() {
375                return Ok(WaitOutcome::Condition);
376            }
377            Some(generation)
378        } else {
379            None
380        };
381        let park = {
382            let mut waiters = self.waiters.lock();
383            if observed_generation.is_some_and(|generation| {
384                self.notification_generation.load(Ordering::Acquire) != generation
385            }) {
386                return Ok(WaitOutcome::OtherWake);
387            }
388            let mut park = match begin_current_park_with_permit(&permit)? {
389                CurrentParkStart::Notified => return Ok(WaitOutcome::OtherWake),
390                CurrentParkStart::Prepared(park) => park,
391            };
392            let thread = park.thread_id();
393            waiters.push_back(Waiter::new(thread, park.generation(), park.wake_handle()));
394            if let Some(deadline) = deadline
395                && let Err(error) = park.arm_deadline(deadline)
396            {
397                remove_waiter(&mut waiters, thread);
398                park.cancel()?;
399                return Err(error);
400            }
401            park
402        };
403        let thread = park.thread_id();
404
405        if let Err(error) = park.commit() {
406            remove_waiter(&mut self.waiters.lock(), thread);
407            return Err(error);
408        }
409        Ok(match remove_waiter(&mut self.waiters.lock(), thread) {
410            WaiterRemoval::OtherWake => WaitOutcome::OtherWake,
411            WaiterRemoval::Missing | WaiterRemoval::Delivered => WaitOutcome::Notified,
412        })
413    }
414
415    fn may_have_active_wait_attempts(&self) -> bool {
416        // This is the same store/full-barrier/load pairing used by Linux's
417        // wq_has_sleeper(). A producer publishes its condition before this
418        // fence. A waiter publishes the attempt through a SeqCst RMW before
419        // checking the condition. Therefore either this load observes the
420        // attempt and the notifier takes the queue lock, or the waiter observes
421        // the producer state before it may park.
422        fence(Ordering::SeqCst);
423        self.active_wait_attempts.load(Ordering::SeqCst) != 0
424    }
425}
426
427/// Blocks until `condition` is true while publishing one exact wake token.
428///
429/// `register` runs after the scheduler park exists, but before the park is
430/// `acquire` must return the external queue lock that protects `condition` and
431/// waiter publication. The lock is held while the scheduler park is prepared
432/// and `register` publishes the token, then released before the park commits.
433/// This is the Linux waitqueue order: a contending rtmutex can sleep while the
434/// caller is still `Running`, while the lock closes the predicate-to-enqueue
435/// race before `Parking` becomes visible.
436///
437/// The returned registration lease keeps the token in the caller's sole
438/// ordered source until this attempt resumes or is cancelled. No second
439/// internal task queue owns the same waiter.
440///
441/// Returns whether at least one registered token was selected while this call
442/// waited. Callers may use that result to continue Linux-style exclusive
443/// handoff when the condition remains consumable.
444#[track_caller]
445pub fn wait_until_registered<F, L, R, G, H>(condition: F, mut acquire: L, mut register: R) -> bool
446where
447    F: Fn() -> bool,
448    L: FnMut() -> G,
449    R: FnMut(&mut G, WaitQueueWakeToken) -> WaitQueueRegistration<H>,
450{
451    let mut selected = false;
452    loop {
453        match wait_once_registered(&condition, &mut acquire, &mut register)
454            .expect("registered conditional wait must satisfy scheduler invariants")
455        {
456            WaitOutcome::Condition => return selected,
457            WaitOutcome::Notified => selected = true,
458            WaitOutcome::OtherWake => {}
459        }
460    }
461}
462
463fn wait_once_registered<F, L, R, G, H>(
464    condition: &F,
465    acquire: &mut L,
466    register: &mut R,
467) -> Result<WaitOutcome, TaskError>
468where
469    F: Fn() -> bool,
470    L: FnMut() -> G,
471    R: FnMut(&mut G, WaitQueueWakeToken) -> WaitQueueRegistration<H>,
472{
473    let permit = acquire_blocking_permit()?;
474    let mut queue_guard = acquire();
475    if condition() {
476        drop(queue_guard);
477        return Ok(WaitOutcome::Condition);
478    }
479    let park = match begin_current_park_with_permit(&permit)? {
480        CurrentParkStart::Notified => {
481            drop(queue_guard);
482            return Ok(WaitOutcome::OtherWake);
483        }
484        CurrentParkStart::Prepared(park) => park,
485    };
486    let token = WaitQueueWakeToken {
487        waiter: Arc::new(WaiterWake::new(
488            park.thread_id(),
489            park.generation(),
490            park.wake_handle(),
491        )),
492    };
493    let registration = register(&mut queue_guard, token.clone());
494    drop(queue_guard);
495    let (registration, retry) = match registration {
496        WaitQueueRegistration::Armed(registration) => (registration, false),
497        WaitQueueRegistration::Retry(registration) => (registration, true),
498    };
499
500    if retry {
501        let removal = token.waiter.deactivate();
502        drop(registration);
503        park.cancel()?;
504        return Ok(if removal == WaiterRemoval::Delivered {
505            WaitOutcome::Notified
506        } else {
507            WaitOutcome::OtherWake
508        });
509    }
510
511    if let Err(error) = park.commit() {
512        token.waiter.deactivate();
513        drop(registration);
514        return Err(error);
515    }
516    let removal = token.waiter.deactivate();
517    drop(registration);
518    Ok(match removal {
519        WaiterRemoval::Delivered => WaitOutcome::Notified,
520        WaiterRemoval::Missing | WaiterRemoval::OtherWake => WaitOutcome::OtherWake,
521    })
522}
523
524struct ActiveWaitAttempt<'a> {
525    active_wait_attempts: &'a AtomicUsize,
526}
527
528impl<'a> ActiveWaitAttempt<'a> {
529    fn begin(active_wait_attempts: &'a AtomicUsize) -> Self {
530        active_wait_attempts
531            .try_update(Ordering::SeqCst, Ordering::SeqCst, |attempts| {
532                attempts.checked_add(1)
533            })
534            .unwrap_or_else(|_| panic!("wait-queue active-attempt count exhausted"));
535        Self {
536            active_wait_attempts,
537        }
538    }
539}
540
541impl Drop for ActiveWaitAttempt<'_> {
542    fn drop(&mut self) {
543        self.active_wait_attempts
544            .try_update(Ordering::SeqCst, Ordering::SeqCst, |attempts| {
545                attempts.checked_sub(1)
546            })
547            .unwrap_or_else(|_| panic!("wait-queue active-attempt count underflow"));
548    }
549}
550
551fn assert_task_context_notification() {
552    assert!(
553        !task_runtime::in_hard_irq(),
554        "WaitQueue notification is task-context-only; use IrqWaitCell from hard IRQ"
555    );
556}
557
558impl Default for WaitQueue {
559    fn default() -> Self {
560        Self::new()
561    }
562}
563
564#[derive(Debug)]
565struct Waiter {
566    wake: Arc<WaiterWake>,
567    last_attempted_by: u64,
568}
569
570impl Waiter {
571    fn new(thread: ThreadId, park_generation: u64, wake: ThreadWakeHandle) -> Self {
572        Self {
573            wake: Arc::new(WaiterWake::new(thread, park_generation, wake)),
574            last_attempted_by: 0,
575        }
576    }
577
578    fn select(&mut self, notification_generation: u64) -> Option<Arc<WaiterWake>> {
579        if self.last_attempted_by == notification_generation {
580            return None;
581        }
582        self.last_attempted_by = notification_generation;
583        let WaiterSelection::Selected(selected_waiter) = self.wake.try_select() else {
584            return None;
585        };
586        Some(selected_waiter)
587    }
588
589    fn owns_claim(&self, claim_owner: &Arc<WaiterWake>) -> bool {
590        Arc::ptr_eq(&self.wake, claim_owner)
591    }
592
593    fn requeue_after_unavailable(&self) {
594        assert!(self.wake.requeue_after_unavailable());
595    }
596}
597
598#[derive(Debug)]
599struct WaiterWake {
600    wake: ThreadWakeHandle,
601    claim: WaitWakeClaim,
602}
603
604impl WaiterWake {
605    fn new(thread: ThreadId, park_generation: u64, wake: ThreadWakeHandle) -> Self {
606        Self {
607            wake,
608            claim: WaitWakeClaim::new(thread, park_generation),
609        }
610    }
611
612    fn try_select(self: &Arc<Self>) -> WaiterSelection {
613        loop {
614            match self.claim.state() {
615                WaitWakeClaimState::Queued => {
616                    if self.claim.select() {
617                        return WaiterSelection::Selected(Arc::clone(self));
618                    }
619                }
620                WaitWakeClaimState::Selected => return WaiterSelection::Retry,
621                WaitWakeClaimState::Delivered
622                | WaitWakeClaimState::Cancelled
623                | WaitWakeClaimState::Inactive => return WaiterSelection::Stale,
624            }
625        }
626    }
627
628    fn requeue_after_unavailable(&self) -> bool {
629        self.claim.requeue_cancelled()
630    }
631
632    fn deactivate(&self) -> WaiterRemoval {
633        if self.claim.deactivate() {
634            WaiterRemoval::Delivered
635        } else {
636            WaiterRemoval::OtherWake
637        }
638    }
639}
640
641enum WaiterSelection {
642    Selected(Arc<WaiterWake>),
643    Retry,
644    Stale,
645}
646
647fn select_waiter(
648    waiters: &mut VecDeque<Waiter>,
649    notification_generation: u64,
650) -> Option<Arc<WaiterWake>> {
651    waiters
652        .iter_mut()
653        .find_map(|waiter| waiter.select(notification_generation))
654}
655
656#[derive(Clone, Copy, Debug, Eq, PartialEq)]
657enum WaitOutcome {
658    Condition,
659    Notified,
660    OtherWake,
661}
662
663#[derive(Clone, Copy, Debug, Eq, PartialEq)]
664enum WaiterRemoval {
665    Missing,
666    OtherWake,
667    Delivered,
668}
669
670fn remove_waiter(waiters: &mut VecDeque<Waiter>, thread: ThreadId) -> WaiterRemoval {
671    let Some(index) = waiters
672        .iter()
673        .position(|waiter| waiter.wake.claim.thread() == thread)
674    else {
675        return WaiterRemoval::Missing;
676    };
677    let waiter = waiters
678        .remove(index)
679        .expect("located wait-queue entry must remain present under its lock");
680    waiter.wake.deactivate()
681}
682
683#[cfg(all(test, not(miri)))]
684mod loom_tests {
685    use loom::{
686        sync::{
687            Arc, Mutex,
688            atomic::{AtomicBool, AtomicUsize, Ordering},
689        },
690        thread,
691    };
692
693    #[test]
694    fn notification_generation_closes_the_predicate_enqueue_window() {
695        loom::model(|| {
696            const READY: usize = 1;
697            const RETRY: usize = 2;
698            const QUEUED: usize = 3;
699
700            let notification_generation = Arc::new(AtomicUsize::new(0));
701            let condition = Arc::new(AtomicBool::new(false));
702            let waiter_queued = Arc::new(Mutex::new(false));
703            let waiter_outcome = Arc::new(AtomicUsize::new(0));
704            let waiter_woken = Arc::new(AtomicBool::new(false));
705
706            let waiter = {
707                let notification_generation = Arc::clone(&notification_generation);
708                let condition = Arc::clone(&condition);
709                let waiter_queued = Arc::clone(&waiter_queued);
710                let waiter_outcome = Arc::clone(&waiter_outcome);
711                thread::spawn(move || {
712                    let observed = notification_generation.load(Ordering::Acquire);
713                    if condition.load(Ordering::Acquire) {
714                        waiter_outcome.store(READY, Ordering::Release);
715                        return;
716                    }
717
718                    let mut queued = waiter_queued.lock().unwrap();
719                    if notification_generation.load(Ordering::Acquire) != observed {
720                        waiter_outcome.store(RETRY, Ordering::Release);
721                    } else {
722                        *queued = true;
723                        waiter_outcome.store(QUEUED, Ordering::Release);
724                    }
725                })
726            };
727            let notifier = {
728                let notification_generation = Arc::clone(&notification_generation);
729                let condition = Arc::clone(&condition);
730                let waiter_queued = Arc::clone(&waiter_queued);
731                let waiter_woken = Arc::clone(&waiter_woken);
732                thread::spawn(move || {
733                    condition.store(true, Ordering::Release);
734                    let mut queued = waiter_queued.lock().unwrap();
735                    notification_generation.fetch_add(1, Ordering::Release);
736                    if *queued {
737                        *queued = false;
738                        waiter_woken.store(true, Ordering::Release);
739                    }
740                })
741            };
742
743            waiter.join().unwrap();
744            notifier.join().unwrap();
745            assert!(condition.load(Ordering::Acquire));
746            if waiter_outcome.load(Ordering::Acquire) == QUEUED {
747                assert!(
748                    waiter_woken.load(Ordering::Acquire),
749                    "a waiter committed before notification must be selected"
750                );
751            }
752        });
753    }
754}