Can `Arc::is_unique` return `true` in this example?
⚓ Rust 📅 2026-06-29 👤 surdeus 👁️ 2Consider this example:
#![feature(arc_is_unique)]
use std::sync::{
Arc,
atomic::{AtomicBool, AtomicPtr, Ordering},
};
use std::thread;
fn main() {
let mut is_unique = false;
let arc = Arc::new(1); // #0
let ptr = AtomicPtr::new(std::ptr::null_mut());
let flag = AtomicBool::new(false);
let ptr_rf = &ptr;
let flag_rf = &flag;
thread::scope(|s| {
// thread 1
s.spawn(move || {
// clone() behaves like `strong.fetch_add(1,Relaxed)`
let arc2 = arc.clone(); // #1
ptr_rf.store(Arc::into_raw(arc2) as *mut i32, Ordering::Relaxed); // #2
while !flag_rf.load(Ordering::Acquire) {} // #3
});
// thread 2
s.spawn(|| {
let raw_arc = loop {
let val = ptr.load(Ordering::Relaxed) as *const i32; // #4
if !val.is_null() {
break val;
}
};
let arc = unsafe { Arc::from_raw(raw_arc as *const _) };
is_unique = Arc::is_unique(&arc); // #5
flag.store(true, Ordering::Release); // #6
});
});
println!("{}", is_unique);
}
In this example, #0 creates the control block and initializes strong and weak to 1, respectively; these modifications all happens-before the beginning of the two threads. The clone() at #1, as explained in the comment, is a modification that increases strong by one.
Since #2 and #4 both use Relaxed memory ordering, the modification at #1 is unordered with #5; In other words, #1 doesn't happen before #5, the modification at #1 is not guaranteed to be visible to #5. According to the write-read coherence rule:
If a side effect X on an atomic object M happens before a value computation B of M, then the evaluation B takes its value from X or from a side effect Y that follows X in the modification order of M.
Such a side effect X in this example is the modification to strong at #0; this means the load to strong within Arc::is_unique is valid to read the initial value 1.
#6 synchronizing with #3 ensures that the load to strong within Arc::is_unique cannot read the modification of the drop of Arc in thread 1, which could be 1. This excludes the case that Arc::is_unqiue returns true because it reads that value.
So, is is_unique in this example possible to be true?
PS:
The implementation of Arc::is_unique is approximately as follows:
pub fn is_unique(this: &Self) -> bool {
// lock the weak pointer count if we appear to be the sole weak pointer
// holder.
//
// The acquire label here ensures a happens-before relationship with any
// writes to `strong` (in particular in `Weak::upgrade`) prior to decrements
// of the `weak` count (via `Weak::drop`, which uses release). If the upgraded
// weak ref was never dropped, the CAS here will fail so we do not care to synchronize.
if this.inner().weak.compare_exchange(1, usize::MAX, Acquire, Relaxed).is_ok() {
// This needs to be an `Acquire` to synchronize with the decrement of the `strong`
// counter in `drop` -- the only access that happens when any but the last reference
// is being dropped.
let unique = this.inner().strong.load(Acquire) == 1;
// The release write here synchronizes with a read in `downgrade`,
// effectively preventing the above read of `strong` from happening
// after the write.
this.inner().weak.store(1, Release); // release the lock
unique
} else {
false
}
}
2 posts - 2 participants
🏷️ Rust_feed