Skip to main content

libasync_dispatcher/
lib.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
5//! Safe bindings for the C libasync async dispatcher library
6
7#![deny(missing_docs, clippy::undocumented_unsafe_blocks)]
8
9use libasync_sys::*;
10
11use core::cell::UnsafeCell;
12use core::future::Future;
13use core::marker::PhantomData;
14use core::ptr::NonNull;
15use std::sync::Arc;
16
17use zx_status::Status;
18use zx_types::zx_time_t;
19
20mod current_dispatcher;
21mod detect_dispatcher;
22mod task;
23
24pub use current_dispatcher::*;
25pub use detect_dispatcher::*;
26pub use task::*;
27
28/// A reference to a dispatcher that supports the v4 async api's reference counting operations,
29/// and so can be held safely without a lifetime.
30#[derive(Debug)]
31pub struct AsyncDispatcher(NonNull<async_dispatcher_t>);
32
33// SAFETY: It is safe to access an `async_dispatcher_t` from any thread per the libasync C api.
34unsafe impl Send for AsyncDispatcher {}
35// SAFETY: It is safe to access an `async_dispatcher_t` from any thread per the libasync C api.
36unsafe impl Sync for AsyncDispatcher {}
37
38impl AsyncDispatcher {
39    /// Converts from something that implements [`AsAsyncDispatcherRef`] to an [`AsyncDispatcher`]
40    /// if it implements the v4 async api's reference counting.
41    ///
42    /// # Panics
43    ///
44    /// This will panic if the implementation does not support reference counting. If you need to be
45    /// able to deal with a dispatcher that might not implement this api, you can use
46    /// [`AsyncDispatcher::new`].
47    pub fn new(dispatcher: &impl AsAsyncDispatcherRef) -> Self {
48        Self::try_new(dispatcher).expect("Dispatcher does not implement reference counting")
49    }
50
51    /// Converts from something that implements [`AsAsyncDispatcherRef`] to an [`AsyncDispatcher`]
52    /// if it implements the v4 async api's reference counting.
53    ///
54    /// Returns [`Status::UNSUPPORTED`] if the dispatcher does not support refcounting.
55    pub fn try_new(dispatcher: &impl AsAsyncDispatcherRef) -> Result<Self, Status> {
56        let dispatcher = dispatcher.as_async_dispatcher_ref();
57        // SAFETY: The dispatcher is a valid reference to a live dispatcher by construction, and
58        // we will only return a new Self if the call succeeds, so we will not release an invalid
59        // reference.
60        Status::ok(unsafe { libasync_sys::async_acquire_shared_ref(dispatcher.0.as_ptr()) })?;
61        Ok(Self(dispatcher.0))
62    }
63
64    /// Returns the current time on the dispatcher's timeline
65    pub fn now(&self) -> zx_time_t {
66        let async_dispatcher = self.as_ptr().as_ptr();
67        // SAFETY: The dispatcher is a valid reference to a live dispatcher by construction, and
68        // this function does not touch any rust memory.
69        unsafe { async_now(async_dispatcher) }
70    }
71
72    /// Gets the inner pointer to the dispatcher struct.
73    pub fn as_ptr(&self) -> NonNull<async_dispatcher_t> {
74        self.0
75    }
76}
77
78impl Clone for AsyncDispatcher {
79    fn clone(&self) -> Self {
80        Self::new(self)
81    }
82}
83
84impl Drop for AsyncDispatcher {
85    fn drop(&mut self) {
86        // SAFETY: The dispatcher is a valid reference to a live dispatcher by construction, and
87        // we have already successfully acquired the shared reference to it in [`Self::try_new`].
88        Status::ok(unsafe { libasync_sys::async_release_shared_ref(self.0.as_ptr()) })
89            .expect("attempted to release shared dispatcher ref that doesn't support refcounting");
90    }
91}
92
93impl AsAsyncDispatcherRef for AsyncDispatcher {
94    fn as_async_dispatcher_ref(&self) -> AsyncDispatcherRef<'_> {
95        AsyncDispatcherRef(self.0, PhantomData)
96    }
97}
98
99/// An unowned reference to a driver runtime dispatcher such as is produced by calling
100/// [`AsyncDispatcher::release`]. When this object goes out of scope it won't shut down the dispatcher,
101/// leaving that up to the driver runtime or another owner.
102#[derive(Debug, Copy, Clone)]
103pub struct AsyncDispatcherRef<'a>(NonNull<async_dispatcher_t>, PhantomData<&'a async_dispatcher_t>);
104
105// SAFETY: It is safe to access an `async_dispatcher_t` from any thread per the libasync C api.
106unsafe impl<'a> Send for AsyncDispatcherRef<'a> {}
107// SAFETY: It is safe to access an `async_dispatcher_t` from any thread per the libasync C api.
108unsafe impl<'a> Sync for AsyncDispatcherRef<'a> {}
109
110impl<'a> AsyncDispatcherRef<'a> {
111    /// Creates a dispatcher ref from a raw ptr.
112    ///
113    /// # Safety
114    ///
115    /// Caller is responsible for ensuring that the given ptr is valid for
116    /// the lifetime `'a`.
117    pub unsafe fn from_raw(ptr: NonNull<async_dispatcher_t>) -> Self {
118        // SAFETY: Caller promises the ptr is valid.
119        Self(ptr, PhantomData)
120    }
121
122    /// Gets the inner pointer to the dispatcher struct.
123    pub fn inner(&self) -> NonNull<async_dispatcher_t> {
124        self.0
125    }
126
127    /// Returns the current time on the dispatcher's timeline
128    pub fn now(&self) -> zx_time_t {
129        let async_dispatcher = self.inner().as_ptr();
130        // SAFETY: The dispatcher is a valid reference to a live dispatcher by construction, and
131        // this function does not touch any rust memory.
132        unsafe { async_now(async_dispatcher) }
133    }
134}
135
136/// A trait for things that can be represented as an [`AsyncDispatcherRef`].
137pub trait AsAsyncDispatcherRef: Send + Sync {
138    /// Gets an [`AsyncDispatcherRef`] corresponding to this object.
139    fn as_async_dispatcher_ref(&self) -> AsyncDispatcherRef<'_>;
140
141    /// Schedules the callback [`p`] to be run on this dispatcher later.
142    fn post_task_sync(&self, p: impl TaskCallback) -> Result<(), Status> {
143        #[expect(clippy::arc_with_non_send_sync)]
144        let task_arc = Arc::new(UnsafeCell::new(TaskFunc {
145            task: async_task { handler: Some(TaskFunc::call), ..Default::default() },
146            func: Box::new(p),
147        }));
148
149        let task_cell = Arc::into_raw(task_arc);
150        // SAFETY: we need a raw mut pointer to give to async_post_task. From
151        // when we call that function to when the task is cancelled or the
152        // callback is called, the driver runtime owns the contents of that
153        // object and we will not manipulate it. So even though the Arc only
154        // gives us a shared reference, it's fine to give the runtime a
155        // mutable pointer to it.
156        let res = unsafe {
157            let task_ptr = &raw mut (*UnsafeCell::raw_get(task_cell)).task;
158            Status::ok(async_post_task(self.as_async_dispatcher_ref().0.as_ptr(), task_ptr))
159        };
160        if res.is_err() {
161            // SAFETY: `TaskFunc::call` will never be called now so dispose of
162            // the long-lived reference we just created.
163            unsafe { Arc::decrement_strong_count(task_cell) }
164        }
165        res
166    }
167}
168
169impl<T> AsAsyncDispatcherRef for Arc<T>
170where
171    T: AsAsyncDispatcherRef,
172{
173    fn as_async_dispatcher_ref(&self) -> AsyncDispatcherRef<'_> {
174        (**self).as_async_dispatcher_ref()
175    }
176}
177
178impl<'a> AsAsyncDispatcherRef for AsyncDispatcherRef<'a> {
179    fn as_async_dispatcher_ref(&self) -> AsyncDispatcherRef<'_> {
180        *self
181    }
182}
183
184/// A trait for things that can be represented as an [`AsyncDispatcher`].
185///
186/// This is automatically implemented for things that implement [`AsAsyncDispatcherRef`],
187/// but may be implemented by other things that have more logic to how they obtain the correct
188/// dispatcher object.
189pub trait GetAsyncDispatcher {
190    /// Returns a refcounted handle to the active dispatcher for this object, if there is one.
191    /// Some types of dispatchers (like for the current dispatcher of a thread) may not always have
192    /// an active dispatcher, so it is returned as an option.
193    fn try_get_async_dispatcher(&self) -> Option<AsyncDispatcher>;
194
195    /// Returns a refcounted handle to the active dispatcher for this object.
196    ///
197    /// # Panics
198    ///
199    /// Some types of dispatchers (like for the current dispatcher of a thread) may not always have
200    /// an active dispatcher, in which case this will panic. If you need to be able to handle there
201    /// not being an active dispatcher, use [`Self::try_get_async_dispatcher`].
202    fn get_async_dispatcher(&self) -> AsyncDispatcher {
203        self.try_get_async_dispatcher().expect("No current async dispatcher")
204    }
205}
206
207impl<T> GetAsyncDispatcher for T
208where
209    T: AsAsyncDispatcherRef,
210{
211    fn try_get_async_dispatcher(&self) -> Option<AsyncDispatcher> {
212        Some(AsyncDispatcher::new(self))
213    }
214}
215
216/// A trait that can be used to access a lifetime-constrained dispatcher in a generic way.
217pub trait OnDispatcher: GetAsyncDispatcher + Clone + Send + Sync {
218    /// Runs the function `f` with a lifetime-bound [`AsyncDispatcherRef`] for this object's dispatcher.
219    /// If the dispatcher is no longer valid, the callback will be given [`None`].
220    ///
221    /// Note that it is *very important* that no blocking work be done in this callback to prevent
222    /// long lived strong references to dispatchers that might be shutting down.
223    fn on_dispatcher<R>(&self, f: impl FnOnce(Option<AsyncDispatcherRef<'_>>) -> R) -> R;
224
225    /// Helper version of [`OnDispatcher::on_dispatcher`] that translates an invalidated dispatcher
226    /// handle into a [`Status::BAD_STATE`] error instead of giving the callback [`None`].
227    ///
228    /// Note that it is *very important* that no blocking work be done in this callback to prevent
229    /// long lived strong references to dispatchers that might be shutting down.
230    fn on_maybe_dispatcher<R, E: From<Status>>(
231        &self,
232        f: impl FnOnce(AsyncDispatcherRef<'_>) -> Result<R, E>,
233    ) -> Result<R, E>;
234
235    /// Spawn an asynchronous task on this dispatcher. If this returns [`Ok`] then the task has
236    /// successfully been scheduled and will run or be cancelled and dropped when the dispatcher
237    /// shuts down. The returned future's result will be [`Ok`] if the future completed
238    /// successfully, or an [`Err`] if the task did not complete for some reason (like the
239    /// dispatcher shut down).
240    ///
241    /// Returns a [`JoinHandle`] that will detach the future when dropped.
242    fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) -> JoinHandle<()>
243    where
244        Self: 'static;
245
246    /// Spawn an asynchronous task that outputs type 'T' on this dispatcher. The returned future's
247    /// result will be [`Ok`] if the task was started and completed successfully, or an [`Err`] if
248    /// the task couldn't be started or failed to complete (for example because the dispatcher was
249    /// shutting down).
250    ///
251    /// Returns a [`Task`] that will cancel the future when dropped.
252    ///
253    /// TODO(470088116): This may be the cause of some flakes, so care should be used with it
254    /// in critical paths for now.
255    fn compute<T: Send + 'static>(
256        &self,
257        future: impl Future<Output = T> + Send + 'static,
258    ) -> Task<T>
259    where
260        Self: 'static;
261}
262
263impl<D: GetAsyncDispatcher + Clone + Send + Sync> OnDispatcher for D {
264    fn on_dispatcher<R>(&self, f: impl FnOnce(Option<AsyncDispatcherRef<'_>>) -> R) -> R {
265        if let Some(dispatcher) = self.try_get_async_dispatcher() {
266            f(Some(dispatcher.as_async_dispatcher_ref()))
267        } else {
268            f(None)
269        }
270    }
271
272    fn on_maybe_dispatcher<R, E: From<Status>>(
273        &self,
274        f: impl FnOnce(AsyncDispatcherRef<'_>) -> Result<R, E>,
275    ) -> Result<R, E> {
276        self.on_dispatcher(|dispatcher| {
277            let dispatcher = dispatcher.ok_or(Status::BAD_STATE)?;
278            f(dispatcher)
279        })
280    }
281
282    fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) -> JoinHandle<()>
283    where
284        Self: 'static,
285    {
286        self.compute(future).detach_on_drop()
287    }
288
289    fn compute<T: Send + 'static>(
290        &self,
291        future: impl Future<Output = T> + Send + 'static,
292    ) -> Task<T>
293    where
294        Self: 'static,
295    {
296        match self.try_get_async_dispatcher() {
297            Some(dispatcher) => Task::start(future, dispatcher),
298            None => Task::new_failed(Status::BAD_STATE),
299        }
300    }
301}
302
303/// A marker trait for a callback that can be used with [`Dispatcher::post_task_sync`].
304pub trait TaskCallback: FnOnce(Result<(), Status>) + 'static + Send {}
305impl<T> TaskCallback for T where T: FnOnce(Result<(), Status>) + 'static + Send {}
306
307#[repr(C)]
308struct TaskFunc {
309    task: async_task,
310    func: Box<dyn TaskCallback>,
311}
312
313impl TaskFunc {
314    extern "C" fn call(dispatcher: *mut async_dispatcher, task: *mut async_task, status: i32) {
315        // SAFETY: The async api will only call this function on a valid dispatcher (even if it's
316        // shutting down).
317        let dispatcher =
318            unsafe { AsyncDispatcherRef::from_raw(NonNull::new_unchecked(dispatcher)) };
319        // SAFETY: the async api promises that this function will only be called
320        // up to once, so we can reconstitute the `Arc` and let it get dropped.
321        let task = unsafe { Arc::from_raw(task as *const UnsafeCell<Self>) };
322        // SAFETY: if we can't get a mut ref from the arc, then the task is already
323        // being cancelled, so we don't want to call it.
324        if let Ok(task) = Arc::try_unwrap(task) {
325            CurrentDispatcher::with(&dispatcher, move || {
326                (task.into_inner().func)(Status::ok(status));
327            });
328        }
329    }
330}