Skip to content

Commit

Permalink
Update to the latest upstream Rust code. (#12)
Browse files Browse the repository at this point in the history
* Update to the latest upstream Rust code.

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.

This also adds public type aliases for `MappedMutexGuard`, `MappedRwLockReadGuard`, and `MappedRwLockWriteGuard`.
  • Loading branch information
sunfishcode authored Jun 12, 2024
1 parent 884a549 commit 0489844
Show file tree
Hide file tree
Showing 15 changed files with 541 additions and 69 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 0489844

Please sign in to comment.