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)))]
209mod tests {
210    use std::panic::{AssertUnwindSafe, catch_unwind};
211    use std::sync::atomic::{AtomicUsize, Ordering};
212    use std::task::{Poll, Wake};
213    use std::{future::poll_fn, ptr, sync::Arc};
214
215    use super::*;
216    use crate::Runner;
217
218    #[derive(Default)]
219    struct Counter(AtomicUsize);
220
221    impl Counter {
222        // A fixed vtable makes identity assertions independent of codegen deduplication.
223        fn waker(counter: Arc<Self>) -> Waker {
224            unsafe fn clone(data: *const ()) -> RawWaker {
225                unsafe { Arc::increment_strong_count(data.cast::<Counter>()) };
226                RawWaker::new(data, &VTABLE)
227            }
228            unsafe fn wake(data: *const ()) {
229                let counter = unsafe { Arc::from_raw(data.cast::<Counter>()) };
230                counter.0.fetch_add(1, Ordering::Relaxed);
231            }
232            unsafe fn wake_by_ref(data: *const ()) {
233                unsafe { &*data.cast::<Counter>() }
234                    .0
235                    .fetch_add(1, Ordering::Relaxed);
236            }
237            unsafe fn drop(data: *const ()) {
238                std::mem::drop(unsafe { Arc::from_raw(data.cast::<Counter>()) });
239            }
240            static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
241            unsafe { Waker::from_raw(RawWaker::new(Arc::into_raw(counter).cast(), &VTABLE)) }
242        }
243    }
244
245    // Cloning changes both raw parts to those of the inner waker.
246    fn changing_waker(waker: Waker) -> Waker {
247        unsafe fn clone(data: *const ()) -> RawWaker {
248            let waker = ManuallyDrop::new(unsafe { &*data.cast::<Waker>() }.clone());
249            RawWaker::new(waker.data(), waker.vtable())
250        }
251        unsafe fn wake(data: *const ()) {
252            unsafe { Box::from_raw(data.cast_mut().cast::<Waker>()) }.wake();
253        }
254        unsafe fn wake_by_ref(data: *const ()) {
255            unsafe { &*data.cast::<Waker>() }.wake_by_ref();
256        }
257        unsafe fn drop(data: *const ()) {
258            std::mem::drop(unsafe { Box::from_raw(data.cast_mut().cast::<Waker>()) });
259        }
260        static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
261        unsafe {
262            Waker::from_raw(RawWaker::new(
263                Box::into_raw(Box::new(waker)).cast(),
264                &VTABLE,
265            ))
266        }
267    }
268
269    #[test]
270    fn fallback_clone_parts_and_owned_conversion() {
271        let counter = Arc::new(Counter::default());
272        {
273            let target = Counter::waker(counter.clone());
274            let original = changing_waker(target.clone());
275            let borrowed = LocalWaker::from_waker(&original);
276            assert!(!borrowed.is_native());
277            assert!(borrowed.will_wake(&target));
278            assert!(!borrowed.will_wake(&original));
279
280            let (data, vtable) = (original.data(), original.vtable());
281            let owned = LocalWaker::from(original);
282            let cloned = owned.clone();
283            assert!(cloned.will_wake(&target));
284            assert!(!owned.will_wake(&target));
285
286            let mut replaced = LocalWaker::from_waker(Waker::noop());
287            replaced.clone_from(&owned);
288            assert!(replaced.will_wake(&target));
289            borrowed.wake_by_ref();
290            cloned.wake();
291            replaced.wake();
292
293            let original = owned.into_waker();
294            assert_eq!(original.data(), data);
295            assert!(ptr::eq(original.vtable(), vtable));
296            original.wake();
297        }
298        assert_eq!(counter.0.load(Ordering::Relaxed), 4);
299        assert_eq!(Arc::strong_count(&counter), 1);
300    }
301
302    #[test]
303    fn fallback_null_data_and_clone_unwind() {
304        static PANIC: RawWakerVTable = RawWakerVTable::new(
305            |_| panic!("clone"),
306            |data| assert!(data.is_null()),
307            |data| assert!(data.is_null()),
308            |data| assert!(data.is_null()),
309        );
310        let waker = || unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &PANIC)) };
311        let source = LocalWaker::from(waker());
312        let mut same = LocalWaker::from(waker());
313        same.clone_from(&source); // Same identity must not call the panicking clone.
314        same.wake_by_ref();
315        same.wake();
316        assert!(catch_unwind(|| LocalWaker::from_waker(&waker())).is_err());
317        assert!(catch_unwind(AssertUnwindSafe(|| source.clone())).is_err());
318
319        let counter = Arc::new(Counter::default());
320        let target = Counter::waker(counter.clone());
321        let mut destination = LocalWaker::from_waker(&target);
322        assert!(catch_unwind(AssertUnwindSafe(|| destination.clone_from(&source))).is_err());
323        assert!(destination.will_wake(&target));
324        destination.wake();
325        assert_eq!(counter.0.load(Ordering::Relaxed), 1);
326    }
327
328    #[test]
329    fn fallback_wake_and_drop_unwind() {
330        static VTABLE: RawWakerVTable = RawWakerVTable::new(
331            |data| {
332                unsafe { Arc::increment_strong_count(data.cast::<Counter>()) };
333                RawWaker::new(data, &VTABLE)
334            },
335            |data| {
336                let counter = unsafe { Arc::from_raw(data.cast::<Counter>()) };
337                counter.0.fetch_add(1, Ordering::Relaxed);
338                panic!("wake");
339            },
340            |data| {
341                unsafe { &*data.cast::<Counter>() }
342                    .0
343                    .fetch_add(1, Ordering::Relaxed);
344                panic!("wake_by_ref");
345            },
346            |data| {
347                let counter = unsafe { Arc::from_raw(data.cast::<Counter>()) };
348                counter.0.fetch_add(1, Ordering::Relaxed);
349                panic!("drop");
350            },
351        );
352        let counter = Arc::new(Counter::default());
353        let local = LocalWaker::from(unsafe {
354            Waker::from_raw(RawWaker::new(
355                Arc::into_raw(counter.clone()).cast(),
356                &VTABLE,
357            ))
358        });
359        assert!(catch_unwind(AssertUnwindSafe(|| local.wake_by_ref())).is_err());
360        assert_eq!(Arc::strong_count(&counter), 2);
361        assert!(catch_unwind(AssertUnwindSafe(|| local.clone().wake())).is_err());
362        assert_eq!(Arc::strong_count(&counter), 2);
363        assert!(catch_unwind(AssertUnwindSafe(|| drop(local))).is_err());
364        assert_eq!(Arc::strong_count(&counter), 1);
365        assert_eq!(counter.0.load(Ordering::Relaxed), 3);
366    }
367
368    #[test]
369    fn selected_runtime_and_wrapped_waker() {
370        struct Forward(Waker, Arc<Counter>);
371
372        impl Wake for Forward {
373            fn wake(self: Arc<Self>) {
374                self.1.0.fetch_add(1, Ordering::Relaxed);
375                self.0.wake_by_ref();
376            }
377        }
378
379        crate::testing::TestRunner
380            .block_on(Box::pin(async {
381                let mut polled = false;
382                let local = crate::spawn(poll_fn(move |cx| {
383                    let local = LocalWaker::from_waker(cx.waker());
384                    assert_eq!(
385                        local.is_native(),
386                        !cfg!(any(feature = "tokio", feature = "compio"))
387                    );
388                    assert!(local.will_wake(cx.waker()));
389                    if polled {
390                        return Poll::Ready(local);
391                    }
392                    polled = true;
393                    let counter = Arc::new(Counter::default());
394                    let wrapped =
395                        Waker::from(Arc::new(Forward(cx.waker().clone(), counter.clone())));
396                    let handle = LocalWaker::from_waker(&wrapped);
397                    assert!(!handle.is_native());
398                    handle.wake();
399                    assert_eq!(counter.0.load(Ordering::Relaxed), 1);
400                    local.wake();
401                    Poll::Pending
402                }))
403                .await
404                .unwrap();
405                local.clone().wake();
406                local.into_waker().wake();
407            }))
408            .unwrap();
409    }
410}