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}