]>
Commit | Line | Data |
---|---|---|
5869c6ff XL |
1 | # Cloning |
2 | ||
3 | Now that we've got some basic code set up, we'll need a way to clone the `Arc`. | |
4 | ||
5 | Basically, we need to: | |
6 | 1. Increment the atomic reference count | |
7 | 2. Construct a new instance of the `Arc` from the inner pointer | |
8 | ||
9 | First, we need to get access to the `ArcInner`: | |
10 | ```rust,ignore | |
11 | let inner = unsafe { self.ptr.as_ref() }; | |
12 | ``` | |
13 | ||
14 | We can update the atomic reference count as follows: | |
15 | ```rust,ignore | |
16 | let old_rc = inner.rc.fetch_add(1, Ordering::???); | |
17 | ``` | |
18 | ||
19 | But what ordering should we use here? We don't really have any code that will | |
20 | need atomic synchronization when cloning, as we do not modify the internal value | |
21 | while cloning. Thus, we can use a Relaxed ordering here, which implies no | |
22 | happens-before relationship but is atomic. When `Drop`ping the Arc, however, | |
23 | we'll need to atomically synchronize when decrementing the reference count. This | |
24 | is described more in [the section on the `Drop` implementation for | |
25 | `Arc`](arc-drop.md). For more information on atomic relationships and Relaxed | |
26 | ordering, see [the section on atomics](atomics.md). | |
27 | ||
28 | Thus, the code becomes this: | |
29 | ```rust,ignore | |
30 | let old_rc = inner.rc.fetch_add(1, Ordering::Relaxed); | |
31 | ``` | |
32 | ||
33 | We'll need to add another import to use `Ordering`: | |
34 | ```rust,ignore | |
35 | use std::sync::atomic::Ordering; | |
36 | ``` | |
37 | ||
38 | However, we have one problem with this implementation right now. What if someone | |
39 | decides to `mem::forget` a bunch of Arcs? The code we have written so far (and | |
40 | will write) assumes that the reference count accurately portrays how many Arcs | |
41 | are in memory, but with `mem::forget` this is false. Thus, when more and more | |
42 | Arcs are cloned from this one without them being `Drop`ped and the reference | |
43 | count being decremented, we can overflow! This will cause use-after-free which | |
44 | is **INCREDIBLY BAD!** | |
45 | ||
46 | To handle this, we need to check that the reference count does not go over some | |
47 | arbitrary value (below `usize::MAX`, as we're storing the reference count as an | |
48 | `AtomicUsize`), and do *something*. | |
49 | ||
50 | The standard library's implementation decides to just abort the program (as it | |
51 | is an incredibly unlikely case in normal code and if it happens, the program is | |
52 | probably incredibly degenerate) if the reference count reaches `isize::MAX` | |
53 | (about half of `usize::MAX`) on any thread, on the assumption that there are | |
54 | probably not about 2 billion threads (or about **9 quintillion** on some 64-bit | |
55 | machines) incrementing the reference count at once. This is what we'll do. | |
56 | ||
57 | It's pretty simple to implement this behaviour: | |
58 | ```rust,ignore | |
59 | if old_rc >= isize::MAX as usize { | |
60 | std::process::abort(); | |
61 | } | |
62 | ``` | |
63 | ||
64 | Then, we need to return a new instance of the `Arc`: | |
65 | ```rust,ignore | |
66 | Self { | |
67 | ptr: self.ptr, | |
68 | phantom: PhantomData | |
69 | } | |
70 | ``` | |
71 | ||
72 | Now, let's wrap this all up inside the `Clone` implementation: | |
73 | ```rust,ignore | |
74 | use std::sync::atomic::Ordering; | |
75 | ||
76 | impl<T> Clone for Arc<T> { | |
77 | fn clone(&self) -> Arc<T> { | |
78 | let inner = unsafe { self.ptr.as_ref() }; | |
79 | // Using a relaxed ordering is alright here as we don't need any atomic | |
80 | // synchronization here as we're not modifying or accessing the inner | |
81 | // data. | |
82 | let old_rc = inner.rc.fetch_add(1, Ordering::Relaxed); | |
83 | ||
84 | if old_rc >= isize::MAX as usize { | |
85 | std::process::abort(); | |
86 | } | |
87 | ||
88 | Self { | |
89 | ptr: self.ptr, | |
90 | phantom: PhantomData, | |
91 | } | |
92 | } | |
93 | } | |
94 | ``` |