Crate atomic_refcell

source ·
Expand description

Implements a container type providing RefCell-like semantics for objects shared across threads.

RwLock is traditionally considered to be the |Sync| analogue of RefCell. However, for consumers that can guarantee that they will never mutably borrow the contents concurrently with immutable borrows, an RwLock is overkill, and has key disadvantages:

  • Performance: Even the fastest existing implementation of RwLock (that of parking_lot) performs at least two atomic operations during immutable borrows. This makes mutable borrows significantly cheaper than immutable borrows, leading to weird incentives when writing performance-critical code.
  • Features: Implementing AtomicRefCell on top of RwLock makes it impossible to implement useful things like AtomicRef{,Mut}::map.

As such, we re-implement RefCell semantics from scratch with a single atomic reference count. The primary complication of this scheme relates to keeping things in a consistent state when one thread performs an illegal borrow and panics. Since an AtomicRefCell can be accessed by multiple threads, and since panics are recoverable, we need to ensure that an illegal (panicking) access by one thread does not lead to undefined behavior on other, still-running threads.

So we represent things as follows:

  • Any value with the high bit set (so half the total refcount space) indicates a mutable borrow.
  • Mutable borrows perform an atomic compare-and-swap, swapping in the high bit if the current value is zero. If the current value is non-zero, the thread panics and the value is left undisturbed.
  • Immutable borrows perform an atomic increment. If the new value has the high bit set, the thread panics. The incremented refcount is left as-is, since it still represents a valid mutable borrow. When the mutable borrow is released, the refcount is set unconditionally to zero, clearing any stray increments by panicked threads.

There are a few additional purely-academic complications to handle overflow, which are documented in the implementation.

The rest of this module is mostly derived by copy-pasting the implementation of RefCell and fixing things up as appropriate. Certain non-threadsafe methods have been removed. We segment the concurrency logic from the rest of the code to keep the tricky parts small and easy to audit.

Structs§