fuchsia_async/runtime/fuchsia/executor/atomic_future/
hooks.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::instrument::Hooks;
6
7use super::{AtomicFutureHandle, Meta, VTable};
8use fuchsia_sync::Mutex;
9use std::collections::HashMap;
10use std::ptr::NonNull;
11use std::task::{Context, Poll};
12
13/// We don't want to pay a cost if there are no hooks, so we store a mapping from task ID to
14/// HooksWrapper in the executor.
15#[derive(Default)]
16pub struct HooksMap(Mutex<HashMap<usize, NonNull<()>>>);
17
18unsafe impl Send for HooksMap {}
19unsafe impl Sync for HooksMap {}
20
21struct HooksWrapper<H> {
22    orig_vtable: &'static VTable,
23    hooks: H,
24}
25
26impl<H: Hooks> HooksWrapper<H> {
27    // # Safety
28    //
29    // We rely on the fact that all these functions are called whilst we have exclusive
30    // access to the underlying future and the associated Hooks object.
31    const VTABLE: VTable = VTable {
32        drop: Self::drop,
33        drop_future: Self::drop_future,
34        poll: Self::poll,
35        get_result: Self::get_result,
36        drop_result: Self::drop_result,
37    };
38
39    // Returns a mutable reference to the wrapper. This will be safe from the functions below
40    // because they are all called when we have exclusive access.
41    unsafe fn wrapper<'a>(meta: NonNull<Meta>) -> &'a mut Self {
42        let id = meta.as_ptr() as usize;
43        unsafe {
44            meta.as_ref()
45                .scope()
46                .executor()
47                .hooks_map
48                .0
49                .lock()
50                .get(&id)
51                .unwrap()
52                .cast::<Self>()
53                .as_mut()
54        }
55    }
56
57    unsafe fn drop(mut meta: NonNull<Meta>) {
58        let meta_ref = unsafe { meta.as_mut() };
59        // Remove the hooks entry from the map.
60        let id = meta.as_ptr() as usize;
61        let hooks = unsafe {
62            Box::from_raw(
63                meta_ref
64                    .scope()
65                    .executor()
66                    .hooks_map
67                    .0
68                    .lock()
69                    .remove(&id)
70                    .unwrap()
71                    .cast::<Self>()
72                    .as_mut(),
73            )
74        };
75        // Restore the vtable because the drop implementation can call `drop_future` or
76        // `drop_result`, but we've removed the hooks from the map now.
77        meta_ref.vtable = hooks.orig_vtable;
78        unsafe { (hooks.orig_vtable.drop)(meta) };
79    }
80
81    unsafe fn poll(meta: NonNull<Meta>, cx: &mut Context<'_>) -> Poll<()> {
82        let wrapper = unsafe { Self::wrapper(meta) };
83        wrapper.hooks.task_poll_start();
84        let result = unsafe { (wrapper.orig_vtable.poll)(meta, cx) };
85        wrapper.hooks.task_poll_end();
86        if result.is_ready() {
87            wrapper.hooks.task_completed();
88        }
89        result
90    }
91
92    unsafe fn drop_future(meta: NonNull<Meta>) {
93        unsafe { (Self::wrapper(meta).orig_vtable.drop_future)(meta) };
94    }
95
96    unsafe fn get_result(meta: NonNull<Meta>) -> *const () {
97        unsafe { (Self::wrapper(meta).orig_vtable.get_result)(meta) }
98    }
99
100    unsafe fn drop_result(meta: NonNull<Meta>) {
101        unsafe { (Self::wrapper(meta).orig_vtable.drop_result)(meta) };
102    }
103}
104
105impl AtomicFutureHandle<'_> {
106    /// Adds hooks to the future.
107    pub fn add_hooks<H: Hooks>(&mut self, hooks: H) {
108        let id = self.id();
109        // SAFETY: This is safe because we have exclusive access.
110        let meta: &mut Meta = unsafe { self.0.as_mut() };
111        {
112            let mut hooks_map = meta.scope().executor().hooks_map.0.lock();
113            // SAFETY: Safe because `Box::into_raw` is guaranteed to give is a non-null pointer. We
114            // can use `Box::into_non_null` when it's stabilised.
115            assert!(
116                hooks_map
117                    .insert(id, unsafe {
118                        NonNull::new_unchecked(Box::into_raw(Box::new(HooksWrapper {
119                            orig_vtable: meta.vtable,
120                            hooks,
121                        })))
122                        .cast::<()>()
123                    })
124                    .is_none()
125            );
126        }
127        // Inject our vtable.
128        meta.vtable = &HooksWrapper::<H>::VTABLE;
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::Hooks;
135    use crate::runtime::fuchsia::executor::scope::Spawnable;
136    use crate::{SpawnableFuture, TestExecutor, yield_now};
137    use std::sync::Arc;
138    use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
139
140    #[test]
141    fn test_hooks() {
142        let mut executor = TestExecutor::new();
143        let scope = executor.global_scope();
144        let mut future = SpawnableFuture::new(async {
145            yield_now().await;
146        })
147        .into_task(scope.clone());
148        #[derive(Default)]
149        struct MyHooks {
150            poll_start: AtomicU32,
151            poll_end: AtomicU32,
152            completed: AtomicBool,
153        }
154        impl Hooks for Arc<MyHooks> {
155            fn task_completed(&mut self) {
156                assert!(!self.completed.load(Ordering::Relaxed));
157                self.completed.store(true, Ordering::Relaxed);
158            }
159            fn task_poll_start(&mut self) {
160                self.poll_start.fetch_add(1, Ordering::Relaxed);
161            }
162            fn task_poll_end(&mut self) {
163                self.poll_end.fetch_add(1, Ordering::Relaxed);
164            }
165        }
166        let my_hooks = Arc::new(MyHooks::default());
167        future.add_hooks(my_hooks.clone());
168        scope.insert_task(future, false);
169        assert!(executor.run_until_stalled(&mut std::future::pending::<()>()).is_pending());
170        assert_eq!(my_hooks.poll_start.load(Ordering::Relaxed), 2);
171        assert_eq!(my_hooks.poll_end.load(Ordering::Relaxed), 2);
172        assert!(my_hooks.completed.load(Ordering::Relaxed));
173    }
174}