Whoops - I noticed two silly issues with this patch I will address in the next respin, comments down below:
On Tue, 2026-05-26 at 16:41 -0400, Lyude Paul wrote: > SetOnce is nice, but it does have one problem - you can't use it with > fallible initializers. While we will be adding support for doing that with > SetOnce, this leads into another problem: There's no way for racing callers > to actually block on the initialization of SetOnce, which makes it a > difficult type to use safely for situations where we want to initialize > data fallibly once, and then provide access to it to multiple users at once > until drop. > > So to solve this, introduce a new type: LazyInit. LazyInit is like SetOnce > with a couple of important differences: > > * It can't be used in const context > * It can handle in-place fallible initializers. > * It uses a Mutex for synchronization instead of an atomic, allowing > callers to block on initialization. > * It requires its contents already be Send + Sync, since the Mutex protects > initializing data and not the data itself. > > Signed-off-by: Lyude Paul <[email protected]> > --- > rust/kernel/sync.rs | 2 + > rust/kernel/sync/lazy_init.rs | 354 ++++++++++++++++++++++++++++++++++ > 2 files changed, 356 insertions(+) > create mode 100644 rust/kernel/sync/lazy_init.rs > > diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs > index 993dbf2caa0e3..d18c26c56c4d2 100644 > --- a/rust/kernel/sync.rs > +++ b/rust/kernel/sync.rs > @@ -15,6 +15,7 @@ > pub mod barrier; > pub mod completion; > mod condvar; > +mod lazy_init; > pub mod lock; > mod locked_by; > pub mod poll; > @@ -25,6 +26,7 @@ > pub use arc::{Arc, ArcBorrow, UniqueArc}; > pub use completion::Completion; > pub use condvar::{new_condvar, CondVar, CondVarTimeoutResult}; > +pub use lazy_init::*; > pub use lock::global::{global_lock, GlobalGuard, GlobalLock, > GlobalLockBackend, GlobalLockedBy}; > pub use lock::mutex::{new_mutex, Mutex, MutexGuard}; > pub use lock::spinlock::{new_spinlock, SpinLock, SpinLockGuard}; > diff --git a/rust/kernel/sync/lazy_init.rs b/rust/kernel/sync/lazy_init.rs > new file mode 100644 > index 0000000000000..707af369e965d > --- /dev/null > +++ b/rust/kernel/sync/lazy_init.rs > @@ -0,0 +1,354 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +//! Thread-safe Lazy-[`Init`] and [`PinInit`] > +use crate::{ > + prelude::*, > + sync::{ > + LockClassKey, > + Mutex, > + MutexGuard, // > + }, > +}; > +use core::{ > + mem::{ > + self, > + MaybeUninit, // > + }, > + ptr, > +}; > +use pin_init::{ > + pin_init, > + PinInit, // > +}; > + > +/// # Invariants > +/// > +/// - `data` is guaranteed to be initialized if `is_init` is `true`. > +/// - `data` is only written to once from `LazyInit::init()`, and once from > `LazyInit::reset()` > +/// (which cannot race with `LazyInit::init()` due to requiring a `&mut` > reference to the > +/// `LazyInit`). > +#[pin_data(PinnedDrop)] > +struct Inner<T: Send + Sync> { > + #[pin] > + data: MaybeUninit<T>, > + is_init: bool, > +} > + > +#[pinned_drop] > +impl<T: Send + Sync> PinnedDrop for Inner<T> { > + fn drop(self: Pin<&mut Self>) { > + if self.is_init { > + // SAFETY: The only contents from `this` that we move is > `is_init`. `is_init` is Unpin, > + // making this OK. > + let this = unsafe { self.get_unchecked_mut() }; > + > + // INVARIANT: This is the only place other then > `LazyInit::populate()` where we write to > + // `data`, and this function requires &mut self - ensuring it > cannot race with > + // `LazyInit::populate()`. I renamed this function from populate() to init() before sending out this series, so this definitely needs to be fixed > + // SAFETY: Since `is_init` is true, our type invariants > guarantee `data` is initialized. > + unsafe { this.data.assume_init_drop() }; > + > + this.is_init = false; > + } > + } > +} > + > +/// Errors that can occur during [`LazyInit::new`]. > +#[derive(Debug)] > +pub enum LazyInitError<'a, T: Send + Sync, E = Error> { > + /// The [`LazyInit`] has already been initialized. > + /// > + /// `self.0` contains a reference to the previously initialized value. > + AlreadyInit(&'a T), > + /// An error occurred during initialization. > + DuringInit(E), > +} > + > +impl<'a, T: Send + Sync, E> From<E> for LazyInitError<'a, T, E> { > + fn from(value: E) -> Self { > + Self::DuringInit(value) > + } > +} > + > +/// Creates a [`LazyInit`] initialiser with the given name and newly-created > lock class. > +/// > +/// It uses the name if one is given, otherwise it generates one based on > the file name and line > +/// number. > +#[macro_export] > +macro_rules! new_lazy_init { > + ($( $name:literal )?) => { > + $crate::sync::LazyInit::new($crate::optional_name!($($name)?), > $crate::static_lock_class!()) > + }; > +} > +pub use new_lazy_init; > + > +/// A thread-safe container that allows thread-safe single-time > initialization of its contents, > +/// which can then have shared references taken to its contents. It is > similar to [`SetOnce`] except > +/// that it uses a [`Mutex`], it can be populated using fallible [`PinInit`] > initializers, and > +/// multiple callers attempting to initialize at the same time will block > until the conetents of the > +/// [`LazyInit`] is finished initializing. > +/// > +/// This type cannot be used in `const` contexts, however, unlike > [`SetOnce`] users of this type are > +/// able to block if another thread is busy populating the [`SetOnce`]. This > also allows the use of > +/// fallible initializers for population. > +/// > +/// This type internally uses a [`Mutex`] for synchronizing creation of `T`, > but allows access to > +/// `T` outside of lock post-init. This means that only the initialization > of the value is protected > +/// by the lock, and thus `T` must provide it's own synchronization by > implementing `Send` + `Sync`. > +/// > +/// # Examples > +/// > +/// ``` > +/// use kernel::sync::{ > +/// LazyInit, > +/// LazyInitError, > +/// Arc, > +/// new_lazy_init, // > +/// }; > +/// > +/// struct Inner { > +/// a: u8, > +/// } > +/// > +/// #[pin_data] > +/// struct Example { > +/// #[pin] > +/// lazy: LazyInit<Arc<Inner>>, > +/// } > +/// > +/// impl Example { > +/// fn new() -> impl PinInit<Self> { > +/// pin_init!(Self { > +/// lazy <- new_lazy_init!(), > +/// }) > +/// } > +/// } > +/// > +/// // Allocate a boxed `Example`. > +/// let e = KBox::pin_init(Example::new(), GFP_KERNEL)?; > +/// > +/// assert!(e.lazy.init(Arc::init(init!(Inner { a: 42 }), > GFP_KERNEL)).is_ok()); > +/// > +/// // `as_ref()` can be used to get a reference to the contents of a > `LazyInit` if its initialized. > +/// assert_eq!(e.lazy.as_ref().unwrap().a, 42); > +/// > +/// // If `LazyInit::init()` fails due to the `LazyInit` already being > initialized, a reference to > +/// // the initialized data can still be retrieved from the error. > +/// match e.lazy.init(Arc::init(init!(Inner { a: 54 }), GFP_KERNEL)) { > +/// Err(LazyInitError::AlreadyInit(ret)) => assert_eq!(ret.a, 42), > +/// _ => unreachable!(), > +/// } > +/// # Ok::<(), Error>(()) > +/// ``` > +/// > +/// [`SetOnce`]: super::SetOnce > +#[pin_data] > +pub struct LazyInit<T: Send + Sync> { > + #[pin] > + inner: Mutex<Inner<T>>, > +} > + > +impl<T: Send + Sync> LazyInit<T> { > + /// Create a new initializer for an empty [`LazyInit`]. > + pub fn new(name: &'static CStr, key: Pin<&'static LockClassKey>) -> impl > PinInit<Self> { > + pin_init!(Self { > + inner <- Mutex::new( > + pin_init!(Inner { > + data <- MaybeUninit::uninit(), > + is_init: false, > + }), > + name, > + key > + ) > + }) > + } > + > + /// Retrieve the contents of `inner.data` and extend their lifetime. > + /// > + /// # Safety > + /// > + /// The caller guarantees that `self.inner.data` has been previously > initialized. > + #[inline(always)] > + unsafe fn data<'a>(&'a self, inner: &MutexGuard<'_, Inner<T>>) -> &'a T { > + // SAFETY: > + // - Our safety contract guarantees `inner.data` is initialized. > + // - `T` is `Send` + `Sync`, and thus does not need the `Mutex` for > synchronization, making > + // it safe to hold onto outside of the lock. > + // - We're guaranteed the container of `T` will not be written to > via `Inner`s type > + // invariants until `Drop`, ensuring it remains populated for the > lifetime of 'a. > + unsafe { mem::transmute::<&_, &'a _>(inner.data.assume_init_ref()) } > + } > + > + /// Attempt to init the contents of [`LazyInit`] and return a reference > to its contents. > + /// > + /// If the contents of the [`LazyInit`] were already initialized, they > will not be > + /// re-initialized. > + /// > + /// Returns: > + /// > + /// - `Ok(&T)` if the container was initialized successfully, or if it > was already initialized > + /// by another user. > + /// - [`Err(LazyInitError::AlreadyInit)`](LazyInitError::AlreadyInit) if > the container was > + /// already initialized. This error contains a reference to the > contents of the [`LazyInit`]. > + /// - [`Err(LazyInitError::DuringInit)`](LazyInitError::DuringInit) if > an error was encountered > + /// while trying to initialize this [`LazyInit`]. > + pub fn init<'a, E>( > + &'a self, > + init: impl PinInit<T, E>, > + ) -> Result<&'a T, LazyInitError<'a, T, E>> { > + let mut inner = self.inner.lock(); > + let do_init = !inner.is_init; > + > + if do_init { > + // SAFETY: The only thing that we move is `is_init`, which is > Unpin. > + let inner = unsafe { inner.as_mut().get_unchecked_mut() }; > + > + // INVARIANT: This is the only place we can write to `data` > before Drop, fulfilling > + // `Inner`'s relevant type invariant. > + // > + // SAFETY: > + // - Via Inner's invariants, since we checked `is_init` we're > guaranteed `data` is > + // uninitialized. > + // - We do not touch `data` at any point after this if we fail. > + // - This does not move `data`. > + unsafe { init.__pinned_init(inner.data.as_mut_ptr()) }?; > + > + inner.is_init = true; > + } > + > + // INVARIANT: This code is only reachable if `data` was, either > previously or just now, > + // initialized with a valid instance of `T`. Otherwise we've never > written to it and `data` > + // is guaranteed to be uninitialized, fulfilling `Inner`s type > invariants regarding > + // `data` initialization state. > + > + // SAFETY: We confirmed `data` is initialized above. > + let ret = unsafe { self.data(&inner) }; > + > + match do_init { > + true => Ok(ret), > + false => Err(LazyInitError::AlreadyInit(ret)), > + } > + } > + > + /// Get a reference to the contained object. > + /// > + /// Returns [`None`] if this [`LazyInit`] is empty. > + pub fn as_ref<'a>(&'a self) -> Option<&'a T> { > + let inner = self.inner.lock(); > + > + inner.is_init.then(|| { > + // SAFETY: This closure can only execute if `inner.is_init` is > true, guaranteeing > + // `inner.data` is initialized via `Inner`s type invariants. > + unsafe { self.data(&inner) } > + }) > + } > + > + /// Release the contents of the [`LazyInit`]. > + /// > + /// Since this call borrows the [`LazyInit`] mutably, no locking > actually needs to take place - > + /// as parallel lock acquisitions are prevented via the compiler > enforcing Rust's borrowing > + /// rules. > + /// > + /// Returns [`true`] if `self` was initialized before calling this > function. > + pub fn reset(self: Pin<&mut Self>) -> bool { > + let inner = self.project().inner.get_mut_pinned(); > + let was_init = inner.is_init; > + > + // SAFETY: > + // - We drop in place, and therefore do not move `inner` > + // - The `PinnedDrop` implementation of `Inner` leaves it in a > well-defined > + // state, so we do not need to worry about UB from further usage. > + unsafe { ptr::drop_in_place(inner.get_unchecked_mut()) }; > + > + was_init > + } > +} > + > +#[kunit_tests(rust_lazy_init)] > +mod tests { > + use super::*; > + use core::ops::Deref; Deref is unused and should be dropped > + use pin_init::{ > + init_from_closure, > + stack_pin_init, // > + }; > + > + #[derive(Debug)] > + #[pin_data] > + struct LazyInitTest {} > + > + impl LazyInitTest { > + fn init_ok() -> impl Init<Self> { > + init!(LazyInitTest {}) > + } > + > + fn init_err(e: Error) -> impl Init<Self, Error> { > + // SAFETY: > + // This initializer is intended to fail for testing purposes, > and does not actually > + // initialize any data meaning: > + // > + // - We don't need to worry about returning Ok(()). > + // - We have nothing to actually clean in the slot. > + // - Since there is no data in the slot, nothing can > meaningfully move and we have no > + // pinning invariants. > + unsafe { init_from_closure(move |_| Err(e)) } > + } > + } > + > + fn try_lazy_init(once: &LazyInit<LazyInitTest>) -> Result<(), Error> { > + // It should start as being empty. > + assert!((*once).as_ref().is_none()); > + > + // Try populating it a single time, this should succeed. > + let res = once.init(LazyInitTest::init_ok()).map_err(|_| EINVAL)?; > + assert!((*once).as_ref().is_some()); > + > + // Populating a second time should fail and return the contents. > + match once.init(LazyInitTest::init_ok()) { > + Err(LazyInitError::AlreadyInit(data)) => > assert!(core::ptr::eq(data, res)), > + r @ _ => panic!("Calling once.init() twice returned unexpected > value: {r:?}"), > + } > + > + // And it should still hold a value > + assert!((*once).as_ref().is_some()); > + > + Ok(()) > + } > + > + #[test] > + fn lazy_init() -> Result { > + stack_pin_init!(let once: LazyInit<LazyInitTest> = new_lazy_init!()); > + > + try_lazy_init(&once)?; > + Ok(()) > + } > + > + #[test] > + fn lazy_init_error() -> Result { > + stack_pin_init!(let once: LazyInit<LazyInitTest> = new_lazy_init!()); > + > + // Make sure it starts as empty. > + assert!((*once).as_ref().is_none()); > + > + // Try populating it with a initializer that fails. > + match once.init(LazyInitTest::init_err(EJUKEBOX)) { > + Err(LazyInitError::DuringInit(e)) => assert_eq!(e, EJUKEBOX), > + r @ _ => panic!( > + "Calling once.init() with a failing initializer returned an > unexpected value: {r:?}" > + ), > + } > + > + try_lazy_init(&once)?; > + Ok(()) > + } > + > + #[test] > + fn lazy_init_reset() -> Result { > + stack_pin_init!(let once: LazyInit<LazyInitTest> = new_lazy_init!()); > + > + try_lazy_init(&once)?; > + assert_eq!(once.as_mut().reset(), true); > + assert_eq!(once.as_mut().reset(), false); > + try_lazy_init(&once) > + } > +}
