Skip to content

Implementation: #[feature(nonpoison_once)] #144653

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion library/std/src/sync/lazy_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ union Data<T, F> {
/// ```
#[stable(feature = "lazy_cell", since = "1.80.0")]
pub struct LazyLock<T, F = fn() -> T> {
// FIXME(nonpoison_once): if possible, switch to nonpoison version once it is available
/// We use `poison::Once` here to enable the `force_mut` method.
once: Once,
data: UnsafeCell<Data<T, F>>,
}
Expand Down
3 changes: 3 additions & 0 deletions library/std/src/sync/nonpoison.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@ impl fmt::Display for WouldBlock {
pub use self::mutex::MappedMutexGuard;
#[unstable(feature = "nonpoison_mutex", issue = "134645")]
pub use self::mutex::{Mutex, MutexGuard};
#[unstable(feature = "nonpoison_once", issue = "134645")]
pub use self::once::Once;

mod mutex;
pub(crate) mod once;
221 changes: 221 additions & 0 deletions library/std/src/sync/nonpoison/once.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
use crate::fmt;
use crate::sys::sync as sys;

/// A low-level synchronization primitive for one-time global execution that ignores poisoning.
///
/// For more information about `Once`, check out the documentation for the poisoning variant (which
/// can be found at [`poison::Once`]).
///
/// [`poison::Once`]: crate::sync::poison::Once
///
/// # Examples
///
/// ```
/// use std::sync::Once;
///
/// static START: Once = Once::new();
///
/// START.call_once(|| {
/// // run initialization here
/// });
/// ```
#[unstable(feature = "nonpoison_once", issue = "134645")]
pub struct Once {
inner: sys::Once,
}

impl Once {
/// Creates a new `Once` value.
#[inline]
#[unstable(feature = "nonpoison_once", issue = "134645")]
#[must_use]
pub const fn new() -> Once {
Once { inner: sys::Once::new() }
}

/// Performs an initialization routine once and only once. The given closure
/// will be executed if this is the first time `call_once` has been called,
/// and otherwise the routine will *not* be invoked.
///
/// This method will block the calling thread if another initialization
/// routine is currently running.
///
/// When this function returns, it is guaranteed that some initialization
/// has run and completed (it might not be the closure specified). It is also
/// guaranteed that any memory writes performed by the executed closure can
/// be reliably observed by other threads at this point (there is a
/// happens-before relation between the closure and code executing after the
/// return).
///
/// If the given closure recursively invokes `call_once` on the same [`Once`]
/// instance, the exact behavior is not specified: allowed outcomes are
/// a panic or a deadlock.
///
/// # Examples
///
/// ```
/// #![feature(nonpoison_once)]
///
/// use std::sync::nonpoison::Once;
///
/// static mut VAL: usize = 0;
/// static INIT: Once = Once::new();
///
/// // Accessing a `static mut` is unsafe much of the time, but if we do so
/// // in a synchronized fashion (e.g., write once or read all) then we're
/// // good to go!
/// //
/// // This function will only call `expensive_computation` once, and will
/// // otherwise always return the value returned from the first invocation.
/// fn get_cached_val() -> usize {
/// unsafe {
/// INIT.call_once(|| {
/// VAL = expensive_computation();
/// });
/// VAL
/// }
/// }
///
/// fn expensive_computation() -> usize {
/// // ...
/// # 2
/// }
/// ```
///
/// # Panics
///
/// The closure `f` will only be executed once even if this is called concurrently amongst many
/// threads. If the closure panics, the calling thread will panic and the `Once` will remain in
/// an incompleted state.
///
/// In contrast to the [`poison::Once`] variant, all calls to `call_once` will ignore panics in
/// other threads. This method is identical to the [`poison::Once::call_once_force`] method.
///
/// If you need observability into whether any threads have panicked while calling `call_once`,
/// see [`poison::Once`].
///
/// [`poison::Once`]: crate::sync::poison::Once
/// [`poison::Once::call_once_force`]: crate::sync::poison::Once::call_once_force
///
/// ```
/// #![feature(nonpoison_once)]
///
/// use std::sync::nonpoison::Once;
/// use std::thread;
///
/// static INIT: Once = Once::new();
///
/// // Panic during `call_once`.
/// let handle = thread::spawn(|| {
/// INIT.call_once(|| panic!());
/// });
/// assert!(handle.join().is_err());
///
/// // `call_once` will still run from a different thread.
/// INIT.call_once(|| {
/// assert_eq!(2 + 2, 4);
/// });
/// ```
#[inline]
#[unstable(feature = "nonpoison_once", issue = "134645")]
#[track_caller]
pub fn call_once<F>(&self, f: F)
where
F: FnOnce(),
{
// Fast path check.
if self.inner.is_completed() {
return;
}

let mut f = Some(f);
self.inner.call(true, &mut |_| f.take().unwrap()());
}

/// Returns `true` if some [`call_once()`] call has completed
/// successfully. Specifically, `is_completed` will return false in
/// the following situations:
/// * [`call_once()`] was not called at all,
/// * [`call_once()`] was called, but has not yet completed
///
/// This function returning `false` does not mean that [`Once`] has not been
/// executed. For example, it may have been executed in the time between
/// when `is_completed` starts executing and when it returns, in which case
/// the `false` return value would be stale (but still permissible).
///
/// [`call_once()`]: Once::call_once
///
/// # Examples
///
/// ```
/// #![feature(nonpoison_once)]
///
/// use std::sync::nonpoison::Once;
///
/// static INIT: Once = Once::new();
///
/// assert_eq!(INIT.is_completed(), false);
/// INIT.call_once(|| {
/// assert_eq!(INIT.is_completed(), false);
/// });
/// assert_eq!(INIT.is_completed(), true);
/// ```
///
/// ```
/// #![feature(nonpoison_once)]
///
/// use std::sync::nonpoison::Once;
/// use std::thread;
///
/// static INIT: Once = Once::new();
///
/// assert_eq!(INIT.is_completed(), false);
/// let handle = thread::spawn(|| {
/// INIT.call_once(|| panic!());
/// });
/// assert!(handle.join().is_err());
/// assert_eq!(INIT.is_completed(), false);
/// ```
#[inline]
#[unstable(feature = "nonpoison_once", issue = "134645")]
pub fn is_completed(&self) -> bool {
self.inner.is_completed()
}

/// Blocks the current thread until initialization has completed.
///
/// # Example
///
/// ```rust
/// use std::sync::Once;
/// use std::thread;
///
/// static READY: Once = Once::new();
///
/// let thread = thread::spawn(|| {
/// READY.wait();
/// println!("everything is ready");
/// });
///
/// READY.call_once(|| println!("performing setup"));
/// ```
///
/// This function will continue to block even if a thread initializing via [`call_once()`] has
/// panicked. This behavior is identical to the [`poison::Once::wait_force`] method.
///
/// [`call_once()`]: Once::call_once
/// [`poison::Once::wait_force`]: crate::sync::poison::Once::wait_force
#[unstable(feature = "nonpoison_once", issue = "134645")]
pub fn wait(&self) {
if !self.inner.is_completed() {
self.inner.wait(true);
}
}
}

#[unstable(feature = "nonpoison_once", issue = "134645")]
impl fmt::Debug for Once {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Once").finish_non_exhaustive()
}
}
5 changes: 3 additions & 2 deletions library/std/src/sync/once_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,10 @@ use crate::sync::Once;
/// ```
#[stable(feature = "once_cell", since = "1.70.0")]
pub struct OnceLock<T> {
// FIXME(nonpoison_once): switch to nonpoison version once it is available
/// We use `poison::Once` here to allow us to pseudo-"poison" the `Once` whenever a
/// `get_or_try_init` fails, which allows other calls to be run after a failure.
once: Once,
// Whether or not the value is initialized is tracked by `once.is_completed()`.
/// Note that `once.is_completed()` tells us if the value is initialized or not.
value: UnsafeCell<MaybeUninit<T>>,
/// `PhantomData` to make sure dropck understands we're dropping T in our Drop impl.
///
Expand Down
1 change: 1 addition & 0 deletions library/std/tests/sync/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#![feature(std_internals)]
#![feature(sync_nonpoison)]
#![feature(nonpoison_mutex)]
#![feature(nonpoison_once)]
#![allow(internal_features)]
#![feature(macro_metavar_expr_concat)] // For concatenating identifiers in macros.

Expand Down
Loading
Loading