Closed jeehoonkang closed 6 years ago
Hi @jeehoonkang, thanks for your kind words, I'm glad you find my project useful 😀!
When I first wrote pinboard I had the same worry as you (though less clearly formed) and asked on the #rust IRC channel. One of the crossbeam developers had a look and thought it looked okay, but he commented that the unlinked()
call contains a AcqRel
which meant that it would be fine. Possibly that's not true any more in the new crossbeam-epoch
crate?
On the other hand, I intend my code outside crossbeam should be correct in and of itself, so if AcqRel
is the right thing to do, I'd like to make the change. I'm not too familiar with the memory orderings so bear with me here (actually, I've convinced myself you're right, but I'll leave my notes here for posterity).
Release
When coupled with a store, all previous writes become visible to the other threads that perform a load with Acquire ordering on the same value.
Acquire
When coupled with a load, all subsequent loads will see data written before a store with Release ordering on the same value in other threads.
AcqRel
When coupled with a load, uses Acquire ordering, and with a store Release ordering.
In the set()
function, we're swapping the value in the store for one in our hands. Hence we're performing both a read and a write. In your case there are two threads doing a set()
simultaneously, they both use Release
ordering, which means that neither of their reads are subject to the terms of the Release
ordering (as neither uses Acquire
) so previous writes in the first thread may not be visible to the second thread (including, as you point out, the constructor of the first thread's value) hence the second thread may read an incomplete value as a Shared<T>
, attempt to call unlinked()
on it and potentially invoke unsafe behaviour. I'm suspicious that unlinked()
doesn't do anything dangerous (at least not until GC time), but I guess there's no guarantee in my code that the constructor of t
becomes visible to the second thread ever :anguished:.
With AcqRel
semantics, the second thread will see all writes in the first thread when it reads the pinboard, so it's certain to see a consistent view.
Fix released in version 1.4.1. Thanks!
I think, in
set()
andclear()
, it's necessary to swapself.0
with theAcqRel
ordering (instead ofRelease
). Because otherwise,guard.unlinked(t)
is not necessarily happening after the initialization of the data contained int
.For example, suppose that an invocation "A" of
set()
writes a valuet
and another invocation "B" ofset()
is replacingt
. Then B's invocation ofguard.unlinked(t)
should happen after A's invocation ofOwned::new(t)
. However, with onlyRelease
ordering we cannot guarantee that.By the way, thank you for building this wonderful library. I needed pinboard in building a new version of Crossbeam, so I reimplemented it with the name
AtomicBox
: https://github.com/jeehoonkang/crossbeam-epoch/blob/hazard-pointer/src/sync/atomic_box.rs (warning: no documentation at all yet!) I'll properly acknowledge the pinboard crate before submitting it as a PR tocrossbeam-epoch
.