Expand description

Making Arc itself atomic

The ArcSwap type is a container for an Arc that can be changed atomically. Semantically, it is similar to something like Atomic<Arc<T>> (if there was such a thing) or RwLock<Arc<T>> (but without the need for the locking). It is optimized for read-mostly scenarios, with consistent performance characteristics.

Motivation

There are many situations in which one might want to have some data structure that is often read and seldom updated. Some examples might be a configuration of a service, routing tables, snapshot of some data that is renewed every few minutes, etc.

In all these cases one needs:

  • Being able to read the current value of the data structure, fast, often and concurrently from many threads.
  • Using the same version of the data structure over longer period of time ‒ a query should be answered by a consistent version of data, a packet should be routed either by an old or by a new version of the routing table but not by a combination, etc.
  • Perform an update without disrupting the processing.

The first idea would be to use RwLock<T> and keep a read-lock for the whole time of processing. Update would, however, pause all processing until done.

Better option would be to have RwLock<Arc<T>>. Then one would lock, clone the Arc and unlock. This suffers from CPU-level contention (on the lock and on the reference count of the Arc) which makes it relatively slow. Depending on the implementation, an update may be blocked for arbitrary long time by a steady inflow of readers.

static ROUTING_TABLE: Lazy<RwLock<Arc<RoutingTable>>> = Lazy::new(|| {
    RwLock::new(Arc::new(RoutingTable))
});

fn process_packet(packet: Packet) {
    let table = Arc::clone(&ROUTING_TABLE.read().unwrap());
    table.route(packet);
}

The ArcSwap can be used instead, which solves the above problems and has better performance characteristics than the RwLock, both in contended and non-contended scenarios.

static ROUTING_TABLE: Lazy<ArcSwap<RoutingTable>> = Lazy::new(|| {
    ArcSwap::from_pointee(RoutingTable)
});

fn process_packet(packet: Packet) {
    let table = ROUTING_TABLE.load();
    table.route(packet);
}

Crate contents

At the heart of the crate there are ArcSwap and ArcSwapOption types, containers for an Arc and Option<Arc>.

Technically, these are type aliases for partial instantiations of the ArcSwapAny type. The ArcSwapAny is more flexible and allows tweaking of many things (can store other things than Arcs, can configure the locking Strategy). For details about the tweaking, see the documentation of the strategy module and the RefCnt trait.

The cache module provides means for speeding up read access of the contained data at the cost of delayed reclamation.

The access module can be used to do projections into the contained data to separate parts of application from each other (eg. giving a component access to only its own part of configuration while still having it reloaded as a whole).

Before using

The data structure is a bit niche. Before using, please check the limitations and common pitfalls and the performance characteristics, including choosing the right read operation.

You can also get an inspiration about what’s possible in the common patterns section.

Examples

use std::sync::Arc;

use arc_swap::ArcSwap;
use crossbeam_utils::thread;

fn main() {
    let config = ArcSwap::from(Arc::new(String::default()));
    thread::scope(|scope| {
        scope.spawn(|_| {
            let new_conf = Arc::new("New configuration".to_owned());
            config.store(new_conf);
        });
        for _ in 0..10 {
            scope.spawn(|_| {
                loop {
                    let cfg = config.load();
                    if !cfg.is_empty() {
                        assert_eq!(**cfg, "New configuration");
                        return;
                    }
                }
            });
        }
    }).unwrap();
}

Re-exports

pub use crate::cache::Cache;
pub use crate::strategy::DefaultStrategy;
pub use crate::strategy::IndependentStrategy;

Modules

Abstracting over accessing parts of stored value.

Caching handle into the ArcSwapAny.

Additional documentation.

Strategies for protecting the reference counts.

Structs

An atomic storage for a reference counted smart pointer like Arc or Option<Arc>.

A temporary storage of the pointer.

Traits

A trait describing things that can be turned into a raw pointer.

A trait describing smart reference counted pointers.

Type Definitions

An atomic storage for Arc.

An atomic storage for Option<Arc>.