use arc_swap::ArcSwap;
use std::sync::{Arc, Weak};
pub struct LazyArc<T> {
weak: ArcSwap<Weak<T>>,
}
impl<T> LazyArc<T> {
#[inline]
pub fn new() -> Self {
Self {
weak: ArcSwap::from(Arc::new(Weak::new())),
}
}
#[inline]
pub fn get_or_build<F>(&self, build: F) -> Arc<T>
where
F: FnOnce() -> T,
{
if let Some(arc) = self.weak.load().upgrade() {
return arc;
}
let arc = Arc::new(build());
self.weak.store(Arc::new(Arc::downgrade(&arc)));
arc
}
#[inline]
pub fn try_get(&self) -> Option<Arc<T>> {
self.weak.load().upgrade()
}
#[inline]
pub fn is_live(&self) -> bool {
self.weak.load().strong_count() > 0
}
}
impl<T> Default for LazyArc<T> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<T> std::fmt::Debug for LazyArc<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LazyArc")
.field("live", &self.is_live())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn first_call_invokes_builder() {
let calls = AtomicUsize::new(0);
let lazy: LazyArc<u64> = LazyArc::new();
let a = lazy.get_or_build(|| {
calls.fetch_add(1, Ordering::Relaxed);
42
});
assert_eq!(*a, 42);
assert_eq!(calls.load(Ordering::Relaxed), 1);
}
#[test]
fn second_call_reuses_while_first_arc_lives() {
let calls = AtomicUsize::new(0);
let lazy: LazyArc<u64> = LazyArc::new();
let a = lazy.get_or_build(|| {
calls.fetch_add(1, Ordering::Relaxed);
42
});
let b = lazy.get_or_build(|| {
calls.fetch_add(1, Ordering::Relaxed);
999
});
assert!(Arc::ptr_eq(&a, &b));
assert_eq!(*b, 42);
assert_eq!(calls.load(Ordering::Relaxed), 1);
}
#[test]
fn rebuilds_after_all_clones_drop() {
let calls = AtomicUsize::new(0);
let lazy: LazyArc<u64> = LazyArc::new();
{
let a = lazy.get_or_build(|| {
calls.fetch_add(1, Ordering::Relaxed);
1
});
let b = lazy.get_or_build(|| {
calls.fetch_add(1, Ordering::Relaxed);
2
});
assert!(Arc::ptr_eq(&a, &b));
assert!(lazy.is_live());
}
assert!(!lazy.is_live(), "expected slot dead after clones drop");
let c = lazy.get_or_build(|| {
calls.fetch_add(1, Ordering::Relaxed);
7
});
assert_eq!(*c, 7);
assert_eq!(calls.load(Ordering::Relaxed), 2);
}
#[test]
fn try_get_does_not_build() {
let lazy: LazyArc<u64> = LazyArc::new();
assert!(lazy.try_get().is_none());
let _a = lazy.get_or_build(|| 5);
assert!(lazy.try_get().is_some());
}
#[test]
fn concurrent_cold_miss_all_callers_succeed() {
let lazy: Arc<LazyArc<u64>> = Arc::new(LazyArc::new());
let total_builds = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..16)
.map(|_| {
let lazy = lazy.clone();
let total_builds = total_builds.clone();
std::thread::spawn(move || {
lazy.get_or_build(|| {
total_builds.fetch_add(1, Ordering::Relaxed);
std::thread::sleep(std::time::Duration::from_micros(50));
100
})
})
})
.collect();
for h in handles {
let arc = h.join().unwrap();
assert_eq!(*arc, 100);
}
let n = total_builds.load(Ordering::Relaxed);
assert!((1..=16).contains(&n), "unexpected build count: {n}");
}
#[test]
fn debug_impl_does_not_panic() {
let lazy: LazyArc<u64> = LazyArc::new();
let _ = format!("{:?}", lazy);
let _arc = lazy.get_or_build(|| 1);
let _ = format!("{:?}", lazy);
}
}