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}