mirror of https://github.com/rust-lang/nomicon
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
103 lines
3.2 KiB
103 lines
3.2 KiB
5 years ago
|
# Cloning
|
||
|
|
||
|
Now that we've got some basic code set up, we'll need a way to clone the `Arc`.
|
||
|
|
||
|
Basically, we need to:
|
||
4 years ago
|
|
||
5 years ago
|
1. Increment the atomic reference count
|
||
|
2. Construct a new instance of the `Arc` from the inner pointer
|
||
5 years ago
|
|
||
5 years ago
|
First, we need to get access to the `ArcInner`:
|
||
4 years ago
|
|
||
5 years ago
|
```rust,ignore
|
||
5 years ago
|
let inner = unsafe { self.ptr.as_ref() };
|
||
|
```
|
||
|
|
||
|
We can update the atomic reference count as follows:
|
||
4 years ago
|
|
||
5 years ago
|
```rust,ignore
|
||
5 years ago
|
let old_rc = inner.rc.fetch_add(1, Ordering::???);
|
||
5 years ago
|
```
|
||
|
|
||
5 years ago
|
But what ordering should we use here? We don't really have any code that will
|
||
|
need atomic synchronization when cloning, as we do not modify the internal value
|
||
|
while cloning. Thus, we can use a Relaxed ordering here, which implies no
|
||
|
happens-before relationship but is atomic. When `Drop`ping the Arc, however,
|
||
|
we'll need to atomically synchronize when decrementing the reference count. This
|
||
|
is described more in [the section on the `Drop` implementation for
|
||
5 years ago
|
`Arc`](arc-drop.md). For more information on atomic relationships and Relaxed
|
||
5 years ago
|
ordering, see [the section on atomics](atomics.md).
|
||
|
|
||
|
Thus, the code becomes this:
|
||
4 years ago
|
|
||
5 years ago
|
```rust,ignore
|
||
|
let old_rc = inner.rc.fetch_add(1, Ordering::Relaxed);
|
||
|
```
|
||
5 years ago
|
|
||
|
We'll need to add another import to use `Ordering`:
|
||
4 years ago
|
|
||
5 years ago
|
```rust,ignore
|
||
|
use std::sync::atomic::Ordering;
|
||
|
```
|
||
|
|
||
5 years ago
|
However, we have one problem with this implementation right now. What if someone
|
||
|
decides to `mem::forget` a bunch of Arcs? The code we have written so far (and
|
||
|
will write) assumes that the reference count accurately portrays how many Arcs
|
||
|
are in memory, but with `mem::forget` this is false. Thus, when more and more
|
||
|
Arcs are cloned from this one without them being `Drop`ped and the reference
|
||
|
count being decremented, we can overflow! This will cause use-after-free which
|
||
|
is **INCREDIBLY BAD!**
|
||
|
|
||
|
To handle this, we need to check that the reference count does not go over some
|
||
|
arbitrary value (below `usize::MAX`, as we're storing the reference count as an
|
||
|
`AtomicUsize`), and do *something*.
|
||
|
|
||
|
The standard library's implementation decides to just abort the program (as it
|
||
|
is an incredibly unlikely case in normal code and if it happens, the program is
|
||
|
probably incredibly degenerate) if the reference count reaches `isize::MAX`
|
||
|
(about half of `usize::MAX`) on any thread, on the assumption that there are
|
||
|
probably not about 2 billion threads (or about **9 quintillion** on some 64-bit
|
||
|
machines) incrementing the reference count at once. This is what we'll do.
|
||
|
|
||
4 years ago
|
It's pretty simple to implement this behavior:
|
||
|
|
||
5 years ago
|
```rust,ignore
|
||
5 years ago
|
if old_rc >= isize::MAX as usize {
|
||
5 years ago
|
std::process::abort();
|
||
|
}
|
||
|
```
|
||
5 years ago
|
|
||
|
Then, we need to return a new instance of the `Arc`:
|
||
4 years ago
|
|
||
5 years ago
|
```rust,ignore
|
||
|
Self {
|
||
|
ptr: self.ptr,
|
||
5 years ago
|
phantom: PhantomData
|
||
5 years ago
|
}
|
||
|
```
|
||
|
|
||
|
Now, let's wrap this all up inside the `Clone` implementation:
|
||
4 years ago
|
|
||
5 years ago
|
```rust,ignore
|
||
|
use std::sync::atomic::Ordering;
|
||
|
|
||
|
impl<T> Clone for Arc<T> {
|
||
|
fn clone(&self) -> Arc<T> {
|
||
5 years ago
|
let inner = unsafe { self.ptr.as_ref() };
|
||
5 years ago
|
// Using a relaxed ordering is alright here as we don't need any atomic
|
||
|
// synchronization here as we're not modifying or accessing the inner
|
||
|
// data.
|
||
5 years ago
|
let old_rc = inner.rc.fetch_add(1, Ordering::Relaxed);
|
||
5 years ago
|
|
||
5 years ago
|
if old_rc >= isize::MAX as usize {
|
||
5 years ago
|
std::process::abort();
|
||
|
}
|
||
|
|
||
5 years ago
|
Self {
|
||
|
ptr: self.ptr,
|
||
5 years ago
|
phantom: PhantomData,
|
||
5 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|