fdf_env/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 driver runtime environment.
6
7#![allow(unused)]
8
9use fdf_sys::*;
10
11use core::ffi;
12use core::marker::PhantomData;
13use core::ptr::{null_mut, NonNull};
14
15use zx::Status;
16
17use fdf::{Dispatcher, DispatcherBuilder, DispatcherRef, ShutdownObserver};
18
19/// Create the dispatcher as configured by this object. This must be called from a
20/// thread managed by the driver runtime. The dispatcher returned is owned by the caller,
21/// and will initiate asynchronous shutdown when the object is dropped unless
22/// [`Dispatcher::release`] is called on it to convert it into an unowned [`DispatcherRef`].
23///
24fn create_with_driver<'a>(
25 dispatcher: DispatcherBuilder,
26 driver: DriverRefTypeErased<'a>,
27) -> Result<Dispatcher, Status> {
28 let mut out_dispatcher = null_mut();
29 let owner = driver.0;
30 let options = dispatcher.options;
31 let name = dispatcher.name.as_ptr() as *mut ffi::c_char;
32 let name_len = dispatcher.name.len();
33 let scheduler_role = dispatcher.scheduler_role.as_ptr() as *mut ffi::c_char;
34 let scheduler_role_len = dispatcher.scheduler_role.len();
35 let observer =
36 dispatcher.shutdown_observer.unwrap_or_else(|| ShutdownObserver::new(|_| {})).into_ptr();
37 // SAFETY: all arguments point to memory that will be available for the duration
38 // of the call, except `observer`, which will be available until it is unallocated
39 // by the dispatcher exit handler.
40 Status::ok(unsafe {
41 fdf_env_dispatcher_create_with_owner(
42 owner,
43 options,
44 name,
45 name_len,
46 scheduler_role,
47 scheduler_role_len,
48 observer,
49 &mut out_dispatcher,
50 )
51 })?;
52 // SAFETY: `out_dispatcher` is valid by construction if `fdf_dispatcher_create` returns
53 // ZX_OK.
54 Ok(unsafe { Dispatcher::from_raw(NonNull::new_unchecked(out_dispatcher)) })
55}
56
57/// As with [`create_with_driver`], this creates a new dispatcher as configured by this object, but
58/// instead of returning an owned reference it immediately releases the reference to be
59/// managed by the driver runtime.
60///
61/// # Safety
62///
63/// |owner| must outlive the dispatcher. You can use the shutdown_observer to find out when it is
64/// safe to drop it.
65fn create_with_driver_released<'a>(
66 dispatcher: DispatcherBuilder,
67 driver: DriverRefTypeErased<'a>,
68) -> Result<DispatcherRef<'static>, Status> {
69 create_with_driver(dispatcher, driver).map(Dispatcher::release)
70}
71
72pub trait DriverShutdownObserverFn<T: 'static>:
73 FnOnce(DriverRef<'static, T>) + Send + Sync + 'static
74{
75}
76impl<T, U: 'static> DriverShutdownObserverFn<U> for T where
77 T: FnOnce(DriverRef<'static, U>) + Send + Sync + 'static
78{
79}
80
81/// A shutdown observer for [`fdf_dispatcher_create`] that can call any kind of callback instead of
82/// just a C-compatible function when a dispatcher is shutdown.
83///
84/// # Safety
85///
86/// This object relies on a specific layout to allow it to be cast between a
87/// `*mut fdf_dispatcher_shutdown_observer` and a `*mut ShutdownObserver`. To that end,
88/// it is important that this struct stay both `#[repr(C)]` and that `observer` be its first member.
89#[repr(C)]
90pub struct DriverShutdownObserver<T: 'static> {
91 observer: fdf_env_driver_shutdown_observer,
92 shutdown_fn: Box<dyn DriverShutdownObserverFn<T>>,
93 driver: Driver<T>,
94}
95
96impl<T: 'static> DriverShutdownObserver<T> {
97 /// Creates a new [`ShutdownObserver`] with `f` as the callback to run when a dispatcher
98 /// finishes shutting down.
99 fn new<F: DriverShutdownObserverFn<T>>(driver: Driver<T>, f: F) -> Self {
100 let shutdown_fn = Box::new(f);
101 Self {
102 observer: fdf_env_driver_shutdown_observer { handler: Some(Self::handler) },
103 shutdown_fn,
104 driver,
105 }
106 }
107
108 /// Begins the driver shutdown procedure.
109 /// Turns this object into a stable pointer suitable for passing to
110 /// [`fdf_env_shutdown_dispatchers_async`] by wrapping it in a [`Box`] and leaking it
111 /// to be reconstituded by [`Self::handler`] when the dispatcher is shut down.
112 fn begin(self) -> Result<(), Status> {
113 let driver = self.driver.inner.as_ptr() as *const _;
114 // Note: this relies on the assumption that `self.observer` is at the beginning of the
115 // struct.
116 let this = Box::into_raw(Box::new(self)) as *mut _;
117 // SAFTEY: driver is owned by the driver framework and will be kept alive until the handler
118 // callback is triggered
119 if let Err(e) = Status::ok(unsafe { fdf_env_shutdown_dispatchers_async(driver, this) }) {
120 // SAFTEY: The framework didn't actually take ownership of the object if the call
121 // fails, so we can recover it to avoid leaking.
122 let _ = unsafe { Box::from_raw(this as *mut DriverShutdownObserver<T>) };
123 return Err(e);
124 }
125 Ok(())
126 }
127
128 /// The callback that is registered with the driver that will be called when the driver
129 /// is shut down.
130 ///
131 /// # Safety
132 ///
133 /// This function should only ever be called by the driver runtime at dispatcher shutdown
134 /// time, must only ever be called once for any given [`ShutdownObserver`] object, and
135 /// that [`ShutdownObserver`] object must have previously been made into a pointer by
136 /// [`Self::into_ptr`].
137 unsafe extern "C" fn handler(
138 driver: *const ffi::c_void,
139 observer: *mut fdf_env_driver_shutdown_observer_t,
140 ) {
141 // SAFETY: The driver framework promises to only call this function once, so we can
142 // safely take ownership of the [`Box`] and deallocate it when this function ends.
143 let observer = unsafe { Box::from_raw(observer as *mut DriverShutdownObserver<T>) };
144 (observer.shutdown_fn)(DriverRef(driver as *const T, PhantomData));
145 }
146}
147
148#[derive(Debug)]
149pub struct Driver<T> {
150 pub(crate) inner: NonNull<T>,
151 shutdown_triggered: bool,
152}
153
154/// SAFETY: This inner pointer is movable across threads.
155unsafe impl<T: Send> Send for Driver<T> {}
156
157impl<T: 'static> Driver<T> {
158 /// Returns a builder capable of creating a new dispatcher. Note that this dispatcher cannot
159 /// outlive the driver and is only capable of being stopped by shutting down the driver. It is
160 /// meant to be created to serve as the initial or default dispatcher for a driver.
161 pub fn new_dispatcher(
162 &self,
163 dispatcher: DispatcherBuilder,
164 ) -> Result<DispatcherRef<'static>, Status> {
165 create_with_driver_released(dispatcher, self.as_ref_type_erased())
166 }
167
168 // Run a closure in the context of a driver.
169 pub fn enter<R>(&mut self, f: impl FnOnce() -> R) -> R {
170 unsafe { fdf_env_register_driver_entry(self.inner.as_ptr() as *const _) };
171 let res = f();
172 unsafe { fdf_env_register_driver_exit() };
173 res
174 }
175
176 pub fn add_allowed_scheduler_role(&self, scheduler_role: &str) {
177 let driver_ptr = self.inner.as_ptr() as *const _;
178 let scheduler_role_ptr = scheduler_role.as_ptr() as *mut ffi::c_char;
179 let scheduler_role_len = scheduler_role.len();
180 unsafe {
181 fdf_env_add_allowed_scheduler_role_for_driver(
182 driver_ptr,
183 scheduler_role_ptr,
184 scheduler_role_len,
185 )
186 };
187 }
188
189 // Asynchronously shuts down all dispatchers owned by |driver|.
190 // |f| will be called once shutdown completes. This is guaranteed to be
191 // after all the dispatcher's shutdown observers have been called, and will be running
192 // on the thread of the final dispatcher which has been shutdown.
193 pub fn shutdown<F: DriverShutdownObserverFn<T>>(mut self, f: F) {
194 self.shutdown_triggered = true;
195 // It should be impossible for this to fail as we ensure we are the only caller of this
196 // API, so it cannot be triggered twice nor before the driver has been registered with the
197 // framework.
198 DriverShutdownObserver::new(self, f)
199 .begin()
200 .expect("Unexpectedly failed start shutdown procedure")
201 }
202
203 /// Create a reference to a driver without ownership. The returned reference lacks the ability
204 /// to perform most actions available to the owner of the driver, therefore it doesn't need to
205 /// have it's lifetime tracked closely.
206 pub fn as_ref_type_erased<'a>(&'a self) -> DriverRefTypeErased<'a> {
207 DriverRefTypeErased(self.inner.as_ptr() as *const _, PhantomData)
208 }
209
210 pub fn release(self) -> DriverRef<'static, T> {
211 DriverRef(self.inner.as_ptr() as *const _, PhantomData)
212 }
213}
214
215impl<T> Drop for Driver<T> {
216 fn drop(&mut self) {
217 assert!(self.shutdown_triggered, "Cannot drop driver, must call shutdown method instead");
218 }
219}
220
221// Note that inner type is not guaranteed to not be null.
222#[derive(Clone, Copy)]
223pub struct DriverRefTypeErased<'a>(*const ffi::c_void, PhantomData<&'a u32>);
224
225impl Default for DriverRefTypeErased<'_> {
226 fn default() -> Self {
227 DriverRefTypeErased(std::ptr::null(), PhantomData)
228 }
229}
230
231pub struct DriverRef<'a, T>(pub *const T, PhantomData<&'a Driver<T>>);
232
233pub struct Environment;
234
235impl Environment {
236 pub const ENFORCE_ALLOWED_SCHEDULER_ROLES: u32 = 1;
237
238 /// Start the driver runtime. This sets up the initial thread that the dispatchers run on.
239 pub fn start(options: u32) -> Result<Environment, Status> {
240 // SAFETY: calling fdf_env_start, which does not have any soundness
241 // concerns for rust code. It may be called multiple times without any problems.
242 Status::ok(unsafe { fdf_env_start(options) })?;
243 Ok(Self)
244 }
245
246 /// Creates a new driver. It is expected that the driver passed in is a leaked pointer which
247 /// will only be recovered by triggering the shutdown method on the driver.
248 ///
249 /// # Panics
250 ///
251 /// This method will panic if |driver| is not null.
252 pub fn new_driver<T>(&self, driver: *const T) -> Driver<T> {
253 // We cast to *mut because there is not equivlaent version of NonNull for *const T.
254 Driver {
255 inner: NonNull::new(driver as *mut _).expect("driver must not be null"),
256 shutdown_triggered: false,
257 }
258 }
259
260 /// Calls |f| with the driver on top of the the thread's current call stack. If no drivers
261 /// ar currently on the stack, it will pass in None instead.
262 pub fn with_current_driver<R>(f: impl FnOnce(Option<DriverRefTypeErased<'_>>) -> R) -> R {
263 let driver = unsafe { fdf_env_get_current_driver() };
264 if driver.is_null() {
265 f(None)
266 } else {
267 f(Some(DriverRefTypeErased(driver, PhantomData)))
268 }
269 }
270
271 // TODO: Consider tracking all drivers and providing a method to shutdown all outstanding
272 // drivers and block until they've all finished shutting down.
273
274 /// Returns whether the current thread is managed by the driver runtime or not.
275 fn current_thread_managed_by_driver_runtime() -> bool {
276 // Safety: Calling fdf_dispatcher_get_current_dispatcher from any thread is safe. Because
277 // we are not actually using the dispatcher, we don't need to worry about it's lifetime.
278 !unsafe { fdf_dispatcher_get_current_dispatcher().is_null() }
279 }
280
281 /// Resets the driver runtime to zero threads. This may only be called when there are no
282 /// existing dispatchers.
283 ///
284 /// # Panics
285 ///
286 /// This method should not be called from a thread managed by the driver runtime,
287 /// such as from tasks or ChannelRead callbacks.
288 pub fn reset(&self) {
289 assert!(
290 Self::current_thread_managed_by_driver_runtime() == false,
291 "reset must be called from a thread not managed by the driver runtime"
292 );
293 // SAFETY: calling fdf_env_reset, which does not have any soundness
294 // concerns for rust code. It may be called multiple times without any problems.
295 unsafe { fdf_env_reset() };
296 }
297
298 /// Destroys all dispatchers in the process and blocks the current thread
299 /// until each runtime dispatcher in the process is observed to have been destroyed.
300 ///
301 /// This should only be used called after all drivers have been shutdown.
302 ///
303 /// # Panics
304 ///
305 /// This method should not be called from a thread managed by the driver runtime,
306 /// such as from tasks or ChannelRead callbacks.
307 pub fn destroy_all_dispatchers(&self) {
308 assert!(Self::current_thread_managed_by_driver_runtime() == false,
309 "destroy_all_dispatchers must be called from a thread not managed by the driver runtime");
310 unsafe { fdf_env_destroy_all_dispatchers() };
311 }
312
313 /// Returns whether the dispatcher has any queued tasks.
314 pub fn dispatcher_has_queued_tasks(&self, dispatcher: DispatcherRef<'_>) -> bool {
315 unsafe { fdf_env_dispatcher_has_queued_tasks(dispatcher.inner().as_ptr()) }
316 }
317
318 /// Returns the current maximum number of threads which will be spawned for thread pool associated
319 /// with the given scheduler role.
320 ///
321 /// |scheduler_role| is the name of the role which is passed when creating dispatchers.
322 pub fn get_thread_limit(&self, scheduler_role: &str) -> u32 {
323 let scheduler_role_ptr = scheduler_role.as_ptr() as *mut ffi::c_char;
324 let scheduler_role_len = scheduler_role.len();
325 unsafe { fdf_env_get_thread_limit(scheduler_role_ptr, scheduler_role_len) }
326 }
327
328 /// Sets the number of threads which will be spawned for thread pool associated with the given
329 /// scheduler role. It cannot shrink the limit less to a value lower than the current number of
330 /// threads in the thread pool.
331 ///
332 /// |scheduler_role| is the name of the role which is passed when creating dispatchers.
333 /// |max_threads| is the number of threads to use as new limit.
334 pub fn set_thread_limit(&self, scheduler_role: &str, max_threads: u32) -> Result<(), Status> {
335 let scheduler_role_ptr = scheduler_role.as_ptr() as *mut ffi::c_char;
336 let scheduler_role_len = scheduler_role.len();
337 Status::ok(unsafe {
338 fdf_env_set_thread_limit(scheduler_role_ptr, scheduler_role_len, max_threads)
339 })
340 }
341}
342
343pub mod test {
344 use std::sync::{mpsc, Arc};
345
346 use super::*;
347 use fdf::*;
348
349 pub fn with_raw_dispatcher<T>(
350 name: &str,
351 p: impl for<'a> FnOnce(DispatcherRef<'static>) -> T,
352 ) -> T {
353 with_raw_dispatcher_etc(name, true, false, p)
354 }
355
356 pub fn with_raw_dispatcher_etc<T>(
357 name: &str,
358 allow_thread_blocking: bool,
359 unsynchronized: bool,
360 p: impl for<'a> FnOnce(DispatcherRef<'static>) -> T,
361 ) -> T {
362 let env = Arc::new(Environment::start(0).unwrap());
363 let env_clone = env.clone();
364
365 let (shutdown_tx, shutdown_rx) = mpsc::channel();
366 let driver_value: u32 = 0x1337;
367 let driver_value_ptr = &driver_value as *const u32;
368 let driver = env.new_driver(driver_value_ptr);
369 let dispatcher = DispatcherBuilder::new().name(name);
370 let dispatcher =
371 if allow_thread_blocking { dispatcher.allow_thread_blocking() } else { dispatcher };
372 let dispatcher = if unsynchronized { dispatcher.unsynchronized() } else { dispatcher };
373 let dispatcher = dispatcher.shutdown_observer(move |dispatcher| {
374 // We verify that the dispatcher has no tasks left queued in it,
375 // just because this is testing code.
376 assert!(!env_clone.dispatcher_has_queued_tasks(dispatcher.as_dispatcher_ref()));
377 });
378 let dispatcher = driver.new_dispatcher(dispatcher).unwrap();
379
380 let res = p(dispatcher.clone());
381
382 // This initiates the dispatcher shutdown on a driver runtime
383 // thread. When all tasks on the dispatcher have completed, the wait
384 // on the shutdown_rx below will end and we can tear it down.
385 drop(dispatcher);
386 driver.shutdown(move |driver| {
387 // SAFTEY: driver lives on the stack, it's safe to dereference it.
388 assert!(unsafe { *driver.0 } == 0x1337);
389 shutdown_tx.send(()).unwrap();
390 });
391
392 shutdown_rx.recv().unwrap();
393
394 res
395 }
396}