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