Skip to main content

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, DriverDispatcherRef};
19use fdf_core::shutdown_observer::ShutdownObserver;
20
21pub mod test;
22
23const FDF_DISPATCHER_STATE_RUNNING: fdf_dispatcher_state_t = 0;
24const FDF_DISPATCHER_STATE_SHUTTING_DOWN: fdf_dispatcher_state_t = 1;
25const FDF_DISPATCHER_STATE_SHUTDOWN: fdf_dispatcher_state_t = 2;
26const FDF_DISPATCHER_STATE_DESTROYED: fdf_dispatcher_state_t = 3;
27
28/// Create the dispatcher as configured by this object. This must be called from a
29/// thread managed by the driver runtime. The dispatcher returned is owned by the caller,
30/// and will initiate asynchronous shutdown when the object is dropped unless
31/// [`Dispatcher::release`] is called on it to convert it into an unowned [`DispatcherRef`].
32fn create_with_driver<'a>(
33    dispatcher: DispatcherBuilder,
34    driver: DriverRefTypeErased<'a>,
35) -> Result<Dispatcher, Status> {
36    let mut out_dispatcher = null_mut();
37    let owner = driver.0;
38    let options = dispatcher.options;
39    let name = dispatcher.name.as_ptr() as *mut ffi::c_char;
40    let name_len = dispatcher.name.len();
41    let scheduler_role = dispatcher.scheduler_role.as_ptr() as *mut ffi::c_char;
42    let scheduler_role_len = dispatcher.scheduler_role.len();
43    let observer =
44        ShutdownObserver::new(dispatcher.shutdown_observer.unwrap_or_else(|| Box::new(|_| {})))
45            .into_ptr();
46    // SAFETY: all arguments point to memory that will be available for the duration
47    // of the call, except `observer`, which will be available until it is unallocated
48    // by the dispatcher exit handler.
49    Status::ok(unsafe {
50        fdf_env_dispatcher_create_with_owner(
51            owner,
52            options,
53            name,
54            name_len,
55            scheduler_role,
56            scheduler_role_len,
57            observer,
58            &mut out_dispatcher,
59        )
60    })?;
61    // SAFETY: `out_dispatcher` is valid by construction if `fdf_dispatcher_create` returns
62    // ZX_OK.
63    Ok(unsafe { Dispatcher::from_raw(NonNull::new_unchecked(out_dispatcher)) })
64}
65
66/// A marker trait for a function that can be used as a driver shutdown observer with
67/// [`Driver::shutdown`].
68pub trait DriverShutdownObserverFn<T: 'static>:
69    FnOnce(DriverRef<'static, T>) + Send + Sync + 'static
70{
71}
72impl<T, U: 'static> DriverShutdownObserverFn<U> for T where
73    T: FnOnce(DriverRef<'static, U>) + Send + Sync + 'static
74{
75}
76
77/// A shutdown observer for [`fdf_dispatcher_create`] that can call any kind of callback instead of
78/// just a C-compatible function when a dispatcher is shutdown.
79///
80/// # Safety
81///
82/// This object relies on a specific layout to allow it to be cast between a
83/// `*mut fdf_dispatcher_shutdown_observer` and a `*mut ShutdownObserver`. To that end,
84/// it is important that this struct stay both `#[repr(C)]` and that `observer` be its first member.
85#[repr(C)]
86struct DriverShutdownObserver<T: 'static> {
87    observer: fdf_env_driver_shutdown_observer,
88    shutdown_fn: Box<dyn DriverShutdownObserverFn<T>>,
89    driver: Driver<T>,
90}
91
92impl<T: 'static> DriverShutdownObserver<T> {
93    /// Creates a new [`ShutdownObserver`] with `f` as the callback to run when a dispatcher
94    /// finishes shutting down.
95    fn new<F: DriverShutdownObserverFn<T>>(driver: Driver<T>, f: F) -> Self {
96        let shutdown_fn = Box::new(f);
97        Self {
98            observer: fdf_env_driver_shutdown_observer { handler: Some(Self::handler) },
99            shutdown_fn,
100            driver,
101        }
102    }
103
104    /// Begins the driver shutdown procedure.
105    /// Turns this object into a stable pointer suitable for passing to
106    /// [`fdf_env_shutdown_dispatchers_async`] by wrapping it in a [`Box`] and leaking it
107    /// to be reconstituded by [`Self::handler`] when the dispatcher is shut down.
108    fn begin(self) -> Result<(), Status> {
109        let driver = self.driver.inner.as_ptr() as *const _;
110        // Note: this relies on the assumption that `self.observer` is at the beginning of the
111        // struct.
112        let this = Box::into_raw(Box::new(self)) as *mut _;
113        // SAFETY: driver is owned by the driver framework and will be kept alive until the handler
114        // callback is triggered
115        if let Err(e) = Status::ok(unsafe { fdf_env_shutdown_dispatchers_async(driver, this) }) {
116            // SAFETY: The framework didn't actually take ownership of the object if the call
117            // fails, so we can recover it to avoid leaking.
118            let _ = unsafe { Box::from_raw(this as *mut DriverShutdownObserver<T>) };
119            return Err(e);
120        }
121        Ok(())
122    }
123
124    /// The callback that is registered with the driver that will be called when the driver
125    /// is shut down.
126    ///
127    /// # Safety
128    ///
129    /// This function should only ever be called by the driver runtime at dispatcher shutdown
130    /// time, must only ever be called once for any given [`ShutdownObserver`] object, and
131    /// that [`ShutdownObserver`] object must have previously been made into a pointer by
132    /// [`Self::into_ptr`].
133    unsafe extern "C" fn handler(
134        driver: *const ffi::c_void,
135        observer: *mut fdf_env_driver_shutdown_observer_t,
136    ) {
137        // SAFETY: The driver framework promises to only call this function once, so we can
138        // safely take ownership of the [`Box`] and deallocate it when this function ends.
139        let observer = unsafe { Box::from_raw(observer as *mut DriverShutdownObserver<T>) };
140        (observer.shutdown_fn)(DriverRef(driver as *const T, PhantomData));
141    }
142}
143
144/// An owned handle to a Driver instance that can be used to create initial dispatchers.
145#[derive(Debug)]
146pub struct Driver<T> {
147    pub(crate) inner: NonNull<T>,
148    shutdown_triggered: bool,
149}
150
151/// An unowned handle to the driver that is returned through certain environment APIs like
152/// |get_driver_on_thread_koid|.
153pub struct UnownedDriver {
154    inner: *const ffi::c_void,
155}
156
157/// SAFETY: This inner pointer is movable across threads.
158unsafe impl<T: Send> Send for Driver<T> {}
159
160impl<T: 'static> Driver<T> {
161    /// Constructs a dispatcher from the given builder on this driver. Note that this dispatcher
162    /// cannot outlive the driver and is only capable of being stopped by shutting down the driver.
163    /// It is meant to be created to serve as the initial or default dispatcher for a driver.
164    ///
165    /// The caller should make sure that the dispatcher is released so the driver runtime will
166    /// manage shutting it down, but that may be done differently in test contexts so it does not
167    /// force it.
168    pub fn new_dispatcher(&self, dispatcher: DispatcherBuilder) -> Result<Dispatcher, Status> {
169        create_with_driver(dispatcher, self.as_ref_type_erased())
170    }
171
172    /// Run a closure in the context of a driver.
173    pub fn enter<R>(&mut self, f: impl FnOnce() -> R) -> R {
174        unsafe { fdf_env_register_driver_entry(self.inner.as_ptr() as *const _) };
175        let res = f();
176        unsafe { fdf_env_register_driver_exit() };
177        res
178    }
179
180    /// Adds an allowed scheduler role to the driver
181    pub fn add_allowed_scheduler_role(&self, scheduler_role: &str) {
182        let driver_ptr = self.inner.as_ptr() as *const _;
183        let scheduler_role_ptr = scheduler_role.as_ptr() as *mut ffi::c_char;
184        let scheduler_role_len = scheduler_role.len();
185        unsafe {
186            fdf_env_add_allowed_scheduler_role_for_driver(
187                driver_ptr,
188                scheduler_role_ptr,
189                scheduler_role_len,
190            )
191        };
192    }
193
194    /// Registers a callback which is triggered whenever the runtime needs to be resumed.
195    /// Returns a registration handle that unregisters and frees the requester when dropped.
196    pub fn register_resume_requester(
197        &self,
198        requester: ResumeRequester,
199    ) -> ResumeRequesterRegistration {
200        let driver_ptr = self.inner.as_ptr() as *const _;
201        let requester_ptr = requester.into_ptr();
202
203        // SAFETY: requester_ptr is used by the driver runtime as a callback function.
204        // The driver runtime does not manage this object's lifetime. driver_ptr is not modified
205        // by the runtime.
206        unsafe {
207            fdf_sys::fdf_env_register_resume_requester(driver_ptr, requester_ptr);
208        }
209        ResumeRequesterRegistration { driver_ptr, requester_ptr }
210    }
211
212    /// Asynchronously suspends the dispatchers owned by the driver.
213    pub fn driver_suspend(&self, completer: SuspendCompleter) {
214        unsafe {
215            fdf_sys::fdf_env_driver_suspend(self.inner.as_ptr() as *const _, completer.into_ptr());
216        }
217    }
218
219    /// Resumes the dispatchers owned by the driver.
220    pub fn driver_resume(&self) {
221        unsafe {
222            fdf_sys::fdf_env_driver_resume(self.inner.as_ptr() as *const _);
223        }
224    }
225
226    /// Asynchronously shuts down all dispatchers owned by |driver|.
227    /// |f| will be called once shutdown completes. This is guaranteed to be
228    /// after all the dispatcher's shutdown observers have been called, and will be running
229    /// on the thread of the final dispatcher which has been shutdown.
230    pub fn shutdown<F: DriverShutdownObserverFn<T>>(mut self, f: F) {
231        self.shutdown_triggered = true;
232        // It should be impossible for this to fail as we ensure we are the only caller of this
233        // API, so it cannot be triggered twice nor before the driver has been registered with the
234        // framework.
235        DriverShutdownObserver::new(self, f)
236            .begin()
237            .expect("Unexpectedly failed start shutdown procedure")
238    }
239
240    /// Create a reference to a driver without ownership. The returned reference lacks the ability
241    /// to perform most actions available to the owner of the driver, therefore it doesn't need to
242    /// have it's lifetime tracked closely.
243    fn as_ref_type_erased<'a>(&'a self) -> DriverRefTypeErased<'a> {
244        DriverRefTypeErased(self.inner.as_ptr() as *const _, PhantomData)
245    }
246
247    /// Releases ownership of this driver instance, allowing it to be shut down when the runtime
248    /// shuts down.
249    pub fn release(self) -> DriverRef<'static, T> {
250        DriverRef(self.inner.as_ptr() as *const _, PhantomData)
251    }
252}
253
254impl<T> Drop for Driver<T> {
255    fn drop(&mut self) {
256        assert!(self.shutdown_triggered, "Cannot drop driver, must call shutdown method instead");
257    }
258}
259
260impl<T> PartialEq<UnownedDriver> for Driver<T> {
261    fn eq(&self, other: &UnownedDriver) -> bool {
262        self.inner.as_ptr() as *const _ == other.inner
263    }
264}
265
266// Note that inner type is not guaranteed to not be null.
267#[derive(Clone, Copy, PartialEq)]
268struct DriverRefTypeErased<'a>(*const ffi::c_void, PhantomData<&'a u32>);
269
270impl Default for DriverRefTypeErased<'_> {
271    fn default() -> Self {
272        DriverRefTypeErased(std::ptr::null(), PhantomData)
273    }
274}
275
276/// A lifetime-bound reference to a driver handle.
277pub struct DriverRef<'a, T>(pub *const T, PhantomData<&'a Driver<T>>);
278
279/// A marker trait for a function type that can be used as a stall scanner.
280pub trait StallScannerFn: Fn(zx_duration_mono_t) + Send + Sync + 'static {}
281impl<T> StallScannerFn for T where T: Fn(zx_duration_mono_t) + Send + Sync + 'static {}
282
283/// A stall scanner for [`fdf_env_register_stall_scanner`] that can call any kind of callback instead of
284/// just a C-compatible function when a dispatcher is shutdown.
285///
286/// # Safety
287///
288/// This object relies on a specific layout to allow it to be cast between a
289/// `*mut fdf_env_stall_scanner` and a `*mut StallScanner`. To that end,
290/// it is important that this struct stay both `#[repr(C)]` and that `scanner` be its first member.
291#[repr(C)]
292#[doc(hidden)]
293pub struct StallScanner {
294    scanner: fdf_env_stall_scanner,
295    begin_fn: Box<dyn StallScannerFn>,
296}
297
298impl StallScanner {
299    /// Creates a new [`StallScanner`] with `f` as the callback to run when a dispatcher
300    /// finishes shutting down.
301    pub fn new<F: StallScannerFn>(f: F) -> Self {
302        let begin_fn = Box::new(f);
303        Self { scanner: fdf_env_stall_scanner { handler: Some(Self::handler) }, begin_fn }
304    }
305
306    /// Turns this object into a stable pointer suitable for passing to
307    /// [`fdf_env_register_stall_scanner`] by wrapping it in a [`Box`] and leaking it to be reconstituded
308    /// by [`Self::handler`] when the scanner is triggered.
309    pub fn into_ptr(self) -> *mut fdf_env_stall_scanner {
310        // Note: this relies on the assumption that `self.scanner` is at the beginning of the
311        // struct.
312        Box::leak(Box::new(self)) as *mut _ as *mut _
313    }
314
315    /// The callback that is registered with the dispatcher that will be called when the stall
316    /// scanner should begin a scan.
317    ///
318    /// # Safety
319    ///
320    /// The [`StallScanner`] object must have previously been made into a pointer by
321    /// [`Self::into_ptr`].
322    unsafe extern "C" fn handler(
323        scanner: *mut fdf_env_stall_scanner,
324        duration: zx_duration_mono_t,
325    ) {
326        let scanner = scanner as *mut StallScanner;
327
328        unsafe {
329            ((*scanner).begin_fn)(duration);
330        }
331    }
332}
333
334/// A marker trait for a function type that can be used as a resume requester.
335pub trait ResumeRequesterFn: Fn() -> Result<(), Status> + Send + Sync + 'static {}
336impl<T> ResumeRequesterFn for T where T: Fn() -> Result<(), Status> + Send + Sync + 'static {}
337
338/// A resume requester for [`fdf_env_register_resume_requester`] that can call any kind of callback.
339///
340/// # Safety
341///
342/// This object relies on a specific layout to allow it to be cast between a
343/// `*mut fdf_env_resume_requester_t` and a `*mut ResumeRequester`. To that end,
344/// it is important that this struct stay both `#[repr(C)]` and that `requester` be its first member.
345#[repr(C)]
346pub struct ResumeRequester {
347    /// The underlying C structure.
348    pub requester: fdf_env_resume_requester_t,
349    resume_fn: Box<dyn ResumeRequesterFn>,
350}
351
352impl ResumeRequester {
353    /// Creates a new [`ResumeRequester`] with `f` as the callback to run when the runtime needs to be resumed.
354    pub fn new<F: ResumeRequesterFn>(f: F) -> Self {
355        let resume_fn = Box::new(f);
356        Self { requester: fdf_env_resume_requester_t { handler: Some(Self::handler) }, resume_fn }
357    }
358
359    /// Turns this object into a stable pointer suitable for passing to
360    /// [`fdf_env_register_resume_requester`] by wrapping it in a [`Box`] and leaking it to be reconstituded
361    /// by [`Self::handler`] when the runtime needs to be resumed.
362    pub fn into_ptr(self) -> *mut fdf_env_resume_requester_t {
363        Box::leak(Box::new(self)) as *mut _ as *mut _
364    }
365
366    /// The callback that is registered with the dispatcher that will be called when the runtime
367    /// needs to be resumed.
368    ///
369    /// # Safety
370    ///
371    /// The [`ResumeRequester`] object must have previously been made into a pointer by
372    /// [`Self::into_ptr`].
373    unsafe extern "C" fn handler(requester: *mut fdf_env_resume_requester_t) -> i32 {
374        let requester = requester as *mut ResumeRequester;
375        unsafe {
376            match ((*requester).resume_fn)() {
377                Ok(()) => 0,
378                Err(e) => e.into_raw(),
379            }
380        }
381    }
382}
383
384/// A marker trait for a function type that can be used as a suspend completer.
385pub trait SuspendCompleterFn: FnOnce() + Send + Sync + 'static {}
386impl<T> SuspendCompleterFn for T where T: FnOnce() + Send + Sync + 'static {}
387
388/// A suspend completer for [`fdf_env_driver_suspend`] that can call any kind of callback.
389///
390/// # Safety
391///
392/// This object relies on a specific layout to allow it to be cast between a
393/// `*mut fdf_env_suspend_completer_t` and a `*mut SuspendCompleter`. To that end,
394/// it is important that this struct stay both `#[repr(C)]` and that `completer` be its first member.
395#[repr(C)]
396pub struct SuspendCompleter {
397    completer: fdf_env_suspend_completer_t,
398    complete_fn: Box<dyn SuspendCompleterFn>,
399}
400
401impl SuspendCompleter {
402    /// Creates a new [`SuspendCompleter`] with `f` as the callback to run when the runtime finishes suspending.
403    pub fn new<F: SuspendCompleterFn>(f: F) -> Self {
404        let complete_fn = Box::new(f);
405        Self {
406            completer: fdf_env_suspend_completer_t { handler: Some(Self::handler) },
407            complete_fn,
408        }
409    }
410
411    /// Turns this object into a stable pointer suitable for passing to
412    /// [`fdf_env_driver_suspend`] by wrapping it in a [`Box`] and leaking it to be reconstituded
413    /// by [`Self::handler`] when the runtime finishes suspending.
414    pub fn into_ptr(self) -> *mut fdf_env_suspend_completer_t {
415        Box::leak(Box::new(self)) as *mut _ as *mut _
416    }
417
418    /// The callback that is registered with the dispatcher that will be called when the runtime
419    /// finishes suspending.
420    ///
421    /// # Safety
422    ///
423    /// The [`SuspendCompleter`] object must have previously been made into a pointer by
424    /// [`Self::into_ptr`].
425    unsafe extern "C" fn handler(completer: *mut fdf_env_suspend_completer_t) {
426        let completer = completer as *mut SuspendCompleter;
427        unsafe {
428            let completer = Box::from_raw(completer);
429            (completer.complete_fn)();
430        }
431    }
432}
433
434/// The driver runtime environment
435pub struct Environment;
436
437impl Environment {
438    /// Whether the environment should enforce scheduler roles. Used with [`Self::start`].
439    pub const ENFORCE_ALLOWED_SCHEDULER_ROLES: u32 = 1;
440    /// Whether the environment should dynamically spawn threads on-demand for sync call dispatchers.
441    /// Used with [`Self::start`].
442    pub const DYNAMIC_THREAD_SPAWNING: u32 = 2;
443
444    /// Start the driver runtime. This sets up the initial thread that the dispatchers run on.
445    pub fn start(options: u32) -> Result<Environment, Status> {
446        // SAFETY: calling fdf_env_start, which does not have any soundness
447        // concerns for rust code. It may be called multiple times without any problems.
448        Status::ok(unsafe { fdf_env_start(options) })?;
449        Ok(Self)
450    }
451
452    /// Creates a new driver. It is expected that the driver passed in is a leaked pointer which
453    /// will only be recovered by triggering the shutdown method on the driver.
454    ///
455    /// # Panics
456    ///
457    /// This method will panic if |driver| is not null.
458    pub fn new_driver<T>(&self, driver: *const T) -> Driver<T> {
459        // We cast to *mut because there is not equivlaent version of NonNull for *const T.
460        Driver {
461            inner: NonNull::new(driver as *mut _).expect("driver must not be null"),
462            shutdown_triggered: false,
463        }
464    }
465
466    // TODO: Consider tracking all drivers and providing a method to shutdown all outstanding
467    // drivers and block until they've all finished shutting down.
468
469    /// Returns whether the current thread is managed by the driver runtime or not.
470    fn current_thread_managed_by_driver_runtime() -> bool {
471        // Safety: Calling fdf_dispatcher_get_current_dispatcher from any thread is safe. Because
472        // we are not actually using the dispatcher, we don't need to worry about it's lifetime.
473        !unsafe { fdf_dispatcher_get_current_dispatcher().is_null() }
474    }
475
476    /// Resets the driver runtime to zero threads. This may only be called when there are no
477    /// existing dispatchers.
478    ///
479    /// # Panics
480    ///
481    /// This method should not be called from a thread managed by the driver runtime,
482    /// such as from tasks or ChannelRead callbacks.
483    pub fn reset(&self) {
484        assert!(
485            !Self::current_thread_managed_by_driver_runtime(),
486            "reset must be called from a thread not managed by the driver runtime"
487        );
488        // SAFETY: calling fdf_env_reset, which does not have any soundness
489        // concerns for rust code. It may be called multiple times without any problems.
490        unsafe { fdf_env_reset() };
491    }
492
493    /// Destroys all dispatchers in the process and blocks the current thread
494    /// until each runtime dispatcher in the process is observed to have been destroyed.
495    ///
496    /// This should only be used called after all drivers have been shutdown.
497    ///
498    /// # Panics
499    ///
500    /// This method should not be called from a thread managed by the driver runtime,
501    /// such as from tasks or ChannelRead callbacks.
502    pub fn destroy_all_dispatchers(&self) {
503        assert!(
504            !Self::current_thread_managed_by_driver_runtime(),
505            "destroy_all_dispatchers must be called from a thread not managed by the driver runtime"
506        );
507        unsafe { fdf_env_destroy_all_dispatchers() };
508    }
509
510    /// Returns whether the dispatcher has any queued tasks.
511    pub fn dispatcher_has_queued_tasks(&self, dispatcher: DriverDispatcherRef<'_>) -> bool {
512        unsafe {
513            fdf_env_dispatcher_has_queued_tasks(fdf_core::dispatcher_ptr(&dispatcher).as_ptr())
514        }
515    }
516
517    /// Returns structured runtime diagnostic dumps for all dispatchers currently tracked by the
518    /// driver runtime environment.
519    pub fn dump_all_dispatchers(&self) -> Vec<DispatcherDumpEntry> {
520        struct DumpEntriesGuard {
521            ptr: *mut fdf_dispatcher_dump_entry_t,
522            count: usize,
523        }
524
525        impl Drop for DumpEntriesGuard {
526            fn drop(&mut self) {
527                if !self.ptr.is_null() {
528                    // SAFETY: `self.ptr` and `self.count` were populated by a single call to
529                    // `fdf_env_get_all_dispatchers_dump`, which allocated the entry array and its
530                    // nested string/task buffers on the C++ heap and transferred exclusive
531                    // ownership to the caller. `DumpEntriesGuard` is dropped only after all temporary
532                    // slices and `CStr` borrows derived from `self.ptr` have gone out of scope, so
533                    // no dangling references can exist and the allocation is freed at most once.
534                    unsafe {
535                        fdf_env_free_all_dispatchers_dump(self.ptr, self.count);
536                    }
537                }
538            }
539        }
540
541        let mut raw_entries: *mut fdf_dispatcher_dump_entry_t = null_mut();
542        let mut count: usize = 0;
543        // SAFETY: `raw_entries` and `count` are valid, initialized, and properly aligned stack
544        // variables passed via exclusive mutable references (`&mut`). The C++ implementation of
545        // `fdf_env_get_all_dispatchers_dump` writes the newly allocated buffer pointer and element
546        // count into these out-parameters synchronously before returning and does not retain the
547        // addresses of `raw_entries` or `count`, preserving Rust's aliasing and lifetime rules.
548        unsafe {
549            fdf_env_get_all_dispatchers_dump(&mut raw_entries, &mut count);
550        }
551        let guard = DumpEntriesGuard { ptr: raw_entries, count };
552        if guard.ptr.is_null() || guard.count == 0 {
553            return Vec::new();
554        }
555        // SAFETY: We verified that `guard.ptr` is non-null and `guard.count > 0`. By the FFI
556        // contract of `fdf_env_get_all_dispatchers_dump`, `guard.ptr` points to a single
557        // contiguous heap allocation of `guard.count` initialized and properly aligned
558        // `fdf_dispatcher_dump_entry_t` values whose total byte size does not exceed `isize::MAX`.
559        // Exclusive ownership of the allocation was transferred to this function and is held by
560        // `guard` for the entire lifetime of `entries`, ensuring the memory is neither mutated nor
561        // deallocated while the immutable slice borrow is active.
562        let entries = unsafe { core::slice::from_raw_parts(guard.ptr, guard.count) };
563        let mut result = Vec::with_capacity(guard.count);
564        for entry in entries {
565            let name = if entry.name.is_null() {
566                String::new()
567            } else {
568                // SAFETY: `entry.name` was checked to be non-null. `fdf_env_get_all_dispatchers_dump`
569                // initializes `entry.name` via `CopyStringToHeap` (`malloc` + `memcpy` + NUL-terminator)
570                // from a valid string view, guaranteeing a valid, NUL-terminated sequence of bytes
571                // within a single allocation smaller than `isize::MAX`. The underlying buffer is
572                // exclusively owned by `guard` and is not mutated or freed until `guard` is dropped
573                // after this loop, and `.to_string_lossy()` immediately copies the bytes into an
574                // owned Rust `String`.
575                unsafe { ffi::CStr::from_ptr(entry.name) }.to_string_lossy().into_owned()
576            };
577            let scheduler_role = if entry.scheduler_role.is_null() {
578                String::new()
579            } else {
580                // SAFETY: `entry.scheduler_role` was checked to be non-null. By the FFI contract of
581                // `fdf_env_get_all_dispatchers_dump`, it points to a heap-allocated, NUL-terminated
582                // C string created via `CopyStringToHeap` (`malloc` + `memcpy` + NUL-terminator)
583                // with size less than `isize::MAX`. The memory is exclusively owned by `guard`,
584                // remains immutable for the duration of this borrow, and is copied into an owned
585                // `String` before `guard` is dropped.
586                unsafe { ffi::CStr::from_ptr(entry.scheduler_role) }.to_string_lossy().into_owned()
587            };
588            let destroy_context = if entry.destroy_context.is_null() {
589                String::new()
590            } else {
591                // SAFETY: `entry.destroy_context` was checked to be non-null. By the FFI contract of
592                // `fdf_env_get_all_dispatchers_dump`, it points to a heap-allocated, NUL-terminated
593                // C string created via `CopyStringToHeap` (`malloc` + `memcpy` + NUL-terminator)
594                // with size less than `isize::MAX`. The memory is exclusively owned by `guard`,
595                // remains immutable for the duration of this borrow, and is copied into an owned
596                // `String` before `guard` is dropped.
597                unsafe { ffi::CStr::from_ptr(entry.destroy_context) }.to_string_lossy().into_owned()
598            };
599            let state = match entry.state {
600                FDF_DISPATCHER_STATE_RUNNING => DispatcherState::Running,
601                FDF_DISPATCHER_STATE_SHUTTING_DOWN => DispatcherState::ShuttingDown,
602                FDF_DISPATCHER_STATE_SHUTDOWN => DispatcherState::Shutdown,
603                FDF_DISPATCHER_STATE_DESTROYED => DispatcherState::Destroyed,
604                _ => DispatcherState::Running,
605            };
606            let mut queued_tasks = Vec::with_capacity(entry.num_queued_tasks);
607            if !entry.queued_tasks.is_null() && entry.num_queued_tasks > 0 {
608                // SAFETY: We verified that `entry.queued_tasks` is non-null and
609                // `entry.num_queued_tasks > 0`. By the contract of `fdf_env_get_all_dispatchers_dump`,
610                // `entry.queued_tasks` points to a contiguous heap allocation of
611                // `entry.num_queued_tasks` initialized and properly aligned `fdf_task_debug_info_t`
612                // structs whose total byte length is at most `isize::MAX`. The memory is exclusively
613                // owned by `guard` and is not mutated or freed until `guard` is dropped after the
614                // loop finishes copying the scalar fields into `queued_tasks`.
615                let tasks = unsafe {
616                    core::slice::from_raw_parts(entry.queued_tasks, entry.num_queued_tasks)
617                };
618                for task in tasks {
619                    queued_tasks.push(QueuedTaskDebugInfo {
620                        ptr: task.ptr,
621                        handler: task.handler,
622                        initiating_dispatcher: task.initiating_dispatcher,
623                        initiating_driver: task.initiating_driver as u64,
624                    });
625                }
626            }
627            result.push(DispatcherDumpEntry {
628                driver: entry.driver as u64,
629                dispatcher_ptr: entry.dispatcher_ptr,
630                name,
631                scheduler_role,
632                options: entry.options,
633                synchronized: entry.synchronized,
634                allow_sync_calls: entry.allow_sync_calls,
635                state,
636                destroy_context,
637                destroy_user_initiated: entry
638                    .has_destroy_user_initiated
639                    .then_some(entry.destroy_user_initiated),
640                debug_stats: DispatcherDebugStats {
641                    num_total_requests: entry.debug_stats.num_total_requests,
642                    num_inlined_requests: entry.debug_stats.num_inlined_requests,
643                    non_inlined: NonInlinedStats {
644                        allow_sync_calls: entry.debug_stats.non_inlined.allow_sync_calls,
645                        parallel_dispatch: entry.debug_stats.non_inlined.parallel_dispatch,
646                        task: entry.debug_stats.non_inlined.task,
647                        unknown_thread: entry.debug_stats.non_inlined.unknown_thread,
648                        reentrant: entry.debug_stats.non_inlined.reentrant,
649                        channel_wait_not_yet_registered: entry
650                            .debug_stats
651                            .non_inlined
652                            .channel_wait_not_yet_registered,
653                        no_thread_migration: entry.debug_stats.non_inlined.no_thread_migration,
654                    },
655                },
656                queued_tasks,
657            });
658        }
659        result
660    }
661
662    /// Returns structured runtime diagnostic dumps for all threads currently spawned by the
663    /// driver runtime environment.
664    pub fn dump_all_threads(&self) -> Vec<ThreadDumpEntry> {
665        struct ThreadDumpEntriesGuard {
666            ptr: *mut fdf_thread_dump_entry_t,
667            count: usize,
668        }
669
670        impl Drop for ThreadDumpEntriesGuard {
671            fn drop(&mut self) {
672                if !self.ptr.is_null() {
673                    // SAFETY: `self.ptr` and `self.count` were populated by a single call to
674                    // `fdf_env_get_all_threads_dump`, which allocated the entry array and its
675                    // nested string buffers on the C++ heap and transferred exclusive ownership to
676                    // the caller. `ThreadDumpEntriesGuard` is dropped only after all temporary
677                    // slices and `CStr` borrows derived from `self.ptr` have gone out of scope, so
678                    // no dangling references can exist and the allocation is freed at most once.
679                    unsafe {
680                        fdf_env_free_all_threads_dump(self.ptr, self.count);
681                    }
682                }
683            }
684        }
685
686        let mut raw_entries: *mut fdf_thread_dump_entry_t = null_mut();
687        let mut count: usize = 0;
688        // SAFETY: `raw_entries` and `count` are valid, initialized, and properly aligned stack
689        // variables passed via exclusive mutable references (`&mut`). The C++ implementation of
690        // `fdf_env_get_all_threads_dump` writes the newly allocated buffer pointer and element
691        // count into these out-parameters synchronously before returning and does not retain the
692        // addresses of `raw_entries` or `count`, preserving Rust's aliasing and lifetime rules.
693        unsafe {
694            fdf_env_get_all_threads_dump(&mut raw_entries, &mut count);
695        }
696        let guard = ThreadDumpEntriesGuard { ptr: raw_entries, count };
697        if guard.ptr.is_null() || guard.count == 0 {
698            return Vec::new();
699        }
700        // SAFETY: We verified that `guard.ptr` is non-null and `guard.count > 0`. By the FFI
701        // contract of `fdf_env_get_all_threads_dump`, `guard.ptr` points to a single contiguous
702        // heap allocation of `guard.count` initialized and properly aligned
703        // `fdf_thread_dump_entry_t` values whose total byte size does not exceed `isize::MAX`.
704        // Exclusive ownership of the allocation was transferred to this function and is held by
705        // `guard` for the entire lifetime of `entries`, ensuring the memory is neither mutated nor
706        // deallocated while the immutable slice borrow is active.
707        let entries = unsafe { core::slice::from_raw_parts(guard.ptr, guard.count) };
708        let mut result = Vec::with_capacity(guard.count);
709        for entry in entries {
710            let name = if entry.name.is_null() {
711                String::new()
712            } else {
713                // SAFETY: `entry.name` was checked to be non-null. `fdf_env_get_all_threads_dump`
714                // initializes `entry.name` via `CopyStringToHeap` (`malloc` + `memcpy` + NUL-terminator)
715                // from a valid string view, guaranteeing a valid, NUL-terminated sequence of bytes
716                // within a single allocation smaller than `isize::MAX`. The underlying buffer is
717                // exclusively owned by `guard` and is not mutated or freed until `guard` is dropped
718                // after this loop, and `.to_string_lossy()` immediately copies the bytes into an
719                // owned Rust `String`.
720                unsafe { ffi::CStr::from_ptr(entry.name) }.to_string_lossy().into_owned()
721            };
722            let scheduler_role = if entry.scheduler_role.is_null() {
723                String::new()
724            } else {
725                // SAFETY: `entry.scheduler_role` was checked to be non-null. By the FFI contract of
726                // `fdf_env_get_all_threads_dump`, it points to a heap-allocated, NUL-terminated C
727                // string created via `CopyStringToHeap` (`malloc` + `memcpy` + NUL-terminator) with
728                // size less than `isize::MAX`. The memory is exclusively owned by `guard`, remains
729                // immutable for the duration of this borrow, and is copied into an owned `String`
730                // before `guard` is dropped.
731                unsafe { ffi::CStr::from_ptr(entry.scheduler_role) }.to_string_lossy().into_owned()
732            };
733            result.push(ThreadDumpEntry { koid: entry.koid, name, scheduler_role });
734        }
735        result
736    }
737
738    /// Returns the current maximum number of threads which will be spawned for thread pool associated
739    /// with the given scheduler role.
740    ///
741    /// |scheduler_role| is the name of the role which is passed when creating dispatchers.
742    pub fn get_thread_limit(&self, scheduler_role: &str) -> u32 {
743        let scheduler_role_ptr = scheduler_role.as_ptr() as *mut ffi::c_char;
744        let scheduler_role_len = scheduler_role.len();
745        unsafe { fdf_env_get_thread_limit(scheduler_role_ptr, scheduler_role_len) }
746    }
747
748    /// Sets the number of threads which will be spawned for thread pool associated with the given
749    /// scheduler role. It cannot shrink the limit less to a value lower than the current number of
750    /// threads in the thread pool.
751    ///
752    /// |scheduler_role| is the name of the role which is passed when creating dispatchers.
753    /// |max_threads| is the number of threads to use as new limit.
754    pub fn set_thread_limit(&self, scheduler_role: &str, max_threads: u32) -> Result<(), Status> {
755        let scheduler_role_ptr = scheduler_role.as_ptr() as *mut ffi::c_char;
756        let scheduler_role_len = scheduler_role.len();
757        Status::ok(unsafe {
758            fdf_env_set_thread_limit(scheduler_role_ptr, scheduler_role_len, max_threads)
759        })
760    }
761    /// Returns the currently set options for the scheduler role as a uint32_t bitmask.
762    ///
763    /// |scheduler_role| is the name of the role which is passed when creating dispatchers.
764    pub fn get_scheduler_role_opts(&self, scheduler_role: &str) -> u32 {
765        let scheduler_role_ptr = scheduler_role.as_ptr() as *mut ffi::c_char;
766        let scheduler_role_len = scheduler_role.len();
767        unsafe { fdf_env_get_scheduler_role_opts(scheduler_role_ptr, scheduler_role_len) }
768    }
769
770    /// When used with [`Self::set_scheduler_role_opts`], this will not allow any dispatchers on the
771    /// scheduler role to be created with `FDF_DISPATCHER_OPTION_ALLOW_SYNC_CALLS`.
772    pub const SCHEDULER_ROLE_OPTION_NO_SYNC_CALLS: u32 = FDF_SCHEDULER_ROLE_OPTION_NO_SYNC_CALLS;
773
774    /// Sets the options for the given scheduler role. This can be used to enforce restrictions
775    /// on the kinds of dispatchers that can be created on this scheduler role.
776    ///
777    /// |scheduler_role| is the name of the role which is passed when creating dispatchers.
778    /// |options| is the new options for the scheduler role.
779    ///
780    /// # Errors
781    ///
782    /// [`Status::INVALID_ARGS`]: |options| contains unknown or invalid options.
783    /// [`Status::ERR_NOT_SUPPORTED`]: |options| contains an option that wouldn't allow a dispatcher
784    /// that already exists on this scheduler role.
785    pub fn set_scheduler_role_opts(
786        &self,
787        scheduler_role: &str,
788        options: u32,
789    ) -> Result<(), Status> {
790        let scheduler_role_ptr = scheduler_role.as_ptr() as *mut ffi::c_char;
791        let scheduler_role_len = scheduler_role.len();
792        Status::ok(unsafe {
793            fdf_env_set_scheduler_role_opts(scheduler_role_ptr, scheduler_role_len, options)
794        })
795    }
796
797    /// Gets the driver currently running on the thread identified by |thread_koid|, if the thread
798    /// is running on this driver host with a driver.
799    pub fn get_driver_on_thread_koid(&self, thread_koid: zx::Koid) -> Option<UnownedDriver> {
800        let mut driver = std::ptr::null();
801        unsafe {
802            Status::ok(fdf_env_get_driver_on_tid(thread_koid.raw_koid(), &mut driver)).ok()?;
803        }
804        if driver.is_null() { None } else { Some(UnownedDriver { inner: driver }) }
805    }
806
807    /// Registers a callback which is triggered whenever the stall scanner should run.
808    pub fn register_stall_scanner(&self, scanner: StallScanner) {
809        unsafe {
810            fdf_env_register_stall_scanner(scanner.into_ptr());
811        }
812    }
813}
814
815/// A registration handle returned by [`Driver::register_resume_requester`].
816/// The user MUST call `unregister` to unregister the resume requester when it is no longer valid.
817#[derive(Debug)]
818pub struct ResumeRequesterRegistration {
819    driver_ptr: *const ffi::c_void,
820    requester_ptr: *mut fdf_env_resume_requester_t,
821}
822
823// SAFETY: The runtime API that we call in this object (fdf_env_register_resume_requester)
824// is thread-safe and can be called from any thread. We are also the exclusive maintainer of
825// requester_ptr's lifetime.
826unsafe impl Send for ResumeRequesterRegistration {}
827
828impl ResumeRequesterRegistration {
829    /// Unregisters the resume requester from the runtime and frees the memory associated with it.
830    pub fn unregister(mut self) {
831        // Unregister the callback from the runtime.
832        // SAFETY: The null pointer is handled correctly by the runtime. If driver_ptr is no longer valid
833        // in the driver runtime, it will be treated as a no-op.
834        unsafe {
835            fdf_sys::fdf_env_register_resume_requester(self.driver_ptr, null_mut());
836        }
837
838        // Reconstitute the box and free it.
839        // SAFETY: requester_ptr was created using Box::leak(Box::new(self)).
840        // This is the only location that we re-create the Box, with exclusive ownership of self.
841        let requester = unsafe { Box::from_raw(self.requester_ptr as *mut ResumeRequester) };
842        drop(requester);
843
844        self.requester_ptr = null_mut();
845    }
846}
847
848/// The lifecycle state of a dispatcher.
849#[derive(Debug, Clone, Copy, PartialEq, Eq)]
850pub enum DispatcherState {
851    /// The dispatcher is running and accepting callbacks.
852    Running,
853    /// The dispatcher is currently shutting down.
854    ShuttingDown,
855    /// The dispatcher has completed shutdown.
856    Shutdown,
857    /// The dispatcher has been destroyed.
858    Destroyed,
859}
860
861/// Breakdown of reasons why requests were not inlined by the driver runtime.
862#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
863pub struct NonInlinedStats {
864    /// Calling from a non-blocking to a blocking (`ALLOW_SYNC_CALLS`) dispatcher.
865    pub allow_sync_calls: u64,
866    /// Another thread was already dispatching a request.
867    pub parallel_dispatch: u64,
868    /// The request was a task.
869    pub task: u64,
870    /// The request was queued from an unknown thread.
871    pub unknown_thread: u64,
872    /// The request would have been reentrant.
873    pub reentrant: u64,
874    /// Channel wait was not yet registered when the message was received.
875    pub channel_wait_not_yet_registered: u64,
876    /// Called into a dispatcher that wasn't allowed to migrate threads.
877    pub no_thread_migration: u64,
878}
879
880/// Request statistics for a dispatcher.
881#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
882pub struct DispatcherDebugStats {
883    /// Total number of requests processed by this dispatcher.
884    pub num_total_requests: u64,
885    /// Number of requests that were inlined.
886    pub num_inlined_requests: u64,
887    /// Breakdown of reasons why requests were not inlined.
888    pub non_inlined: NonInlinedStats,
889}
890
891/// Diagnostic information for a task queued on a dispatcher.
892#[derive(Debug, Clone, Copy, PartialEq, Eq)]
893pub struct QueuedTaskDebugInfo {
894    /// Address of the queued `async_task_t`.
895    pub ptr: u64,
896    /// Address of the task handler function.
897    pub handler: u64,
898    /// Address of the dispatcher that queued this task, if queued from a runtime thread.
899    pub initiating_dispatcher: u64,
900    /// Address of the driver that queued this task, if queued from a runtime thread.
901    pub initiating_driver: u64,
902}
903
904/// Structured diagnostic dump entry for a dispatcher in the driver runtime.
905#[derive(Debug, Clone, PartialEq, Eq)]
906pub struct DispatcherDumpEntry {
907    /// Address of the driver owner of this dispatcher.
908    pub driver: u64,
909    /// Address of the dispatcher object.
910    pub dispatcher_ptr: u64,
911    /// Name of the dispatcher.
912    pub name: String,
913    /// Scheduler role configured for the dispatcher.
914    pub scheduler_role: String,
915    /// Options mask the dispatcher was created with.
916    pub options: u32,
917    /// Whether the dispatcher is synchronized.
918    pub synchronized: bool,
919    /// Whether the dispatcher allows blocking synchronous calls.
920    pub allow_sync_calls: bool,
921    /// Current lifecycle state of the dispatcher.
922    pub state: DispatcherState,
923    /// Name of the dispatcher that initiated destruction, if `Destroy` was called.
924    pub destroy_context: String,
925    /// Whether `Destroy` was user-initiated (`true`) or environment-initiated (`false`).
926    pub destroy_user_initiated: Option<bool>,
927    /// Request and inlining statistics for the dispatcher.
928    pub debug_stats: DispatcherDebugStats,
929    /// Currently queued tasks on the dispatcher.
930    pub queued_tasks: Vec<QueuedTaskDebugInfo>,
931}
932
933/// Structured diagnostic dump entry for a thread spawned by the driver runtime.
934#[derive(Debug, Clone, PartialEq, Eq)]
935pub struct ThreadDumpEntry {
936    /// Kernel object ID (KOID) of the thread.
937    pub koid: u64,
938    /// Name of the thread.
939    pub name: String,
940    /// Scheduler role of the thread pool that spawned this thread.
941    pub scheduler_role: String,
942}