Skip to main content

dtact_util/sync/
notify.rs

1//! Single-slot wake-up notification, matching `tokio::sync::Notify`'s
2//! semantics: a permit that survives a `notify_one()` arriving before
3//! anyone is waiting, but `notify_waiters()` only reaches tasks already
4//! parked at the moment it's called.
5
6use super::wait_queue::WaitQueue;
7use std::future::Future;
8use std::pin::Pin;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::task::{Context, Poll};
11
12/// A notification primitive commonly used to hand-wake another async
13/// task/structure — e.g. signaling "state changed, go re-check" without a
14/// full channel.
15#[repr(align(64))]
16pub struct Notify {
17    /// A single-slot "permit": set by `notify_one()` when nothing was
18    /// waiting, consumed by the very next `notified().await`.
19    permit: AtomicBool,
20    wait: WaitQueue,
21}
22
23impl Default for Notify {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29impl Notify {
30    /// Create a `Notify` with no stored permit and no waiters.
31    #[must_use]
32    pub const fn new() -> Self {
33        Self {
34            permit: AtomicBool::new(false),
35            wait: WaitQueue::new(),
36        }
37    }
38
39    /// Wake one waiting `notified().await`, or — if none is currently
40    /// waiting — store a permit so the *next* `notified().await` returns
41    /// immediately without waiting at all.
42    #[inline(always)]
43    pub fn notify_one(&self) {
44        // Unconditionally store the permit, *then* wake one queued waiter
45        // — the same "set flag, then wake the (lock-free) queue" shape
46        // `Mutex`/`RwLock`/`Semaphore` all use, rather than the mutex-
47        // queue-only version this replaced, which conditionally stored
48        // the permit solely when a locked look at the queue found it
49        // empty. That conditional version needed the queue-emptiness
50        // check and the permit store to be atomic together (true under a
51        // shared mutex, false under `WaitQueue`'s lock-free stack: a
52        // `notified()` call's `register` could land in the gap between
53        // "found empty" and "store permit", registering a waker nothing
54        // would ever wake, and permanently stalling that task while a
55        // *different*, later `notified()` caller sees the (still-set)
56        // permit and gets a wakeup that was never meant for it).
57        //
58        // This version can't lose a wakeup: any waiter's `Notified::poll`
59        // rechecks the permit *after* registering (see below), so a store
60        // that lands at any point relative to that registration is either
61        // seen by the first check, the second check, or — if the register
62        // raced ahead of both checks — by this call's `wake_one()`
63        // finding that waiter already queued. If a waiter was already
64        // parked, it gets directly woken (by `wake_one`) *and* finds the
65        // permit set when it re-polls, consuming it immediately — so no
66        // permit is ever left stranded for an unrelated later waiter to
67        // pick up, matching `tokio::sync::Notify`'s "at most one buffered
68        // permit" semantics exactly, just via a different code path.
69        self.permit.store(true, Ordering::Release);
70        self.wait.wake_one();
71    }
72
73    /// Wake every task currently waiting in `notified().await`. Does
74    /// *not* store a permit — a task that calls `notified()` after this
75    /// returns will wait for a future notification, not this one.
76    #[inline(always)]
77    pub fn notify_waiters(&self) {
78        self.wait.wake_all();
79    }
80
81    /// A future that resolves once this `Notify` is notified — either a
82    /// stored permit is consumed immediately, or a future
83    /// [`notify_one`](Self::notify_one)/[`notify_waiters`](Self::notify_waiters)
84    /// wakes it.
85    pub const fn notified(&self) -> Notified<'_> {
86        Notified { notify: self }
87    }
88}
89
90/// Future returned by [`Notify::notified`].
91#[repr(align(64))]
92pub struct Notified<'a> {
93    notify: &'a Notify,
94}
95
96impl Future for Notified<'_> {
97    type Output = ();
98
99    #[inline(always)]
100    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
101        if self
102            .notify
103            .permit
104            .compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed)
105            .is_ok()
106        {
107            return Poll::Ready(());
108        }
109        let token = self.notify.wait.register(cx.waker());
110        if self
111            .notify
112            .permit
113            .compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed)
114            .is_ok()
115        {
116            self.notify.wait.cancel(token);
117            return Poll::Ready(());
118        }
119        Poll::Pending
120    }
121}