Skip to content

Commit 57fd6cf

Browse files
ThePuzzlemakerGankra
authored andcommitted
Arc revisions (pt1/2(+?))
1 parent 9da6fbf commit 57fd6cf

File tree

7 files changed

+158
-99
lines changed

7 files changed

+158
-99
lines changed

src/SUMMARY.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
* [Base Code](arc-base.md)
6161
* [Cloning](arc-clone.md)
6262
* [Dropping](arc-drop.md)
63-
* [Deref](arc-deref.md)
6463
* [Final Code](arc-final.md)
6564
* [FFI](ffi.md)
6665
* [Beneath `std`](beneath-std.md)

src/arc-base.md

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,6 @@ We'll first need a way to construct an `Arc<T>`.
1010
This is pretty simple, as we just need to box the `ArcInner<T>` and get a
1111
`NonNull<T>` pointer to it.
1212

13-
We start the reference counter at 1, as that first reference is the current
14-
pointer. As the `Arc` is cloned or dropped, it is updated. It is okay to call
15-
`unwrap()` on the `Option` returned by `NonNull` as `Box::into_raw` guarantees
16-
that the pointer returned is not null.
17-
1813
```rust,ignore
1914
impl<T> Arc<T> {
2015
pub fn new(data: T) -> Arc<T> {
@@ -28,7 +23,7 @@ impl<T> Arc<T> {
2823
// It is okay to call `.unwrap()` here as we get a pointer from
2924
// `Box::into_raw` which is guaranteed to not be null.
3025
ptr: NonNull::new(Box::into_raw(boxed)).unwrap(),
31-
_marker: PhantomData,
26+
phantom: PhantomData,
3227
}
3328
}
3429
}
@@ -43,8 +38,8 @@ more information on these, see [the section on `Send` and
4338

4439
This is okay because:
4540
* You can only get a mutable reference to the value inside an `Arc` if and only
46-
if it is the only `Arc` referencing that data
47-
* We use atomic counters for reference counting
41+
if it is the only `Arc` referencing that data (which only happens in `Drop`)
42+
* We use atomics for the shared mutable reference counting
4843

4944
```rust,ignore
5045
unsafe impl<T: Sync + Send> Send for Arc<T> {}
@@ -56,26 +51,59 @@ bounds, it would be possible to share values that are thread-unsafe across a
5651
thread boundary via an `Arc`, which could possibly cause data races or
5752
unsoundness.
5853

59-
## Getting the `ArcInner`
54+
For example, if those bounds were not present, `Arc<Rc<u32>>` would be `Sync` or
55+
`Send`, meaning that you could clone the `Rc` out of the `Arc` to send it across
56+
a thread (without creating an entirely new `Rc`), which would create data races
57+
as `Rc` is not thread-safe.
6058

61-
We'll now want to make a private helper function, `inner()`, which just returns
62-
the dereferenced `NonNull` pointer.
59+
## Getting the `ArcInner`
6360

6461
To dereference the `NonNull<T>` pointer into a `&T`, we can call
6562
`NonNull::as_ref`. This is unsafe, unlike the typical `as_ref` function, so we
6663
must call it like this:
6764
```rust,ignore
68-
// inside the impl<T> Arc<T> block from before:
69-
fn inner(&self) -> &ArcInner<T> {
70-
unsafe { self.ptr.as_ref() }
71-
}
65+
unsafe { self.ptr.as_ref() }
7266
```
7367

68+
We'll be using this snippet a few times in this code (usually with an associated
69+
`let` binding).
70+
7471
This unsafety is okay because while this `Arc` is alive, we're guaranteed that
7572
the inner pointer is valid.
7673

74+
## Deref
75+
76+
Alright. Now we can make `Arc`s (and soon will be able to clone and destroy them correctly), but how do we get
77+
to the data inside?
78+
79+
What we need now is an implementation of `Deref`.
80+
81+
We'll need to import the trait:
82+
```rust,ignore
83+
use std::ops::Deref;
84+
```
85+
86+
And here's the implementation:
87+
```rust,ignore
88+
impl<T> Deref for Arc<T> {
89+
type Target = T;
90+
91+
fn deref(&self) -> &T {
92+
let inner = unsafe { self.ptr.as_ref() };
93+
&inner.data
94+
}
95+
}
96+
```
97+
98+
Pretty simple, eh? This simply dereferences the `NonNull` pointer to the
99+
`ArcInner<T>`, then gets a reference to the data inside.
100+
101+
## Code
102+
77103
Here's all the code from this section:
78104
```rust,ignore
105+
use std::ops::Deref;
106+
79107
impl<T> Arc<T> {
80108
pub fn new(data: T) -> Arc<T> {
81109
// We start the reference count at 1, as that first reference is the
@@ -88,17 +116,21 @@ impl<T> Arc<T> {
88116
// It is okay to call `.unwrap()` here as we get a pointer from
89117
// `Box::into_raw` which is guaranteed to not be null.
90118
ptr: NonNull::new(Box::into_raw(boxed)).unwrap(),
91-
_marker: PhantomData,
119+
phantom: PhantomData,
92120
}
93121
}
94-
95-
fn inner(&self) -> &ArcInner<T> {
96-
// This unsafety is okay because while this Arc is alive, we're
97-
// guaranteed that the inner pointer is valid.
98-
unsafe { self.ptr.as_ref() }
99-
}
100122
}
101123
102124
unsafe impl<T: Sync + Send> Send for Arc<T> {}
103125
unsafe impl<T: Sync + Send> Sync for Arc<T> {}
126+
127+
128+
impl<T> Deref for Arc<T> {
129+
type Target = T;
130+
131+
fn deref(&self) -> &T {
132+
let inner = unsafe { self.ptr.as_ref() };
133+
&inner.data
134+
}
135+
}
104136
```

src/arc-clone.md

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
Now that we've got some basic code set up, we'll need a way to clone the `Arc`.
44

55
Basically, we need to:
6-
1. Get the `ArcInner` value of the `Arc`
7-
2. Increment the atomic reference count
8-
3. Construct a new instance of the `Arc` from the inner pointer
6+
1. Increment the atomic reference count
7+
2. Construct a new instance of the `Arc` from the inner pointer
98

10-
Next, we can update the atomic reference count as follows:
9+
First, we need to get access to the `ArcInner`:
1110
```rust,ignore
12-
self.inner().rc.fetch_add(1, Ordering::Relaxed);
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::Relaxed);
1317
```
1418

1519
As described in [the standard library's implementation of `Arc` cloning][2]:
@@ -30,15 +34,37 @@ We'll need to add another import to use `Ordering`:
3034
use std::sync::atomic::Ordering;
3135
```
3236

33-
It is possible that in some contrived programs (e.g. using `mem::forget`) that
34-
the reference count could overflow, but it's unreasonable that would happen in
35-
any reasonable program.
37+
However, we have one problem with this implementation right now. What if someone
38+
decides to `mem::forget` a bunch of Arcs? The code we have written so far (and
39+
will write) assumes that the reference count accurately portrays how many Arcs
40+
are in memory, but with `mem::forget` this is false. Thus, when more and more
41+
Arcs are cloned from this one without them being `Drop`ped and the reference
42+
count being decremented, we can overflow! This will cause use-after-free which
43+
is **INCREDIBLY BAD!**
44+
45+
To handle this, we need to check that the reference count does not go over some
46+
arbitrary value (below `usize::MAX`, as we're storing the reference count as an
47+
`AtomicUsize`), and do *something*.
48+
49+
The standard library's implementation decides to just abort the program (as it
50+
is an incredibly unlikely case in normal code and if it happens, the program is
51+
probably incredibly degenerate) if the reference count reaches `isize::MAX`
52+
(about half of `usize::MAX`) on any thread, on the assumption that there are
53+
probably not about 2 billion threads (or about **9 quintillion** on some 64-bit
54+
machines) incrementing the reference count at once. This is what we'll do.
55+
56+
It's pretty simple to implement this behaviour:
57+
```rust,ignore
58+
if old_rc >= isize::MAX {
59+
std::process::abort();
60+
}
61+
```
3662

3763
Then, we need to return a new instance of the `Arc`:
3864
```rust,ignore
3965
Self {
4066
ptr: self.ptr,
41-
_marker: PhantomData
67+
phantom: PhantomData
4268
}
4369
```
4470

@@ -48,12 +74,18 @@ use std::sync::atomic::Ordering;
4874
4975
impl<T> Clone for Arc<T> {
5076
fn clone(&self) -> Arc<T> {
77+
let inner = unsafe { self.ptr.as_ref() };
5178
// Using a relaxed ordering is alright here as knowledge of the original
5279
// reference prevents other threads from wrongly deleting the object.
53-
self.inner().rc.fetch_add(1, Ordering::Relaxed);
80+
inner.rc.fetch_add(1, Ordering::Relaxed);
81+
82+
if old_rc >= isize::MAX {
83+
std::process::abort();
84+
}
85+
5486
Self {
5587
ptr: self.ptr,
56-
_marker: PhantomData,
88+
phantom: PhantomData,
5789
}
5890
}
5991
}

src/arc-deref.md

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/arc-drop.md

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,28 @@ low enough, otherwise the data will live forever on the heap.
66
To do this, we can implement `Drop`.
77

88
Basically, we need to:
9-
1. Get the `ArcInner` value of the `Arc`
10-
2. Decrement the reference count
11-
3. If there is only one reference remaining to the data, then:
12-
4. Atomically fence the data to prevent reordering of the use and deletion of
13-
the data, then:
14-
5. Drop the inner data
9+
1. Decrement the reference count
10+
2. If there is only one reference remaining to the data, then:
11+
3. Atomically fence the data to prevent reordering of the use and deletion of
12+
the data
13+
4. Drop the inner data
1514

16-
Now, we need to decrement the reference count. We can also bring in step 3 by
17-
returning if the reference count is not equal to 1 (as `fetch_sub` returns the
18-
previous value):
15+
First, we'll need to get access to the `ArcInner`:
1916
```rust,ignore
20-
if self.inner().rc.fetch_sub(1, Ordering::Release) != 1 {
17+
let inner = unsafe { self.ptr.as_ref() };
18+
```
19+
20+
Now, we need to decrement the reference count. To streamline our code, we can
21+
also return if the returned value from `fetch_sub` (the value of the reference
22+
count before decrementing it) is not equal to `1` (which happens when we are not
23+
the last reference to the data).
24+
```rust,ignore
25+
if inner.rc.fetch_sub(1, Ordering::???) != 1 {
2126
return;
2227
}
2328
```
2429

30+
TODO
2531
We then need to create an atomic fence to prevent reordering of the use of the
2632
data and deletion of the data. As described in [the standard library's
2733
implementation of `Arc`][3]:
@@ -78,7 +84,8 @@ Now, let's wrap this all up inside the `Drop` implementation:
7884
```rust,ignore
7985
impl<T> Drop for Arc<T> {
8086
fn drop(&mut self) {
81-
if self.inner().rc.fetch_sub(1, Ordering::Release) != 1 {
87+
let inner = unsafe { self.ptr.as_ref() };
88+
if inner.rc.fetch_sub(1, Ordering::Release) != 1 {
8289
return;
8390
}
8491
// This fence is needed to prevent reordering of the use and deletion

src/arc-final.md

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::sync::atomic::{self, AtomicUsize, Ordering};
99

1010
pub struct Arc<T> {
1111
ptr: NonNull<ArcInner<T>>,
12-
_marker: PhantomData<ArcInner<T>>,
12+
phantom: PhantomData<ArcInner<T>>,
1313
}
1414

1515
pub struct ArcInner<T> {
@@ -29,36 +29,47 @@ impl<T> Arc<T> {
2929
// It is okay to call `.unwrap()` here as we get a pointer from
3030
// `Box::into_raw` which is guaranteed to not be null.
3131
ptr: NonNull::new(Box::into_raw(boxed)).unwrap(),
32-
_marker: PhantomData,
32+
phantom: PhantomData,
3333
}
3434
}
35-
36-
fn inner(&self) -> &ArcInner<T> {
37-
// This unsafety is okay because while this Arc is alive, we're
38-
// guaranteed that the inner pointer is valid. Also, ArcInner<T> is
39-
// Sync if T is Sync.
40-
unsafe { self.ptr.as_ref() }
41-
}
4235
}
4336

4437
unsafe impl<T: Sync + Send> Send for Arc<T> {}
4538
unsafe impl<T: Sync + Send> Sync for Arc<T> {}
4639

40+
impl<T> Deref for Arc<T> {
41+
type Target = T;
42+
43+
fn deref(&self) -> &T {
44+
let inner = unsafe { self.ptr.as_ref() };
45+
&inner.data
46+
}
47+
}
48+
49+
use std::sync::atomic::Ordering;
50+
4751
impl<T> Clone for Arc<T> {
4852
fn clone(&self) -> Arc<T> {
53+
let inner = unsafe { self.ptr.as_ref() };
4954
// Using a relaxed ordering is alright here as knowledge of the original
5055
// reference prevents other threads from wrongly deleting the object.
51-
self.inner().rc.fetch_add(1, Ordering::Relaxed);
56+
inner.rc.fetch_add(1, Ordering::Relaxed);
57+
58+
if old_rc >= isize::MAX {
59+
std::process::abort();
60+
}
61+
5262
Self {
5363
ptr: self.ptr,
54-
_marker: PhantomData,
64+
phantom: PhantomData,
5565
}
5666
}
5767
}
5868

5969
impl<T> Drop for Arc<T> {
6070
fn drop(&mut self) {
61-
if self.inner().rc.fetch_sub(1, Ordering::Release) != 1 {
71+
let inner = unsafe { self.ptr.as_ref() };
72+
if inner.rc.fetch_sub(1, Ordering::Release) != 1 {
6273
return;
6374
}
6475
// This fence is needed to prevent reordering of the use and deletion
@@ -69,12 +80,4 @@ impl<T> Drop for Arc<T> {
6980
unsafe { Box::from_raw(self.ptr.as_ptr()); }
7081
}
7182
}
72-
73-
impl<T> Deref for Arc<T> {
74-
type Target = T;
75-
76-
fn deref(&self) -> &T {
77-
&self.inner().data
78-
}
79-
}
8083
```

0 commit comments

Comments
 (0)