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::task;
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) = task::local_waker_ptr(waker) {
67            unsafe { task::clone_local_waker(ptr) };
68            Self::native(ptr)
69        } else {
70            Self::fallback(waker.clone())
71        }
72    }
73
74    /// Wakes the associated task, consuming this handle.
75    #[inline]
76    pub fn wake(self) {
77        if self.is_native() {
78            let this = ManuallyDrop::new(self);
79            unsafe { task::wake_local_waker(this.data, true) };
80        } else {
81            self.into_waker().wake();
82        }
83    }
84
85    /// Wakes the associated task without consuming this handle.
86    #[inline]
87    pub fn wake_by_ref(&self) {
88        if self.is_native() {
89            unsafe { task::wake_local_waker(self.data, false) };
90        } else {
91            self.borrowed_waker().wake_by_ref();
92        }
93    }
94
95    /// Returns whether this handle and `waker` wake the same task.
96    ///
97    /// Like [`Waker::will_wake`], `true` guarantees the same task, but `false`
98    /// may be returned for different representations of the same task.
99    #[inline]
100    pub fn will_wake(&self, waker: &Waker) -> bool {
101        self.borrowed_waker().will_wake(waker)
102    }
103
104    /// Consumes this handle to produce a thread-safe standard waker.
105    ///
106    /// Native conversion acquires standard-waker ownership before releasing the local reference.
107    #[inline]
108    pub fn into_waker(self) -> Waker {
109        let this = ManuallyDrop::new(self);
110        if this.is_native() {
111            unsafe { task::into_waker(this.data) }
112        } else {
113            unsafe { Waker::from_raw(this.raw_waker()) }
114        }
115    }
116
117    fn native(data: *const ()) -> Self {
118        Self {
119            data,
120            metadata: NATIVE,
121            _local: PhantomData,
122        }
123    }
124
125    fn fallback(waker: Waker) -> Self {
126        let waker = ManuallyDrop::new(waker);
127        Self {
128            data: waker.data(),
129            metadata: NonNull::from(waker.vtable()).cast(),
130            _local: PhantomData,
131        }
132    }
133
134    fn is_native(&self) -> bool {
135        self.metadata == NATIVE
136    }
137
138    fn raw_waker(&self) -> RawWaker {
139        if self.is_native() {
140            task::raw_waker(self.data)
141        } else {
142            // Fallback metadata came from Waker::vtable(), a static reference.
143            RawWaker::new(self.data, unsafe {
144                self.metadata.cast::<RawWakerVTable>().as_ref()
145            })
146        }
147    }
148
149    fn borrowed_waker(&self) -> ManuallyDrop<Waker> {
150        // The temporary borrows this handle's reference, it must not release it.
151        ManuallyDrop::new(unsafe { Waker::from_raw(self.raw_waker()) })
152    }
153}
154
155impl From<Waker> for LocalWaker {
156    /// Consumes a standard waker to acquire thread-confined wake ownership.
157    #[inline]
158    fn from(waker: Waker) -> Self {
159        if let Some(ptr) = task::local_waker_ptr(&waker) {
160            unsafe { task::clone_local_waker(ptr) };
161            drop(waker);
162            Self::native(ptr)
163        } else {
164            Self::fallback(waker)
165        }
166    }
167}
168
169impl Clone for LocalWaker {
170    #[inline]
171    fn clone(&self) -> Self {
172        if self.is_native() {
173            unsafe { task::clone_local_waker(self.data) };
174            Self::native(self.data)
175        } else {
176            Self::fallback((*self.borrowed_waker()).clone())
177        }
178    }
179
180    #[inline]
181    fn clone_from(&mut self, source: &Self) {
182        if self.data != source.data || self.metadata != source.metadata {
183            *self = source.clone();
184        }
185    }
186}
187
188impl Drop for LocalWaker {
189    #[inline]
190    fn drop(&mut self) {
191        if self.is_native() {
192            unsafe { task::drop_local_waker(self.data) };
193        } else {
194            drop(unsafe { Waker::from_raw(self.raw_waker()) });
195        }
196    }
197}
198
199impl fmt::Debug for LocalWaker {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        f.debug_struct("LocalWaker")
202            .field("data", &self.data)
203            .field("native", &self.is_native())
204            .finish()
205    }
206}
207
208#[cfg(all(test, not(ntex_loom)))]
209#[path = "../tests/unit/local_waker.rs"]
210mod tests;