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}