Expand description
Making [Arc
][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>
][Option].
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
[Arc
]s, 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
Structs
Arc
or Option<Arc>
.Traits
Type Definitions
Arc
.Option<Arc>
.