|
|
|
@ -32,45 +32,37 @@ if inner.rc.fetch_sub(1, Ordering::Release) != 1 {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
We then need to create an atomic fence to prevent reordering of the use of the
|
|
|
|
|
data and deletion of the data. As described in [the standard library's
|
|
|
|
|
implementation of `Arc`][3]:
|
|
|
|
|
> This fence is needed to prevent reordering of use of the data and deletion of
|
|
|
|
|
> the data. Because it is marked `Release`, the decreasing of the reference
|
|
|
|
|
> count synchronizes with this `Acquire` fence. This means that use of the data
|
|
|
|
|
> happens before decreasing the reference count, which happens before this
|
|
|
|
|
> fence, which happens before the deletion of the data.
|
|
|
|
|
>
|
|
|
|
|
> As explained in the [Boost documentation][1],
|
|
|
|
|
>
|
|
|
|
|
> > It is important to enforce any possible access to the object in one
|
|
|
|
|
> > thread (through an existing reference) to *happen before* deleting
|
|
|
|
|
> > the object in a different thread. This is achieved by a "release"
|
|
|
|
|
> > operation after dropping a reference (any access to the object
|
|
|
|
|
> > through this reference must obviously happened before), and an
|
|
|
|
|
> > "acquire" operation before deleting the object.
|
|
|
|
|
>
|
|
|
|
|
> In particular, while the contents of an Arc are usually immutable, it's
|
|
|
|
|
> possible to have interior writes to something like a Mutex<T>. Since a Mutex
|
|
|
|
|
> is not acquired when it is deleted, we can't rely on its synchronization logic
|
|
|
|
|
> to make writes in thread A visible to a destructor running in thread B.
|
|
|
|
|
>
|
|
|
|
|
> Also note that the Acquire fence here could probably be replaced with an
|
|
|
|
|
> Acquire load, which could improve performance in highly-contended situations.
|
|
|
|
|
> See [2].
|
|
|
|
|
>
|
|
|
|
|
> [1]: https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html
|
|
|
|
|
> [2]: https://github.com/rust-lang/rust/pull/41714
|
|
|
|
|
[3]: https://github.com/rust-lang/rust/blob/e1884a8e3c3e813aada8254edfa120e85bf5ffca/library/alloc/src/sync.rs#L1440-L1467
|
|
|
|
|
Why do we use `Release` here? Well, the `Release` ordering ensures
|
|
|
|
|
that any writes to the data from other threads happen-before this
|
|
|
|
|
decrementing of the reference count.
|
|
|
|
|
|
|
|
|
|
If we succeed, however, we need further guarantees. We must use an
|
|
|
|
|
`Acquire` fence, which ensures that the decrement of the reference
|
|
|
|
|
count happens-before our deletion of the data.
|
|
|
|
|
|
|
|
|
|
To do this, we do the following:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
# use std::sync::atomic::Ordering;
|
|
|
|
|
<!-- ignore: simplified code -->
|
|
|
|
|
```rust,ignore
|
|
|
|
|
use std::sync::atomic;
|
|
|
|
|
atomic::fence(Ordering::Acquire);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
We could have used `AcqRel` for the `fetch_sub` operation, but that
|
|
|
|
|
would give more guarantees than we need when we *don't* succeed. On
|
|
|
|
|
some platforms, using an `AcqRel` ordering for *every* `Drop` may have
|
|
|
|
|
an impact on performance. While this is a niche optimization, it can't
|
|
|
|
|
hurt--also, it helps to further convey the guarantees necessary not
|
|
|
|
|
only to the processor but also to readers of the code.
|
|
|
|
|
|
|
|
|
|
With the combination of these two synchronization points, we ensure
|
|
|
|
|
that, to our thread, the following order of events manifests:
|
|
|
|
|
- Data used by our/other thread
|
|
|
|
|
- Reference count decremented by our thread
|
|
|
|
|
- Data deleted by our thread
|
|
|
|
|
This way, we ensure that our data is not dropped while it is still
|
|
|
|
|
in use.
|
|
|
|
|
|
|
|
|
|
Finally, we can drop the data itself. We use `Box::from_raw` to drop the boxed
|
|
|
|
|
`ArcInner<T>` and its data. This takes a `*mut T` and not a `NonNull<T>`, so we
|
|
|
|
|
must convert using `NonNull::as_ptr`.
|
|
|
|
|