Hello! I am looking just for some clarification on part of the downgrade method on the Arc type.
Towards the end of Chapter 6 of Mara Bos' Brilliant 'Rust Atomics and Locks' book, the final optimization on the handling of Arc and Weak pointer types includes adding a spin loop on the downgrade method. This involves checking whether the alloc_ref_count
has been 'locked' i.e. set usize::MAX
:
pub fn downgrade(arc: &Self) -> Weak<T> {
let mut n = arc.data().alloc_ref_count.load(Relaxed);
loop {
if n == usize::MAX {
std::hint::spin_loop();
n = arc.data().alloc_ref_count.load(Relaxed);
continue;
}
assert!(n <= usize::MAX / 2);
// Acquire synchronises with get_mut's release-store.
if let Err(e) =
arc.data()
.alloc_ref_count
.compare_exchange_weak(n, n + 1, Acquire, Relaxed)
{
n = e;
continue;
}
return Weak { ptr: arc.ptr };
}
}
So far as I can see, alloc_ref_count
is only set to usize::MAX
in get_mut.
I'm a little unclear on why this check in downgrade is needed, as my understanding of get_mut is that if arc.data().alloc_ref_count.compare_exchange(1, usize::MAX, Acquire, Relaxed)
succeeds, then we are guaranteed to have the only Arc around, so no other threads would be able to call downgrade I wouldn't have thought?
pub fn get_mut(arc: &mut Self) -> Option<&mut T> {
// Acquire matches Weak::drop's Release decrement, to make sure any
// upgraded pointers are visible in the next data_ref_count.load.
if arc.data().alloc_ref_count.compare_exchange(
1, usize::MAX, Acquire, Relaxed
).is_err() {
return None;
}
let is_unique = arc.data().data_ref_count.load(Relaxed) == 1;
// Release matches Acquire increment in `downgrade`, to make sure any
// changes to the data_ref_count that come after `downgrade` don't
// change the is_unique result above.
arc.data().alloc_ref_count.store(1, Release);
if !is_unique {
return None;
}
// Acquire to match Arc::drop's Release decrement, to make sure nothing
// else is accessing the data.
fence(Acquire);
unsafe { Some(&mut *arc.data().data.get()) }
}
Any idea what I'm missing in my understanding?