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