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
128/// A trait for things that can be represented as an [`AsyncDispatcherRef`].
129pub trait AsAsyncDispatcherRef: Send + Sync {
130    /// Gets an [`AsyncDispatcherRef`] corresponding to this object.
131    fn as_async_dispatcher_ref(&self) -> AsyncDispatcherRef<'_>;
132
133    /// Schedules the callback [`p`] to be run on this dispatcher later.
134    fn post_task_sync(&self, p: impl TaskCallback) -> Result<(), Status> {
135        #[expect(clippy::arc_with_non_send_sync)]
136        let task_arc = Arc::new(UnsafeCell::new(TaskFunc {
137            task: async_task { handler: Some(TaskFunc::call), ..Default::default() },
138            func: Box::new(p),
139        }));
140
141        let task_cell = Arc::into_raw(task_arc);
142        // SAFETY: we need a raw mut pointer to give to async_post_task. From
143        // when we call that function to when the task is cancelled or the
144        // callback is called, the driver runtime owns the contents of that
145        // object and we will not manipulate it. So even though the Arc only
146        // gives us a shared reference, it's fine to give the runtime a
147        // mutable pointer to it.
148        let res = unsafe {
149            let task_ptr = &raw mut (*UnsafeCell::raw_get(task_cell)).task;
150            Status::ok(async_post_task(self.as_async_dispatcher_ref().0.as_ptr(), task_ptr))
151        };
152        if res.is_err() {
153            // SAFETY: `TaskFunc::call` will never be called now so dispose of
154            // the long-lived reference we just created.
155            unsafe { Arc::decrement_strong_count(task_cell) }
156        }
157        res
158    }
159}
160
161impl<T> AsAsyncDispatcherRef for Arc<T>
162where
163    T: AsAsyncDispatcherRef,
164{
165    fn as_async_dispatcher_ref(&self) -> AsyncDispatcherRef<'_> {
166        (**self).as_async_dispatcher_ref()
167    }
168}
169
170impl<'a> AsAsyncDispatcherRef for AsyncDispatcherRef<'a> {
171    fn as_async_dispatcher_ref(&self) -> AsyncDispatcherRef<'_> {
172        *self
173    }
174}
175
176/// A trait for things that can be represented as an [`AsyncDispatcher`].
177///
178/// This is automatically implemented for things that implement [`AsAsyncDispatcherRef`],
179/// but may be implemented by other things that have more logic to how they obtain the correct
180/// dispatcher object.
181pub trait GetAsyncDispatcher {
182    /// Returns a refcounted handle to the active dispatcher for this object, if there is one.
183    /// Some types of dispatchers (like for the current dispatcher of a thread) may not always have
184    /// an active dispatcher, so it is returned as an option.
185    fn try_get_async_dispatcher(&self) -> Option<AsyncDispatcher>;
186
187    /// Returns a refcounted handle to the active dispatcher for this object.
188    ///
189    /// # Panics
190    ///
191    /// Some types of dispatchers (like for the current dispatcher of a thread) may not always have
192    /// an active dispatcher, in which case this will panic. If you need to be able to handle there
193    /// not being an active dispatcher, use [`Self::try_get_async_dispatcher`].
194    fn get_async_dispatcher(&self) -> AsyncDispatcher {
195        self.try_get_async_dispatcher().expect("No current async dispatcher")
196    }
197}
198
199impl<T> GetAsyncDispatcher for T
200where
201    T: AsAsyncDispatcherRef,
202{
203    fn try_get_async_dispatcher(&self) -> Option<AsyncDispatcher> {
204        Some(AsyncDispatcher::new(self))
205    }
206}
207
208/// A trait that can be used to access a lifetime-constrained dispatcher in a generic way.
209pub trait OnDispatcher: GetAsyncDispatcher + Clone + Send + Sync {
210    /// Runs the function `f` with a lifetime-bound [`AsyncDispatcherRef`] for this object's dispatcher.
211    /// If the dispatcher is no longer valid, the callback will be given [`None`].
212    ///
213    /// Note that it is *very important* that no blocking work be done in this callback to prevent
214    /// long lived strong references to dispatchers that might be shutting down.
215    fn on_dispatcher<R>(&self, f: impl FnOnce(Option<AsyncDispatcherRef<'_>>) -> R) -> R;
216
217    /// Helper version of [`OnDispatcher::on_dispatcher`] that translates an invalidated dispatcher
218    /// handle into a [`Status::BAD_STATE`] error instead of giving the callback [`None`].
219    ///
220    /// Note that it is *very important* that no blocking work be done in this callback to prevent
221    /// long lived strong references to dispatchers that might be shutting down.
222    fn on_maybe_dispatcher<R, E: From<Status>>(
223        &self,
224        f: impl FnOnce(AsyncDispatcherRef<'_>) -> Result<R, E>,
225    ) -> Result<R, E>;
226
227    /// Spawn an asynchronous task on this dispatcher. If this returns [`Ok`] then the task has
228    /// successfully been scheduled and will run or be cancelled and dropped when the dispatcher
229    /// shuts down. The returned future's result will be [`Ok`] if the future completed
230    /// successfully, or an [`Err`] if the task did not complete for some reason (like the
231    /// dispatcher shut down).
232    ///
233    /// Returns a [`JoinHandle`] that will detach the future when dropped.
234    fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) -> JoinHandle<()>
235    where
236        Self: 'static;
237
238    /// Spawn an asynchronous task that outputs type 'T' on this dispatcher. The returned future's
239    /// result will be [`Ok`] if the task was started and completed successfully, or an [`Err`] if
240    /// the task couldn't be started or failed to complete (for example because the dispatcher was
241    /// shutting down).
242    ///
243    /// Returns a [`Task`] that will cancel the future when dropped.
244    ///
245    /// TODO(470088116): This may be the cause of some flakes, so care should be used with it
246    /// in critical paths for now.
247    fn compute<T: Send + 'static>(
248        &self,
249        future: impl Future<Output = T> + Send + 'static,
250    ) -> Task<T>
251    where
252        Self: 'static;
253}
254
255impl<D: GetAsyncDispatcher + Clone + Send + Sync> OnDispatcher for D {
256    fn on_dispatcher<R>(&self, f: impl FnOnce(Option<AsyncDispatcherRef<'_>>) -> R) -> R {
257        if let Some(dispatcher) = self.try_get_async_dispatcher() {
258            f(Some(dispatcher.as_async_dispatcher_ref()))
259        } else {
260            f(None)
261        }
262    }
263
264    fn on_maybe_dispatcher<R, E: From<Status>>(
265        &self,
266        f: impl FnOnce(AsyncDispatcherRef<'_>) -> Result<R, E>,
267    ) -> Result<R, E> {
268        self.on_dispatcher(|dispatcher| {
269            let dispatcher = dispatcher.ok_or(Status::BAD_STATE)?;
270            f(dispatcher)
271        })
272    }
273
274    fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) -> JoinHandle<()>
275    where
276        Self: 'static,
277    {
278        self.compute(future).detach_on_drop()
279    }
280
281    fn compute<T: Send + 'static>(
282        &self,
283        future: impl Future<Output = T> + Send + 'static,
284    ) -> Task<T>
285    where
286        Self: 'static,
287    {
288        match self.try_get_async_dispatcher() {
289            Some(dispatcher) => Task::start(future, dispatcher),
290            None => Task::new_failed(Status::BAD_STATE),
291        }
292    }
293}
294
295/// A marker trait for a callback that can be used with [`Dispatcher::post_task_sync`].
296pub trait TaskCallback: FnOnce(Status) + 'static + Send {}
297impl<T> TaskCallback for T where T: FnOnce(Status) + 'static + Send {}
298
299#[repr(C)]
300struct TaskFunc {
301    task: async_task,
302    func: Box<dyn TaskCallback>,
303}
304
305impl TaskFunc {
306    extern "C" fn call(dispatcher: *mut async_dispatcher, task: *mut async_task, status: i32) {
307        // SAFETY: The async api will only call this function on a valid dispatcher (even if it's
308        // shutting down).
309        let dispatcher =
310            unsafe { AsyncDispatcherRef::from_raw(NonNull::new_unchecked(dispatcher)) };
311        // SAFETY: the async api promises that this function will only be called
312        // up to once, so we can reconstitute the `Arc` and let it get dropped.
313        let task = unsafe { Arc::from_raw(task as *const UnsafeCell<Self>) };
314        // SAFETY: if we can't get a mut ref from the arc, then the task is already
315        // being cancelled, so we don't want to call it.
316        if let Ok(task) = Arc::try_unwrap(task) {
317            CurrentDispatcher::with(&dispatcher, move || {
318                (task.into_inner().func)(Status::from_raw(status));
319            });
320        }
321    }
322}