Skip to main content

ntex_rt/
local_waker.rs

1use std::marker::PhantomData;
2use std::mem::ManuallyDrop;
3use std::ptr::NonNull;
4use std::task::{RawWaker, RawWakerVTable, Waker};
5use std::{fmt, rc::Rc};
6
7use crate::rt::wake;
8
9// Address 1 cannot be an aligned vtable pointer and leaves the null niche free.
10const NATIVE: NonNull<()> = NonNull::dangling();
11const _: () = assert!(align_of::<RawWakerVTable>() > 1);
12
13/// An owned wake handle that can only be used on the thread that acquired it.
14///
15/// Native ntex task wakers acquired on their registered owner thread use local operations
16/// without repeating the thread check or registry lookup. Other wakers retain their standard
17/// behavior, including wakers from Tokio, Compio, and custom wrappers.
18///
19/// Cloning this handle clones wake ownership; it does not create an empty
20/// notification slot. Waking consumes the handle, while [`wake_by_ref`](Self::wake_by_ref)
21/// retains it. A handle remains valid after its task completes or its runtime
22/// is dropped. Use [`into_waker`](Self::into_waker) to transfer it to another
23/// thread as a standard, thread-safe [`Waker`].
24///
25/// On 64-bit targets both this type and `Option<LocalWaker>` occupy 16 bytes.
26/// There is no additional wrapper allocation; a foreign waker's own clone
27/// operation may allocate.
28///
29/// ```
30/// use ntex_rt::LocalWaker;
31/// use std::task::Waker;
32///
33/// let local = LocalWaker::from_waker(Waker::noop());
34/// local.wake_by_ref();
35/// let waker = local.into_waker();
36/// std::thread::spawn(move || waker.wake()).join().unwrap();
37/// ```
38///
39/// ```compile_fail
40/// fn assert_send<T: Send>() {}
41/// assert_send::<ntex_rt::LocalWaker>();
42/// ```
43///
44/// ```compile_fail
45/// fn assert_sync<T: Sync>() {}
46/// assert_sync::<ntex_rt::LocalWaker>();
47/// ```
48#[repr(C)]
49pub struct LocalWaker {
50    data: *const (),
51    // NATIVE on the owner thread, otherwise the original standard vtable.
52    metadata: NonNull<()>,
53    _local: PhantomData<Rc<()>>,
54}
55
56#[cfg(target_pointer_width = "64")]
57const _: () = {
58    assert!(size_of::<LocalWaker>() == 16);
59    assert!(size_of::<Option<LocalWaker>>() == 16);
60};
61
62impl LocalWaker {
63    /// Clones a standard waker into a thread-confined wake handle.
64    #[inline]
65    pub fn from_waker(waker: &Waker) -> Self {
66        if let Some(ptr) = wake::local_waker_ptr(waker) {
67            // SAFETY: `local_waker_ptr` verified a native waker on its registered owner thread.
68            unsafe { wake::clone_local_waker(ptr) };
69            Self::native(ptr)
70        } else {
71            Self::fallback(waker.clone())
72        }
73    }
74
75    /// Wakes the associated task, consuming this handle.
76    #[inline]
77    pub fn wake(self) {
78        if self.is_native() {
79            let this = ManuallyDrop::new(self);
80            // SAFETY: This thread-confined native handle transfers its owned reference without running `Drop`.
81            unsafe { wake::wake_local_waker(this.data, true) };
82        } else {
83            self.into_waker().wake();
84        }
85    }
86
87    /// Wakes the associated task without consuming this handle.
88    #[inline]
89    pub fn wake_by_ref(&self) {
90        if self.is_native() {
91            // SAFETY: This native handle retains a task reference and cannot leave its owner thread.
92            unsafe { wake::wake_local_waker(self.data, false) };
93        } else {
94            self.borrowed_waker().wake_by_ref();
95        }
96    }
97
98    /// Returns whether this handle and `waker` wake the same task.
99    ///
100    /// Like [`Waker::will_wake`], `true` guarantees the same task, but `false`
101    /// may be returned for different representations of the same task.
102    #[inline]
103    pub fn will_wake(&self, waker: &Waker) -> bool {
104        self.borrowed_waker().will_wake(waker)
105    }
106
107    /// Consumes this handle to produce a thread-safe standard waker.
108    ///
109    /// Native conversion acquires standard-waker ownership before releasing the local reference.
110    #[inline]
111    pub fn into_waker(self) -> Waker {
112        let this = ManuallyDrop::new(self);
113        if this.is_native() {
114            // SAFETY: This native handle transfers its owner-thread reference, suppressing its own `Drop`.
115            unsafe { wake::into_waker(this.data) }
116        } else {
117            // SAFETY: The saved data and vtable retain the original standard waker's owned reference.
118            unsafe { Waker::from_raw(this.raw_waker()) }
119        }
120    }
121
122    fn native(data: *const ()) -> Self {
123        Self {
124            data,
125            metadata: NATIVE,
126            _local: PhantomData,
127        }
128    }
129
130    fn fallback(waker: Waker) -> Self {
131        let waker = ManuallyDrop::new(waker);
132        Self {
133            data: waker.data(),
134            metadata: NonNull::from(waker.vtable()).cast(),
135            _local: PhantomData,
136        }
137    }
138
139    fn is_native(&self) -> bool {
140        self.metadata == NATIVE
141    }
142
143    fn raw_waker(&self) -> RawWaker {
144        if self.is_native() {
145            wake::raw_waker(self.data)
146        } else {
147            // SAFETY: Fallback metadata came from `Waker::vtable()`, a static reference.
148            RawWaker::new(self.data, unsafe {
149                self.metadata.cast::<RawWakerVTable>().as_ref()
150            })
151        }
152    }
153
154    fn borrowed_waker(&self) -> ManuallyDrop<Waker> {
155        // SAFETY: This handle retains the matching data and vtable; `ManuallyDrop` prevents a borrowed release.
156        ManuallyDrop::new(unsafe { Waker::from_raw(self.raw_waker()) })
157    }
158}
159
160impl From<Waker> for LocalWaker {
161    /// Consumes a standard waker to acquire thread-confined wake ownership.
162    #[inline]
163    fn from(waker: Waker) -> Self {
164        if let Some(ptr) = wake::local_waker_ptr(&waker) {
165            // SAFETY: `local_waker_ptr` verified a native waker on its registered owner thread.
166            unsafe { wake::clone_local_waker(ptr) };
167            drop(waker);
168            Self::native(ptr)
169        } else {
170            Self::fallback(waker)
171        }
172    }
173}
174
175impl Clone for LocalWaker {
176    #[inline]
177    fn clone(&self) -> Self {
178        if self.is_native() {
179            // SAFETY: This native handle retains a task reference and is confined to its owner thread.
180            unsafe { wake::clone_local_waker(self.data) };
181            Self::native(self.data)
182        } else {
183            Self::fallback((*self.borrowed_waker()).clone())
184        }
185    }
186
187    #[inline]
188    fn clone_from(&mut self, source: &Self) {
189        if self.data != source.data || self.metadata != source.metadata {
190            *self = source.clone();
191        }
192    }
193}
194
195impl Drop for LocalWaker {
196    #[inline]
197    fn drop(&mut self) {
198        if self.is_native() {
199            // SAFETY: This thread-confined native handle owns the reference being released.
200            unsafe { wake::drop_local_waker(self.data) };
201        } else {
202            // SAFETY: The saved data and vtable reconstruct this handle's owned standard-waker reference.
203            drop(unsafe { Waker::from_raw(self.raw_waker()) });
204        }
205    }
206}
207
208impl fmt::Debug for LocalWaker {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        f.debug_struct("LocalWaker")
211            .field("data", &self.data)
212            .field("native", &self.is_native())
213            .finish()
214    }
215}
216
217#[cfg(all(test, not(ntex_loom)))]
218#[path = "../tests/unit/local_waker.rs"]
219mod tests;