rust 130 lines · 3 tabs

Lock-Free Config Reads With Arc-Swap and Copy-on-Write Snapshots in Rust

Shared by codesnips Sep 2026
3 tabs
use std::time::Duration;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Config {
    pub endpoint: String,
    pub timeout: Duration,
    pub max_retries: u32,
    pub feature_flags: Vec<String>,
}

impl Config {
    pub fn new(endpoint: impl Into<String>) -> Self {
        Config {
            endpoint: endpoint.into(),
            timeout: Duration::from_secs(30),
            max_retries: 3,
            feature_flags: Vec::new(),
        }
    }

    pub fn with_timeout(&self, timeout: Duration) -> Self {
        let mut next = self.clone();
        next.timeout = timeout;
        next
    }

    pub fn with_flag(&self, flag: impl Into<String>) -> Self {
        let mut next = self.clone();
        let flag = flag.into();
        if !next.feature_flags.contains(&flag) {
            next.feature_flags.push(flag);
        }
        next
    }

    pub fn has_flag(&self, flag: &str) -> bool {
        self.feature_flags.iter().any(|f| f == flag)
    }
}
3 files · rust Explain with highlit

This snippet shows a common pattern for serving hot-reloadable configuration to many reader threads without any lock contention on the read path. The core idea is copy-on-write: readers hold an immutable Arc<Config> snapshot, and updates never mutate a shared struct in place. Instead a writer builds a brand new Config, wraps it in a fresh Arc, and atomically swaps the pointer. Readers that grabbed the old Arc keep observing a consistent snapshot until they drop it, so there is no torn read and no reader-side blocking.

In config.rs, Config is a plain immutable struct plus a with_timeout helper that returns a modified clone rather than mutating self. This is the copy-on-write step: cloning a config is cheap relative to how often it is read, and it guarantees each published snapshot is frozen forever. Because Config is never mutated after publication, it needs no interior locking and is trivially Send + Sync.

In store.rs, ConfigStore wraps an arc_swap::ArcSwap<Config>. The read path current() calls load_full(), which is a lock-free atomic load returning a cheaply-cloned Arc; hot code can call this on every request. The write path reload() and update() use rcu, a read-copy-update loop that reads the current value, derives a new one via a closure, and compare-and-swaps it in, retrying if another writer won the race. This makes concurrent updates safe without a mutex, and store() handles the simpler unconditional replace.

The trade-off is memory and clone cost: each update allocates a new Config, and readers holding old snapshots delay reclamation. That is acceptable when reads vastly outnumber writes, which is exactly the config-reload case. A subtle pitfall is expecting a long-running request to see a mid-flight update — it will not, and that consistency is usually a feature. main.rs demonstrates spawning reader threads that each pin a snapshot while a writer thread swaps in new values, showing readers never block and always see a coherent Config.


Related snips

Share this code

Here's the card — post it anywhere.

Lock-Free Config Reads With Arc-Swap and Copy-on-Write Snapshots in Rust — share card
Link copied