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;