blob: 6138735c8f74afd358e4f0b71d70fa8c7a2da21a [file] [log] [blame]
pub(crate) use imp::*;
#[cfg(not(feature = "std"))]
mod imp {
use alloc::sync::Arc;
use core::sync::atomic::{AtomicPtr, Ordering};
use core::{mem, ptr};
#[derive(Debug, Default)]
pub(crate) struct LazyArc<T> {
// Only written once with a value obtained from `Arc<T>::into_raw`.
// This holds a ref count for the `Arc`, so it is always safe to
// clone the `Arc` given a reference to the `LazyArc`.
value: AtomicPtr<T>,
}
impl<T> Drop for LazyArc<T> {
fn drop(&mut self) {
let value_ptr = self.value.load(Ordering::Acquire);
if !value_ptr.is_null() {
// SAFETY: all writes to `self.value` are pointers obtained from `Arc::into_raw`.
drop(unsafe { Arc::from_raw(value_ptr) });
}
}
}
impl<T> LazyArc<T> {
pub(crate) fn get<E, F: FnOnce() -> Result<T, E>>(&self, f: F) -> Result<Arc<T>, E> {
// Clone an `Arc` given a pointer obtained from `Arc::into_raw`.
// SAFETY: `value_ptr` must be a valid pointer obtained from `Arc<T>::into_raw`.
unsafe fn clone_arc_ptr<T>(value_ptr: *const T) -> Arc<T> {
let value = Arc::from_raw(value_ptr);
let clone = Arc::clone(&value);
mem::forget(value);
clone
}
// Return the existing value if already computed.
// `Ordering::Acquire` is needed so that the content of the loaded `Arc` is
// visible to this thread.
let value_ptr = self.value.load(Ordering::Acquire);
if !value_ptr.is_null() {
// SAFETY: all writes to `self.value` are pointers obtained from `Arc::into_raw`.
return Ok(unsafe { clone_arc_ptr(value_ptr) });
}
// Race to compute and set the value.
let value = f().map(Arc::new)?;
let value_ptr = Arc::into_raw(value);
match self.value.compare_exchange(
ptr::null_mut(),
value_ptr as *mut T,
// Success: `Ordering::Release` is needed so that the content of the stored `Arc`
// is visible to other threads. No ordering is required for the null ptr that is
// loaded, but older rust versions (< 1.64) require that its ordering must not
// be weaker than the failure ordering, so we use `Ordering::AcqRel`.
Ordering::AcqRel,
// Failure: `Ordering::Acquire` is needed so that the content of the loaded `Arc`
// is visible to this thread.
Ordering::Acquire,
) {
Ok(_) => {
// Return the value we computed.
// SAFETY: `value_ptr` was obtained from `Arc::into_raw`.
Ok(unsafe { clone_arc_ptr(value_ptr) })
}
Err(existing_value_ptr) => {
// We lost the race, drop unneeded `value_ptr`.
// SAFETY: `value_ptr` was obtained from `Arc::into_raw`.
drop(unsafe { Arc::from_raw(value_ptr) });
// Return the existing value.
// SAFETY: all writes to `self.value` are pointers obtained from `Arc::into_raw`.
Ok(unsafe { clone_arc_ptr(existing_value_ptr) })
}
}
}
}
}
#[cfg(feature = "std")]
mod imp {
use std::sync::{Arc, Mutex};
#[derive(Debug, Default)]
pub(crate) struct LazyArc<T> {
value: Mutex<Option<Arc<T>>>,
}
impl<T> LazyArc<T> {
pub(crate) fn get<E, F: FnOnce() -> Result<T, E>>(&self, f: F) -> Result<Arc<T>, E> {
let mut lock = self.value.lock().unwrap();
if let Some(value) = &*lock {
return Ok(value.clone());
}
let value = f().map(Arc::new)?;
*lock = Some(value.clone());
Ok(value)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lazy_arc() {
let lazy = LazyArc::default();
let value = lazy.get(|| Err(()));
assert_eq!(value, Err(()));
let value = lazy.get(|| Ok::<i32, ()>(3)).unwrap();
assert_eq!(*value, 3);
let value = lazy.get(|| Err(())).unwrap();
assert_eq!(*value, 3);
}
}