Memory fences and general improvements
EricLBuehler opened this issue · 2 comments
Quoted from u/TDplay from this comment.
Todo
-
Weak::to_trc
race condition -
Weak::clone
race condition -
impl Clone for SharedTrc
-
Support no_std
-
Trc::cmp
documentation - Fix
Pointer
behavior - Use different
Layout
API. - Use
ptr::drop_in_place
instead ofptr::read
Quote
Line 245 in 89f3eb2
Not entirely sure about this part. You seem to decrement and check the weak count, and then construct and drop a Weak (thus calling Weak::drop, which should also decrement and check the weak count).
It also seems like you only drop the data when the weak count reaches zero, which seems to defeat the point of supporting Weak pointers.
Another point is to perhaps consider using ptr::drop_in_place instead of ptr::read. It makes the intent more obvious, and it can make some optimisations more obvious to the compiler (since it doesn't need to reason about removing the copy). I think this would also allow you to support dynamically-sized types.
Trc::drop looks fine.
Line 1148 in 89f3eb2
Weak::to_trc has a race condition, leading to a possible use-after-free.
Initial state: We have a SharedTrc in thread A and a Weak in thread B.
Thread B calls Weak::to_trc
Weak::to_trc checks the strong count, and reads 1
Thread A drops the SharedTrc, decrementing the strong count.
SharedTrc::drop notices that the strong count is now zero, so it deallocates the data.
Weak::to_trc increments the strong count and constructs a Trc.
The Trc is now passed to user code, which access the contents, causing a use-after-free.
To resolve this, the zero-check and the increment need to be one atomic operation, and the increment needs to not happen if the zero check fails. fetch_update is perfect for this. Sadly, this is slower than fetch_add, but I can't see a way to avoid it, and it seems the standard library developers couldn't either.
Line 1191 in 89f3eb2
Clone for Weak technically has a race condition: if a program clones an absurd number of Weaks in different threads, with thread being pre-empted before it can call abort, it may be possible to overflow the weak count. In practice, I don't think any real program will ever do this, and the actual likelihood of this behaviour is slim.
That's the big stuff out of the way, on to the small nitpicks:
Clone is not implemented for SharedTrc, this feels like an oversight.
You might be able to support no_std. Add this to your lib.rs:
#![cfg_attr(not(test), no_std)]
extern crate alloc;
#[cfg(feature = "force_lock")]
extern crate std;
It will then throw errors at every use of std. Replace these with either core or alloc.
Line 899 in 89f3eb2
Documentation for Trc::cmp doesn't seem right.
Line 793 in 89f3eb2
Behaviour of the Pointer formatting seems a little strange. Trc is a pointer, so it should probably print the address the Trc points to. This would also be in-line with what the standard library smart pointers do: Arc, Rc, Box, &T, and &mut T all implement Pointer unconditionally.
Line 1043 in 89f3eb2
There is no need for the unsafe call to Layout::from_size_align_unchecked, you can just use:
Layout::new::<SharedTrcInternal<T>>()
If you choose to support dynamically sized types, then you can instead use:
Layout::for_value
impl Clone for SharedTrc
: Clones do not need any special sync because they can only be created from existing objects. However, refcount bounds checking is necessary.
Closed as all problems are fixed.