Skip to content

Commit

Permalink
Update to the latest upstream Rust code.
Browse files Browse the repository at this point in the history
Update all files derived from std to the latest version of std.

This adds new `get_mut_or_init` and `get_mut_or_try_init` functions
to `OnceLock`, documentation updates for `OnceLock`, minor code cleanups
in futex_mutex.rs, and a few file renames.
  • Loading branch information
sunfishcode committed Jun 12, 2024
1 parent 884a549 commit 6a73619
Show file tree
Hide file tree
Showing 14 changed files with 540 additions and 68 deletions.
2 changes: 1 addition & 1 deletion src/condvar.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! The following is derived from Rust's
//! library/std/src/sync/condvar.rs at revision
//! d67d9890ae20492e26803d70b993ab5b03785890.
//! 22a5267c83a3e17f2b763279eb24bb632c45dc6b.
/*
#[cfg(test)]
Expand Down
4 changes: 2 additions & 2 deletions src/futex_condvar.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! The following is derived from Rust's
//! library/std/src/sys/unix/locks/futex_condvar.rs at revision
//! 98815742cf2e914ee0d7142a02322cf939c47834.
//! library/std/src/sys/sync/condvar/futex.rs at revision
//! 22a5267c83a3e17f2b763279eb24bb632c45dc6b.
use core::sync::atomic::{AtomicU32, Ordering::Relaxed};
use super::wait_wake::{futex_wait, futex_wake, futex_wake_all};
Expand Down
50 changes: 27 additions & 23 deletions src/futex_mutex.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
//! The following is derived from Rust's
//! library/std/src/sys/unix/locks/futex_mutex.rs at revision
//! 98815742cf2e914ee0d7142a02322cf939c47834.
//! library/std/src/sys/sync/mutex/futex.rs at revision
//! 22a5267c83a3e17f2b763279eb24bb632c45dc6b.
use core::sync::atomic::{
AtomicU32,
self,
Ordering::{Acquire, Relaxed, Release},
};
use super::wait_wake::{futex_wait_timespec, futex_wake};

type Atomic = atomic::AtomicU32;
type State = u32;

#[repr(transparent)]
pub struct Mutex {
/// 0: unlocked
/// 1: locked, no other threads waiting
/// 2: locked, and other threads waiting (contended)
futex: AtomicU32,
futex: Atomic,
}

const UNLOCKED: State = 0;
const LOCKED: State = 1; // locked, no other threads waiting
const CONTENDED: State = 2; // locked, and other threads waiting (contended)

impl Mutex {
#[inline]
pub const fn new() -> Self {
Self { futex: AtomicU32::new(0) }
Self { futex: Atomic::new(UNLOCKED) }
}

#[inline]
pub fn try_lock(&self) -> bool {
self.futex.compare_exchange(0, 1, Acquire, Relaxed).is_ok()
self.futex.compare_exchange(UNLOCKED, LOCKED, Acquire, Relaxed).is_ok()
}

#[inline]
pub fn lock(&self) {
if self.futex.compare_exchange(0, 1, Acquire, Relaxed).is_err() {
if self.futex.compare_exchange(UNLOCKED, LOCKED, Acquire, Relaxed).is_err() {
self.lock_contended();
}
}
Expand All @@ -41,40 +45,40 @@ impl Mutex {

// If it's unlocked now, attempt to take the lock
// without marking it as contended.
if state == 0 {
match self.futex.compare_exchange(0, 1, Acquire, Relaxed) {
if state == UNLOCKED {
match self.futex.compare_exchange(UNLOCKED, LOCKED, Acquire, Relaxed) {
Ok(_) => return, // Locked!
Err(s) => state = s,
}
}

loop {
// Put the lock in contended state.
// We avoid an unnecessary write if it as already set to 2,
// We avoid an unnecessary write if it as already set to CONTENDED,
// to be friendlier for the caches.
if state != 2 && self.futex.swap(2, Acquire) == 0 {
// We changed it from 0 to 2, so we just successfully locked it.
if state != CONTENDED && self.futex.swap(CONTENDED, Acquire) == UNLOCKED {
// We changed it from UNLOCKED to CONTENDED, so we just successfully locked it.
return;
}

// Wait for the futex to change state, assuming it is still 2.
futex_wait_timespec(&self.futex, 2, None);
// Wait for the futex to change state, assuming it is still CONTENDED.
futex_wait_timespec(&self.futex, CONTENDED, None);

// Spin again after waking up.
state = self.spin();
}
}

fn spin(&self) -> u32 {
fn spin(&self) -> State {
let mut spin = 100;
loop {
// We only use `load` (and not `swap` or `compare_exchange`)
// while spinning, to be easier on the caches.
let state = self.futex.load(Relaxed);

// We stop spinning when the mutex is unlocked (0),
// but also when it's contended (2).
if state != 1 || spin == 0 {
// We stop spinning when the mutex is UNLOCKED,
// but also when it's CONTENDED.
if state != LOCKED || spin == 0 {
return state;
}

Expand All @@ -85,9 +89,9 @@ impl Mutex {

#[inline]
pub unsafe fn unlock(&self) {
if self.futex.swap(0, Release) == 2 {
if self.futex.swap(UNLOCKED, Release) == CONTENDED {
// We only wake up one thread. When that thread locks the mutex, it
// will mark the mutex as contended (2) (see lock_contended above),
// will mark the mutex as CONTENDED (see lock_contended above),
// which makes sure that any other waiting threads will also be
// woken up eventually.
self.wake();
Expand Down
4 changes: 2 additions & 2 deletions src/futex_once.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! The following is derived from Rust's
//! library/std/src/sys_common/once/futex.rs at revision
//! c1cced8d040a26f27490531e7dbffda54642982f.
//! library/std/src/sys/sync/once/futex.rs at revision
//! 22a5267c83a3e17f2b763279eb24bb632c45dc6b.
use core::cell::Cell;
use crate as public;
Expand Down
4 changes: 2 additions & 2 deletions src/futex_rwlock.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! The following is derived from Rust's
//! library/std/src/sys/unix/locks/futex_rwlock.rs at revision
//! 98815742cf2e914ee0d7142a02322cf939c47834.
//! library/std/src/sys/sync/rwlock/futex.rs at revision
//! 22a5267c83a3e17f2b763279eb24bb632c45dc6b.
use core::sync::atomic::{
AtomicU32,
Expand Down
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ pub type RwLock<T> = lock_api::RwLock<RawRwLock, T>;
#[cfg(feature = "lock_api")]
pub type MutexGuard<'a, T> = lock_api::MutexGuard<'a, RawMutex, T>;
#[cfg(feature = "lock_api")]
pub type MappedMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, RawMutex, T>;
#[cfg(feature = "lock_api")]
pub type RwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, RawRwLock, T>;
#[cfg(feature = "lock_api")]
pub type RwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, RawRwLock, T>;
#[cfg(feature = "lock_api")]
pub type MappedRwLockReadGuard<'a, T> = lock_api::MappedRwLockReadGuard<'a, RawRwLock, T>;
#[cfg(feature = "lock_api")]
pub type MappedRwLockWriteGuard<'a, T> = lock_api::MappedRwLockWriteGuard<'a, RawRwLock, T>;
#[cfg(feature = "lock_api")]
#[cfg(feature = "atomic_usize")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "atomic_usize")))]
pub type ReentrantMutex<G, T> = lock_api::ReentrantMutex<RawMutex, G, T>;
Expand Down
2 changes: 1 addition & 1 deletion src/once.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! The following is derived from Rust's
//! library/std/src/sync/once.rs at revision
//! c95015c2955e8507f93a1106fa3f7eaafc25308b.
//! 22a5267c83a3e17f2b763279eb24bb632c45dc6b.
//!
//! A "once initialization" primitive
//!
Expand Down
170 changes: 158 additions & 12 deletions src/once_lock.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! The following is derived from Rust's
//! library/std/src/sync/once_lock.rs at revision
//! a8a5704f1bc1511ddf3720a16a1ff82d3076e884.
//! ee04e0f35ed516e4f1cc6a12c28838eaf98a16d1.
use core::cell::UnsafeCell;
use core::fmt;
Expand All @@ -9,30 +9,93 @@ use core::mem::MaybeUninit;
use core::panic::{RefUnwindSafe, UnwindSafe};
use crate::Once;

/// A synchronization primitive which can be written to only once.
/// A synchronization primitive which can nominally be written to only once.
///
/// This type is a thread-safe [`OnceCell`], and can be used in statics.
/// In many simple cases, you can use [`LazyLock<T, F>`] instead to get the benefits of this type
/// with less effort: `LazyLock<T, F>` "looks like" `&T` because it initializes with `F` on deref!
/// Where OnceLock shines is when LazyLock is too simple to support a given case, as LazyLock
/// doesn't allow additional inputs to its function after you call [`LazyLock::new(|| ...)`].
///
/// [`OnceCell`]: core::cell::OnceCell
/// [`LazyLock<T, F>`]: https://doc.rust-lang.org/std/sync/struct.LazyLock.html
/// [`LazyLock::new(|| ...)`]: https://doc.rust-lang.org/std/sync/struct.LazyLock.html#method.new
///
/// # Examples
///
/// Writing to a `OnceLock` from a separate thread:
///
/// ```
/// use rustix_futex_sync::OnceLock;
///
/// static CELL: OnceLock<String> = OnceLock::new();
/// static CELL: OnceLock<usize> = OnceLock::new();
///
/// // `OnceLock` has not been written to yet.
/// assert!(CELL.get().is_none());
///
/// // Spawn a thread and write to `OnceLock`.
/// std::thread::spawn(|| {
/// let value: &String = CELL.get_or_init(|| {
/// "Hello, World!".to_string()
/// });
/// assert_eq!(value, "Hello, World!");
/// }).join().unwrap();
/// let value = CELL.get_or_init(|| 12345);
/// assert_eq!(value, &12345);
/// })
/// .join()
/// .unwrap();
///
/// // `OnceLock` now contains the value.
/// assert_eq!(
/// CELL.get(),
/// Some(&12345),
/// );
/// ```
///
/// You can use `OnceLock` to implement a type that requires "append-only" logic:
///
/// ```
/// use std::sync::atomic::{AtomicU32, Ordering};
/// use rustix_futex_sync::OnceLock;
/// use std::thread;
///
/// struct OnceList<T> {
/// data: OnceLock<T>,
/// next: OnceLock<Box<OnceList<T>>>,
/// }
/// impl<T> OnceList<T> {
/// const fn new() -> OnceList<T> {
/// OnceList { data: OnceLock::new(), next: OnceLock::new() }
/// }
/// fn push(&self, value: T) {
/// // FIXME: this impl is concise, but is also slow for long lists or many threads.
/// // as an exercise, consider how you might improve on it while preserving the behavior
/// if let Err(value) = self.data.set(value) {
/// let next = self.next.get_or_init(|| Box::new(OnceList::new()));
/// next.push(value)
/// };
/// }
/// fn contains(&self, example: &T) -> bool
/// where
/// T: PartialEq,
/// {
/// self.data.get().map(|item| item == example).filter(|v| *v).unwrap_or_else(|| {
/// self.next.get().map(|next| next.contains(example)).unwrap_or(false)
/// })
/// }
/// }
///
/// // Let's exercise this new Sync append-only list by doing a little counting
/// static LIST: OnceList<u32> = OnceList::new();
/// static COUNTER: AtomicU32 = AtomicU32::new(0);
///
/// let vec = (0..thread::available_parallelism().unwrap().get()).map(|_| thread::spawn(|| {
/// while let i @ 0..=1000 = COUNTER.fetch_add(1, Ordering::Relaxed) {
/// LIST.push(i);
/// }
/// })).collect::<Vec<thread::JoinHandle<_>>>();
/// vec.into_iter().for_each(|handle| handle.join().unwrap());
///
/// for i in 0..=1000 {
/// assert!(LIST.contains(&i));
/// }
///
/// let value: Option<&String> = CELL.get();
/// assert!(value.is_some());
/// assert_eq!(value.unwrap().as_str(), "Hello, World!");
/// ```
//#[stable(feature = "once_cell", since = "1.70.0")]
pub struct OnceLock<T> {
Expand Down Expand Up @@ -209,7 +272,49 @@ impl<T> OnceLock<T> {
F: FnOnce() -> T,
{
//match self.get_or_try_init(|| Ok::<T, !>(f())) {
match self.get_or_try_init(|| Ok ::<T, ()>(f())) {
match self.get_or_try_init(|| Ok::<T, ()>(f())) {
Ok(val) => val,
Err(()) => panic!(),
}
}

/// Gets the mutable reference of the contents of the cell, initializing
/// it with `f` if the cell was empty.
///
/// Many threads may call `get_mut_or_init` concurrently with different
/// initializing functions, but it is guaranteed that only one function
/// will be executed.
///
/// # Panics
///
/// If `f` panics, the panic is propagated to the caller, and the cell
/// remains uninitialized.
///
/// # Examples
///
/// ```
/// //#![feature(once_cell_get_mut)]
///
/// use rustix_futex_sync::OnceLock;
///
/// let mut cell = OnceLock::new();
/// let value = cell.get_mut_or_init(|| 92);
/// assert_eq!(*value, 92);
///
/// *value += 2;
/// assert_eq!(*value, 94);
///
/// let value = cell.get_mut_or_init(|| unreachable!());
/// assert_eq!(*value, 94);
/// ```
#[inline]
//#[unstable(feature = "once_cell_get_mut", issue = "121641")]
pub fn get_mut_or_init<F>(&mut self, f: F) -> &mut T
where
F: FnOnce() -> T,
{
//match self.get_mut_or_try_init(|| Ok::<T, !>(f())) {
match self.get_mut_or_try_init(|| Ok::<T, ()>(f())) {
Ok(val) => val,
Err(()) => panic!(),
}
Expand Down Expand Up @@ -266,6 +371,47 @@ impl<T> OnceLock<T> {
Ok(unsafe { self.get_unchecked() })
}

/// Gets the mutable reference of the contents of the cell, initializing
/// it with `f` if the cell was empty. If the cell was empty and `f` failed,
/// an error is returned.
///
/// # Panics
///
/// If `f` panics, the panic is propagated to the caller, and
/// the cell remains uninitialized.
///
/// # Examples
///
/// ```
/// //#![feature(once_cell_get_mut)]
///
/// use rustix_futex_sync::OnceLock;
///
/// let mut cell: OnceLock<u32> = OnceLock::new();
///
/// // Failed initializers do not change the value
/// assert!(cell.get_mut_or_try_init(|| "not a number!".parse()).is_err());
/// assert!(cell.get().is_none());
///
/// let value = cell.get_mut_or_try_init(|| "1234".parse());
/// assert_eq!(value, Ok(&mut 1234));
/// *value.unwrap() += 2;
/// assert_eq!(cell.get(), Some(&1236))
/// ```
#[inline]
//#[unstable(feature = "once_cell_get_mut", issue = "121641")]
pub fn get_mut_or_try_init<F, E>(&mut self, f: F) -> Result<&mut T, E>
where
F: FnOnce() -> Result<T, E>,
{
if self.get().is_none() {
self.initialize(f)?;
}
debug_assert!(self.is_initialized());
// SAFETY: The inner value has been initialized
Ok(unsafe { self.get_unchecked_mut() })
}

/// Consumes the `OnceLock`, returning the wrapped value. Returns
/// `None` if the cell was empty.
///
Expand Down
Loading

0 comments on commit 6a73619

Please sign in to comment.