Crate reclaim

Source
Expand description

A generic trait based interface for abstracting over various schemes for concurrent memory reclamation.

§Memory Management in Rust

Unlike garbage collected languages such as Go or Java, memory management in Rust is primarily scope or ownership based and more akin to C++. Rust’s ownership model in combination with the standard library’s smart pointer types Box, Rc and Arc make memory management as painless as possible and are able to handle the vast majority of use-cases, while at the same time preventing the classic memory bugs such as use-after-free, double-free or memory leaks. Consequently, there is usually little need for the relatively small additional comfort provided by a fully automated Garbage Collector (GC).

§The Need for Automatic Memory Reclamation

In the domain of concurrent lock-free data structures, however, the aforementioned memory management schemes are insufficient for determining, when a removed entry can be actually dropped and de-allocated: Just because an entry has been removed (unlinked) from some shared data structure does not guarantee, that no other thread could still be in the process of reading that same entry at the same time. This is due to the possible existence of stale references that were created by other threads before the unlinking occurred. The only fact that can be ascertained, due to nature of atomic swap and compare-and-swap operations, is that other threads can not acquire new references after an entry has been unlinked.

§Extending the Grace Period

Concurrent memory reclamation schemes work by granting every value (record) earmarked for deletion (retired) a certain grace period before being actually dropped and de-allocated. During this period the value will be cached and can still be safely read by other threads with live references to it, but no new references must be possible. Determining the exact length of this grace period is up to each individual reclamation scheme. It is usually either not possible or not practical to determine the exact moment at which it becomes safe to reclaim a retired. Hence, reclamation schemes commonly tend to only guarantee grace periods that are at least as long as to ensure no references can possibly exist afterwards.

§The Reclaim Interface

Lock-free data structures are usually required to work on atomic pointers to heap allocated memory. This is due to the restrictions of atomic CPU instructions to machine-word sized values, such as pointers. Working with raw pointers is inherently unsafe in the Rust sense. Consequently, this crate avoids and discourages the use of raw pointers as much as possible in favor of safer abstractions with stricter constraints for their usage. In effect, the vast majority of this crate’s public API is safe to use under any circumstances. This, however, is achieved by shifting and concentrating the burden of manually maintaining safety invariants into one specific key aspect: The retiring (and eventual reclamation) of records.

§Traits and Types

The reclaim crate primarily exposes four different traits, which are relevant for users of generic code and implementors of reclamation schemes alike. The first trait is Reclaim, which provides generic functionality for retiring records. Note, that this trait does not presume the presence of an operating system and functionality like thread local storage. Hence, this trait can even be used in #[no_std] environments. However, in order to use this trait’s associated methods, an explicit reference to the current thread’s (local) state is required. For environments with implicit access to thread local storage, the GlobalReclaim trait exists as an extension to Reclaim. This trait additionally requires an associated type Guard, which must implement both the Default and the Protect trait.

Types implementing the Protect trait can be used to safely read values from shared memory that are subsequently safe from concurrent reclamation and can hence be safely de-referenced. Note that a single guard can only protect one value at a time. This follows the design of many reclamation schemes, such as hazard pointers. This is represented by the requirement to pass a mutable reference to a guard in order to safely load a shared value.

Some reclamation schemes (e.g. epoch based ones) do not require individual protection of values, but instead protect arbitrary numbers of shared at once. The guard types for these schemes can additionally implement the ProtectRegion trait. Such guards do not have to carry any state and protect values simply by their existence. Consequently, it is also possible to call eg Atomic::load with a shared reference to a guard implementing that trait.

§The Atomic Type

The Atomic markable concurrent pointer type is the main point of interaction with this crate. It can only be safely created as a null pointer or with valid heap allocation backing it up. It supports all common atomic operations like load, store, compare_exchange, etc. The key aspect of this type is that together with a guard, shared values can be safely loaded and de-referenced while other threads can concurrently reclaim removed values. In addition to the Shared type, which represents a shared reference that is protected from reclamation, other atomic operations can yield Unprotected or Unlinked values. The former are explicitly not protected from reclamation and can be loaded without any guards. They are not safe to de-reference, but can be used to e.g. swing a pointer from one linked list node to another. Unlinked values are the result of swap or compare-and-swap operations and represent values/references to which no new references can be acquired any more by other threads. They are like owned values that are also borrowed, since other threads may still reference them. All of these three different types are guaranteed to never be null at the type level.

§Marked Pointer & Reference Types

It is a ubiquitous technique in lock-free programming to use the lower bits of a pointer address to store additional information alongside an address. Common use-cases are ABA problem mitigation or to mark a node of a linked list for removal.

Accordingly, this crate allows all pointer and reference types (MarkedPtr, Shared, etc.) to be marked. The number of usable mark bits is encoded in the type itself as a generic parameter N. However, the number of available mark bits has a physical upper bound, which is dictated by the alignment of the pointed-to type. For instance, a bool has an alignment of 1, hence pointers to boolean values can not, in fact, be marked. On a 64-bit system, an usize has an alignment of 8, which means a pointer to one can use up to 3 mark bits. Since the number N is encoded in the pointer types themselves, attempting to declare types with more available mark bits than what the pointed-to type’s alignment will lead to a (currently fairly cryptic) compile time error. Note, that tags are allowed to overflow. This can lead to surprising results when attempting to e.g. mark a pointer that is declared to support zero mark bits (N = 0), as the tag will be silently truncated.

§Terminology

Throughout this crate’s API and its documentation a certain terminology is consistently used, which is summarized below:

  • record

    A heap allocated value which is managed by some reclamation scheme.

  • unlink

    The act removing the pointer to a record from shared memory through an atomic operation such as compare-and-swap.

  • retire

    The act of marking an unlinked record as no longer in use and handing off the responsibility for de-allocation to the reclamation scheme.

  • reclaim

    The act of dropping and de-allocating a retired record. The reclamation scheme is responsible for guaranteeing that retired records are kept alive (cached) at least until their respective grace periods have expired.

Re-exports§

Modules§

  • Thin wrapper types for adjusting a type’s alignment to increase the number of markable lower bits.
  • A no-op memory reclamation scheme that leaks memory, mainly for exemplary and testing purposes.
  • Useful and/or required types, discriminants and traits for the reclaim crate.

Structs§

  • An atomic markable pointer type to an owned heap allocated value similar to AtomicPtr.
  • A raw pointer type which can be safely shared between threads, which can store additional information in its lower (unused) bits.
  • The returned error type for a failed compare_exchange or compare_exchange_weak operation.
  • A guard type fused with a protected value.
  • An error type for representing failed conversions from nullable to non-nullable pointer types.
  • A non-nullable marked raw pointer type like NonNull.
  • A raw, unsafe pointer type like *mut T in which up to N of the pointer’s lower bits can be used to store additional information (the tag).
  • A zero-size marker type that represents the failure state of an [acquire_if_equal][Protect::acquire_if_equal] operation.
  • A pointer type for heap allocated values similar to Box.
  • A record type that is associated with a specific reclamation scheme.
  • A type-erased fat pointer to a retired record.
  • A shared reference to a value that is actively protected from reclamation by other threads.
  • A reference to a value that has been removed from its previous location in memory and is hence no longer reachable by other threads.
  • A reference to a value loaded from an Atomic that is not actively protected from reclamation.

Enums§

  • A value that represents the possible states of a nullable marked pointer.

Traits§

  • A trait for retiring and reclaiming entries removed from concurrent collections and data structures.
  • Trait for nullable and non-nullable markable pointer types.
  • An sealed (internal) marker trait for non-nullable pointer types.
  • A trait for guard types that protect a specific value from reclamation during the lifetime of the protecting guard.
  • A trait for guard types that protect any values loaded during their existence and lifetime.
  • A trait, which constitutes the foundation for the GlobalReclaim trait.

Type Aliases§

  • Result type for [acquire_if_equal][Protect::acquire_if_equal] operations.