rust/kernel/sync/set_once.rs | 53 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 7 deletions(-)
Add methods to get a reference to the contained value or populate the
SetOnce if empty. The new `as_ref_or_populate` method accepts a value
directly, while `as_ref_or_populate_with` accepts a fallible closure,
allowing for lazy initialization that may fail. Both methods spin-wait
if another thread is concurrently initializing the container.
Also add `populate_with` which takes a fallible closure and serves as
the implementation basis for the other populate methods.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/kernel/sync/set_once.rs | 53 ++++++++++++++++++++++++++++++++++++++------
1 file changed, 46 insertions(+), 7 deletions(-)
diff --git a/rust/kernel/sync/set_once.rs b/rust/kernel/sync/set_once.rs
index bdba601807d8b..9e3c4be4047f8 100644
--- a/rust/kernel/sync/set_once.rs
+++ b/rust/kernel/sync/set_once.rs
@@ -2,11 +2,14 @@
//! A container that can be initialized at most once.
-use super::atomic::{
- ordering::{Acquire, Relaxed, Release},
- Atomic,
-};
use core::{cell::UnsafeCell, mem::MaybeUninit};
+use kernel::{
+ error::Result,
+ sync::atomic::{
+ ordering::{Acquire, Relaxed, Release},
+ Atomic,
+ },
+};
/// A container that can be populated at most once. Thread safe.
///
@@ -76,10 +79,46 @@ pub fn as_ref(&self) -> Option<&T> {
}
}
+ /// Get a reference to the contained object, or populate the [`SetOnce`]
+ /// with the value returned by `callable` and return a reference to that
+ /// object.
+ pub fn as_ref_or_populate_with(&self, callable: impl FnOnce() -> Result<T>) -> Result<&T> {
+ if !self.populate_with(callable)? {
+ while self.init.load(Acquire) != 2 {
+ core::hint::spin_loop();
+ }
+ }
+
+ // SAFETY: By the type invariants of `Self`, `self.init == 2` means that `self.value`
+ // is initialized and valid for shared access.
+ Ok(unsafe { &*self.value.get().cast() })
+ }
+
+ /// Get a reference to the contained object, or populate the [`SetOnce`]
+ /// with `value` and return a reference to that object.
+ pub fn as_ref_or_populate(&self, value: T) -> &T {
+ if !self.populate(value) {
+ while self.init.load(Acquire) != 2 {
+ core::hint::spin_loop();
+ }
+ }
+
+ // SAFETY: By the type invariants of `Self`, `self.init == 2` means that `self.value`
+ // is initialized and valid for shared access.
+ unsafe { &*self.value.get().cast() }
+ }
+
/// Populate the [`SetOnce`].
///
/// Returns `true` if the [`SetOnce`] was successfully populated.
pub fn populate(&self, value: T) -> bool {
+ self.populate_with(|| Ok(value)).expect("Cannot error")
+ }
+
+ /// Populate the [`SetOnce`] with the value returned by `callable`.
+ ///
+ /// Returns `true` if the [`SetOnce`] was successfully populated.
+ pub fn populate_with(&self, callable: impl FnOnce() -> Result<T>) -> Result<bool> {
// INVARIANT: If the swap succeeds:
// - We increase `init`.
// - We write the valid value `1` to `init`.
@@ -88,16 +127,16 @@ pub fn populate(&self, value: T) -> bool {
if let Ok(0) = self.init.cmpxchg(0, 1, Relaxed) {
// SAFETY: By the type invariants of `Self`, the fact that we succeeded in writing `1`
// to `self.init` means we obtained exclusive access to `self.value`.
- unsafe { core::ptr::write(self.value.get().cast(), value) };
+ unsafe { core::ptr::write(self.value.get().cast(), callable()?) };
// INVARIANT:
// - We increase `init`.
// - We write the valid value `2` to `init`.
// - We release our exclusive access to `self.value` and it is now valid for shared
// access.
self.init.store(2, Release);
- true
+ Ok(true)
} else {
- false
+ Ok(false)
}
}
---
base-commit: 05f7e89ab9731565d8a62e3b5d1ec206485eeb0b
change-id: 20260215-set-once-lazy-c73fc34a55d9
Best regards,
--
Andreas Hindborg <a.hindborg@kernel.org>
On Sun Feb 15, 2026 at 8:27 PM GMT, Andreas Hindborg wrote:
> Add methods to get a reference to the contained value or populate the
> SetOnce if empty. The new `as_ref_or_populate` method accepts a value
> directly, while `as_ref_or_populate_with` accepts a fallible closure,
> allowing for lazy initialization that may fail. Both methods spin-wait
> if another thread is concurrently initializing the container.
>
> Also add `populate_with` which takes a fallible closure and serves as
> the implementation basis for the other populate methods.
Hi Andreas, in an earlier call I mentioned that I'm working on getting SetOnce
to work with pin-init, the capability of which I think is a superset of you have
here.
The API I have is
impl<T> SetOnce<T> {
pub fn init<E>(&self, init: impl Init<T, E>) -> Result<&T, InitError<E>>;
pub fn pin_init<E>(self, Pin<&Self>, init: impl PinInit<T, E>) -> Result<&T, InitError<E>>;
}
To achieve what you need with a function, you can simply write:
set_once.init(pin_init::init_scope(your_fn))
The patch that implement the API is here:
https://github.com/nbdd0121/linux/commit/4aabdbcf20b11626c253f203745b1d55c37ab2ee
in tree
https://github.com/nbdd0121/linux/tree/lazy_revocable_nova_wip/
which I haven't submitted to the list as the user side of this API isn't ready.
Best,
Gary
>
> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
> ---
> rust/kernel/sync/set_once.rs | 53 ++++++++++++++++++++++++++++++++++++++------
> 1 file changed, 46 insertions(+), 7 deletions(-)
>
> diff --git a/rust/kernel/sync/set_once.rs b/rust/kernel/sync/set_once.rs
> index bdba601807d8b..9e3c4be4047f8 100644
> --- a/rust/kernel/sync/set_once.rs
> +++ b/rust/kernel/sync/set_once.rs
> @@ -2,11 +2,14 @@
>
> //! A container that can be initialized at most once.
>
> -use super::atomic::{
> - ordering::{Acquire, Relaxed, Release},
> - Atomic,
> -};
> use core::{cell::UnsafeCell, mem::MaybeUninit};
> +use kernel::{
> + error::Result,
> + sync::atomic::{
> + ordering::{Acquire, Relaxed, Release},
> + Atomic,
> + },
> +};
>
> /// A container that can be populated at most once. Thread safe.
> ///
> @@ -76,10 +79,46 @@ pub fn as_ref(&self) -> Option<&T> {
> }
> }
>
> + /// Get a reference to the contained object, or populate the [`SetOnce`]
> + /// with the value returned by `callable` and return a reference to that
> + /// object.
> + pub fn as_ref_or_populate_with(&self, callable: impl FnOnce() -> Result<T>) -> Result<&T> {
> + if !self.populate_with(callable)? {
> + while self.init.load(Acquire) != 2 {
> + core::hint::spin_loop();
> + }
> + }
> +
> + // SAFETY: By the type invariants of `Self`, `self.init == 2` means that `self.value`
> + // is initialized and valid for shared access.
> + Ok(unsafe { &*self.value.get().cast() })
> + }
> +
> + /// Get a reference to the contained object, or populate the [`SetOnce`]
> + /// with `value` and return a reference to that object.
> + pub fn as_ref_or_populate(&self, value: T) -> &T {
> + if !self.populate(value) {
> + while self.init.load(Acquire) != 2 {
> + core::hint::spin_loop();
> + }
> + }
> +
> + // SAFETY: By the type invariants of `Self`, `self.init == 2` means that `self.value`
> + // is initialized and valid for shared access.
> + unsafe { &*self.value.get().cast() }
> + }
> +
> /// Populate the [`SetOnce`].
> ///
> /// Returns `true` if the [`SetOnce`] was successfully populated.
> pub fn populate(&self, value: T) -> bool {
> + self.populate_with(|| Ok(value)).expect("Cannot error")
> + }
> +
> + /// Populate the [`SetOnce`] with the value returned by `callable`.
> + ///
> + /// Returns `true` if the [`SetOnce`] was successfully populated.
> + pub fn populate_with(&self, callable: impl FnOnce() -> Result<T>) -> Result<bool> {
> // INVARIANT: If the swap succeeds:
> // - We increase `init`.
> // - We write the valid value `1` to `init`.
> @@ -88,16 +127,16 @@ pub fn populate(&self, value: T) -> bool {
> if let Ok(0) = self.init.cmpxchg(0, 1, Relaxed) {
> // SAFETY: By the type invariants of `Self`, the fact that we succeeded in writing `1`
> // to `self.init` means we obtained exclusive access to `self.value`.
> - unsafe { core::ptr::write(self.value.get().cast(), value) };
> + unsafe { core::ptr::write(self.value.get().cast(), callable()?) };
> // INVARIANT:
> // - We increase `init`.
> // - We write the valid value `2` to `init`.
> // - We release our exclusive access to `self.value` and it is now valid for shared
> // access.
> self.init.store(2, Release);
> - true
> + Ok(true)
> } else {
> - false
> + Ok(false)
> }
> }
>
>
> ---
> base-commit: 05f7e89ab9731565d8a62e3b5d1ec206485eeb0b
> change-id: 20260215-set-once-lazy-c73fc34a55d9
>
> Best regards,
"Gary Guo" <gary@garyguo.net> writes:
> On Sun Feb 15, 2026 at 8:27 PM GMT, Andreas Hindborg wrote:
>> Add methods to get a reference to the contained value or populate the
>> SetOnce if empty. The new `as_ref_or_populate` method accepts a value
>> directly, while `as_ref_or_populate_with` accepts a fallible closure,
>> allowing for lazy initialization that may fail. Both methods spin-wait
>> if another thread is concurrently initializing the container.
>>
>> Also add `populate_with` which takes a fallible closure and serves as
>> the implementation basis for the other populate methods.
>
> Hi Andreas, in an earlier call I mentioned that I'm working on getting SetOnce
> to work with pin-init, the capability of which I think is a superset of you have
> here.
>
> The API I have is
>
> impl<T> SetOnce<T> {
> pub fn init<E>(&self, init: impl Init<T, E>) -> Result<&T, InitError<E>>;
> pub fn pin_init<E>(self, Pin<&Self>, init: impl PinInit<T, E>) -> Result<&T, InitError<E>>;
> }
>
> To achieve what you need with a function, you can simply write:
>
> set_once.init(pin_init::init_scope(your_fn))
>
> The patch that implement the API is here:
> https://github.com/nbdd0121/linux/commit/4aabdbcf20b11626c253f203745b1d55c37ab2ee
> in tree
> https://github.com/nbdd0121/linux/tree/lazy_revocable_nova_wip/
>
> which I haven't submitted to the list as the user side of this API isn't ready.
I probably evicted that from cache.
It looks like that could replace the `populate` in my patch? We would
still need the synchronization in `as_ref_or_populate`, right? (or
`as_ref_or_init` if you will)
Best regards,
Andreas Hindborg
On Sun, Feb 15, 2026 at 09:27:17PM +0100, Andreas Hindborg wrote:
> Add methods to get a reference to the contained value or populate the
> SetOnce if empty. The new `as_ref_or_populate` method accepts a value
> directly, while `as_ref_or_populate_with` accepts a fallible closure,
> allowing for lazy initialization that may fail. Both methods spin-wait
> if another thread is concurrently initializing the container.
>
> Also add `populate_with` which takes a fallible closure and serves as
> the implementation basis for the other populate methods.
>
> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
> + /// Get a reference to the contained object, or populate the [`SetOnce`]
> + /// with the value returned by `callable` and return a reference to that
> + /// object.
> + pub fn as_ref_or_populate_with(&self, callable: impl FnOnce() -> Result<T>) -> Result<&T> {
> + if !self.populate_with(callable)? {
> + while self.init.load(Acquire) != 2 {
> + core::hint::spin_loop();
> + }
We should not be implementing our own spinlocks.
Alice
"Alice Ryhl" <aliceryhl@google.com> writes:
> On Sun, Feb 15, 2026 at 09:27:17PM +0100, Andreas Hindborg wrote:
>> Add methods to get a reference to the contained value or populate the
>> SetOnce if empty. The new `as_ref_or_populate` method accepts a value
>> directly, while `as_ref_or_populate_with` accepts a fallible closure,
>> allowing for lazy initialization that may fail. Both methods spin-wait
>> if another thread is concurrently initializing the container.
>>
>> Also add `populate_with` which takes a fallible closure and serves as
>> the implementation basis for the other populate methods.
>>
>> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
>
>> + /// Get a reference to the contained object, or populate the [`SetOnce`]
>> + /// with the value returned by `callable` and return a reference to that
>> + /// object.
>> + pub fn as_ref_or_populate_with(&self, callable: impl FnOnce() -> Result<T>) -> Result<&T> {
>> + if !self.populate_with(callable)? {
>> + while self.init.load(Acquire) != 2 {
>> + core::hint::spin_loop();
>> + }
>
> We should not be implementing our own spinlocks.
That is a great proverb. I'd be happy to receive a suggestion on an
alternate approach for this particular context.
Best regards,
Andreas Hindborg
On Mon, Feb 16, 2026 at 12:10:16PM +0100, Andreas Hindborg wrote:
> "Alice Ryhl" <aliceryhl@google.com> writes:
>
> > On Sun, Feb 15, 2026 at 09:27:17PM +0100, Andreas Hindborg wrote:
> >> Add methods to get a reference to the contained value or populate the
> >> SetOnce if empty. The new `as_ref_or_populate` method accepts a value
> >> directly, while `as_ref_or_populate_with` accepts a fallible closure,
> >> allowing for lazy initialization that may fail. Both methods spin-wait
> >> if another thread is concurrently initializing the container.
> >>
> >> Also add `populate_with` which takes a fallible closure and serves as
> >> the implementation basis for the other populate methods.
> >>
> >> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
> >
> >> + /// Get a reference to the contained object, or populate the [`SetOnce`]
> >> + /// with the value returned by `callable` and return a reference to that
> >> + /// object.
> >> + pub fn as_ref_or_populate_with(&self, callable: impl FnOnce() -> Result<T>) -> Result<&T> {
> >> + if !self.populate_with(callable)? {
> >> + while self.init.load(Acquire) != 2 {
> >> + core::hint::spin_loop();
> >> + }
> >
> > We should not be implementing our own spinlocks.
>
> That is a great proverb. I'd be happy to receive a suggestion on an
> alternate approach for this particular context.
You can add a spinlock to SetOnce. Like I mentioned previously [1],
support for waiting will require the addition of extra fields.
I suppose we might be able to use include/linux/bit_spinlock.h here to
avoid taking up any additional space.
Alice
[1]: https://lore.kernel.org/all/CAH5fLggY2Ei14nVJzLBEoR1Rut1GKU4SZX=+14tuRH1aSuQVTA@mail.gmail.com/
On Mon, Feb 16, 2026 at 11:26:11AM +0000, Alice Ryhl wrote:
> On Mon, Feb 16, 2026 at 12:10:16PM +0100, Andreas Hindborg wrote:
> > "Alice Ryhl" <aliceryhl@google.com> writes:
> >
> > > On Sun, Feb 15, 2026 at 09:27:17PM +0100, Andreas Hindborg wrote:
> > >> Add methods to get a reference to the contained value or populate the
> > >> SetOnce if empty. The new `as_ref_or_populate` method accepts a value
> > >> directly, while `as_ref_or_populate_with` accepts a fallible closure,
> > >> allowing for lazy initialization that may fail. Both methods spin-wait
> > >> if another thread is concurrently initializing the container.
> > >>
> > >> Also add `populate_with` which takes a fallible closure and serves as
> > >> the implementation basis for the other populate methods.
> > >>
> > >> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
> > >
> > >> + /// Get a reference to the contained object, or populate the [`SetOnce`]
> > >> + /// with the value returned by `callable` and return a reference to that
> > >> + /// object.
> > >> + pub fn as_ref_or_populate_with(&self, callable: impl FnOnce() -> Result<T>) -> Result<&T> {
> > >> + if !self.populate_with(callable)? {
> > >> + while self.init.load(Acquire) != 2 {
> > >> + core::hint::spin_loop();
> > >> + }
> > >
> > > We should not be implementing our own spinlocks.
> >
> > That is a great proverb. I'd be happy to receive a suggestion on an
> > alternate approach for this particular context.
>
> You can add a spinlock to SetOnce. Like I mentioned previously [1],
> support for waiting will require the addition of extra fields.
By the way, back then I suggested renaming it from OnceLock to SetOnce
because you did not support waiting for the value to be populated, and
you said you didn't need that. If you add that feature, then we should
rename it back to OnceLock, or create a new type OnceLock for users who
need that additional feature.
Alice
"Alice Ryhl" <aliceryhl@google.com> writes:
> On Mon, Feb 16, 2026 at 11:26:11AM +0000, Alice Ryhl wrote:
>> On Mon, Feb 16, 2026 at 12:10:16PM +0100, Andreas Hindborg wrote:
>> > "Alice Ryhl" <aliceryhl@google.com> writes:
>> >
>> > > On Sun, Feb 15, 2026 at 09:27:17PM +0100, Andreas Hindborg wrote:
>> > >> Add methods to get a reference to the contained value or populate the
>> > >> SetOnce if empty. The new `as_ref_or_populate` method accepts a value
>> > >> directly, while `as_ref_or_populate_with` accepts a fallible closure,
>> > >> allowing for lazy initialization that may fail. Both methods spin-wait
>> > >> if another thread is concurrently initializing the container.
>> > >>
>> > >> Also add `populate_with` which takes a fallible closure and serves as
>> > >> the implementation basis for the other populate methods.
>> > >>
>> > >> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
>> > >
>> > >> + /// Get a reference to the contained object, or populate the [`SetOnce`]
>> > >> + /// with the value returned by `callable` and return a reference to that
>> > >> + /// object.
>> > >> + pub fn as_ref_or_populate_with(&self, callable: impl FnOnce() -> Result<T>) -> Result<&T> {
>> > >> + if !self.populate_with(callable)? {
>> > >> + while self.init.load(Acquire) != 2 {
>> > >> + core::hint::spin_loop();
>> > >> + }
>> > >
>> > > We should not be implementing our own spinlocks.
>> >
>> > That is a great proverb. I'd be happy to receive a suggestion on an
>> > alternate approach for this particular context.
>>
>> You can add a spinlock to SetOnce. Like I mentioned previously [1],
>> support for waiting will require the addition of extra fields.
Thanks, I'll be sure to take a look again.
>
> By the way, back then I suggested renaming it from OnceLock to SetOnce
> because you did not support waiting for the value to be populated, and
> you said you didn't need that. If you add that feature, then we should
> rename it back to OnceLock, or create a new type OnceLock for users who
> need that additional feature.
That is fair. This is a different use case than the original one though.
I think we should keep this as one type for code reuse, but I am fine
with renaming to something that describe the usage better.
Best regards,
Andreas Hindborg
On Sun Feb 15, 2026 at 9:27 PM CET, Andreas Hindborg wrote:
> @@ -76,10 +79,46 @@ pub fn as_ref(&self) -> Option<&T> {
> }
> }
>
> + /// Get a reference to the contained object, or populate the [`SetOnce`]
> + /// with the value returned by `callable` and return a reference to that
> + /// object.
> + pub fn as_ref_or_populate_with(&self, callable: impl FnOnce() -> Result<T>) -> Result<&T> {
I would name the argument `create`, but not `callable`. Same below.
Cheers,
Benno
> + if !self.populate_with(callable)? {
> + while self.init.load(Acquire) != 2 {
> + core::hint::spin_loop();
> + }
> + }
© 2016 - 2026 Red Hat, Inc.