From ec62dc65a5b0b55b07dbf4db2ecd912dc06506aa Mon Sep 17 00:00:00 2001 From: LinuxHeki Date: Wed, 20 Sep 2023 15:05:55 +0200 Subject: [PATCH] add support for platforms without atomic ptr --- src/race.rs | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/src/race.rs b/src/race.rs index fe36fa1..a15b553 100644 --- a/src/race.rs +++ b/src/race.rs @@ -54,6 +54,7 @@ impl OnceNonZeroUsize { /// /// Returns `Ok(())` if the cell was empty and `Err(())` if it was /// full. + #[cfg(target_has_atomic = "ptr")] #[inline] pub fn set(&self, value: NonZeroUsize) -> Result<(), ()> { let exchange = @@ -64,6 +65,21 @@ impl OnceNonZeroUsize { } } + /// Sets the contents of this cell to `value`. + /// + /// Returns `Ok(())` if the cell was empty and `Err(())` if it was + /// full. + #[cfg(not(target_has_atomic = "ptr"))] + #[inline] + pub fn set(&self, value: NonZeroUsize) -> Result<(), ()> { + if self.inner.load(Ordering::Acquire) == 0 { + self.inner.store(value.get(), Ordering::Release); + Ok(()) + } else { + Err(()) + } + } + /// Gets the contents of the cell, initializing it with `f` if the cell was /// empty. /// @@ -88,6 +104,7 @@ impl OnceNonZeroUsize { /// If several threads concurrently run `get_or_init`, more than one `f` can /// be called. However, all threads will return the same value, produced by /// some `f`. + #[cfg(target_has_atomic = "ptr")] pub fn get_or_try_init(&self, f: F) -> Result where F: FnOnce() -> Result, @@ -107,6 +124,35 @@ impl OnceNonZeroUsize { }; Ok(res) } + + /// Gets 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. + /// + /// If several threads concurrently run `get_or_init`, more than one `f` can + /// be called. However, all threads will return the same value, produced by + /// some `f`. + #[cfg(not(target_has_atomic = "ptr"))] + pub fn get_or_try_init(&self, f: F) -> Result + where + F: FnOnce() -> Result, + { + let val = self.inner.load(Ordering::Acquire); + let res = match NonZeroUsize::new(val) { + Some(it) => it, + None => { + let mut val = f()?.get(); + let old = self.inner.load(Ordering::Acquire); + if old == 0 { + self.inner.store(val, Ordering::Release); + } else { + val = old; + } + unsafe { NonZeroUsize::new_unchecked(val) } + } + }; + Ok(res) + } } /// A thread-safe cell which can be written to only once. @@ -212,6 +258,7 @@ impl<'a, T> OnceRef<'a, T> { /// /// Returns `Ok(())` if the cell was empty and `Err(value)` if it was /// full. + #[cfg(target_has_atomic = "ptr")] pub fn set(&self, value: &'a T) -> Result<(), ()> { let ptr = value as *const T as *mut T; let exchange = @@ -222,6 +269,21 @@ impl<'a, T> OnceRef<'a, T> { } } + /// Sets the contents of this cell to `value`. + /// + /// Returns `Ok(())` if the cell was empty and `Err(value)` if it was + /// full. + #[cfg(not(target_has_atomic = "ptr"))] + pub fn set(&self, value: &'a T) -> Result<(), ()> { + let ptr = value as *const T as *mut T; + if self.inner.load(Ordering::Acquire) == ptr::null_mut() { + self.inner.store(ptr, Ordering::Release); + Ok(()) + } else { + Err(()) + } + } + /// Gets the contents of the cell, initializing it with `f` if the cell was /// empty. /// @@ -246,6 +308,7 @@ impl<'a, T> OnceRef<'a, T> { /// If several threads concurrently run `get_or_init`, more than one `f` can /// be called. However, all threads will return the same value, produced by /// some `f`. + #[cfg(target_has_atomic = "ptr")] pub fn get_or_try_init(&self, f: F) -> Result<&'a T, E> where F: FnOnce() -> Result<&'a T, E>, @@ -269,6 +332,34 @@ impl<'a, T> OnceRef<'a, T> { Ok(unsafe { &*ptr }) } + /// Gets 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. + /// + /// If several threads concurrently run `get_or_init`, more than one `f` can + /// be called. However, all threads will return the same value, produced by + /// some `f`. + #[cfg(not(target_has_atomic = "ptr"))] + pub fn get_or_try_init(&self, f: F) -> Result<&'a T, E> + where + F: FnOnce() -> Result<&'a T, E>, + { + let mut ptr = self.inner.load(Ordering::Acquire); + + if ptr.is_null() { + // TODO replace with `cast_mut` when MSRV reaches 1.65.0 (also in `set`) + ptr = f()? as *const T as *mut T; + let old = self.inner.load(Ordering::Acquire); + if old == ptr::null_mut() { + self.inner.store(ptr, Ordering::Release); + } else { + ptr = old; + } + } + + Ok(unsafe { &*ptr }) + } + /// ```compile_fail /// use once_cell::race::OnceRef; /// @@ -343,6 +434,7 @@ mod once_box { /// /// Returns `Ok(())` if the cell was empty and `Err(value)` if it was /// full. + #[cfg(target_has_atomic = "ptr")] pub fn set(&self, value: Box) -> Result<(), Box> { let ptr = Box::into_raw(value); let exchange = self.inner.compare_exchange( @@ -358,6 +450,22 @@ mod once_box { Ok(()) } + /// Sets the contents of this cell to `value`. + /// + /// Returns `Ok(())` if the cell was empty and `Err(value)` if it was + /// full. + #[cfg(not(target_has_atomic = "ptr"))] + pub fn set(&self, value: Box) -> Result<(), Box> { + let ptr = Box::into_raw(value); + if self.inner.load(Ordering::Acquire) == ptr::null_mut() { + self.inner.store(ptr, Ordering::Release); + Ok(()) + } else { + let value = unsafe { Box::from_raw(ptr) }; + Err(value) + } + } + /// Gets the contents of the cell, initializing it with `f` if the cell was /// empty. /// @@ -382,6 +490,7 @@ mod once_box { /// If several threads concurrently run `get_or_init`, more than one `f` can /// be called. However, all threads will return the same value, produced by /// some `f`. + #[cfg(target_has_atomic = "ptr")] pub fn get_or_try_init(&self, f: F) -> Result<&T, E> where F: FnOnce() -> Result, E>, @@ -404,6 +513,34 @@ mod once_box { }; Ok(unsafe { &*ptr }) } + + /// Gets 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. + /// + /// If several threads concurrently run `get_or_init`, more than one `f` can + /// be called. However, all threads will return the same value, produced by + /// some `f`. + #[cfg(not(target_has_atomic = "ptr"))] + pub fn get_or_try_init(&self, f: F) -> Result<&T, E> + where + F: FnOnce() -> Result, E>, + { + let mut ptr = self.inner.load(Ordering::Acquire); + + if ptr.is_null() { + let val = f()?; + ptr = Box::into_raw(val); + let old = self.inner.load(Ordering::Acquire); + if old == ptr::null_mut() { + self.inner.store(ptr, Ordering::Release); + } else { + drop(unsafe { Box::from_raw(ptr) }); + ptr = old; + } + }; + Ok(unsafe { &*ptr }) + } } unsafe impl Sync for OnceBox {}