//! Common use patterns //! //! Here are some common patterns one can use for inspiration. These are mostly covered by examples //! at the right type in the crate, but this lists them at a single place. //! //! # Sharing of configuration data //! //! We want to share configuration from some source with rare updates to some high performance //! worker threads. It can be configuration in its true sense, or a routing table. //! //! The idea here is, each new version is a newly allocated in its own [`Arc`]. It is then stored //! into a *shared* `ArcSwap` instance. //! //! Each worker then loads the current version before each work chunk. In case a new version is //! stored, the worker keeps using the loaded one until it ends the work chunk and, if it's the //! last one to have the version, deallocates it automatically by dropping the [`Guard`] //! //! Note that the configuration needs to be passed through a *single shared* [`ArcSwap`]. That //! means we need to share that instance and we do so through an [`Arc`] (one could use a global //! variable instead). //! //! Therefore, what we have is `Arc>`. //! //! ```rust //! # use std::sync::Arc; //! # use std::sync::atomic::{AtomicBool, Ordering}; //! # use std::thread; //! # use std::time::Duration; //! # //! # use arc_swap::ArcSwap; //! # struct Work; //! # impl Work { fn fetch() -> Self { Work } fn perform(&self, _: &Config) {} } //! # //! #[derive(Debug, Default)] //! struct Config { //! // ... Stuff in here ... //! } //! //! // We wrap the ArcSwap into an Arc, so we can share it between threads. //! let config = Arc::new(ArcSwap::from_pointee(Config::default())); //! //! let terminate = Arc::new(AtomicBool::new(false)); //! let mut threads = Vec::new(); //! //! // The configuration thread //! threads.push(thread::spawn({ //! let config = Arc::clone(&config); //! let terminate = Arc::clone(&terminate); //! move || { //! while !terminate.load(Ordering::Relaxed) { //! thread::sleep(Duration::from_secs(6)); //! // Actually, load it from somewhere //! let new_config = Arc::new(Config::default()); //! config.store(new_config); //! } //! } //! })); //! //! // The worker thread //! for _ in 0..10 { //! threads.push(thread::spawn({ //! let config = Arc::clone(&config); //! let terminate = Arc::clone(&terminate); //! move || { //! while !terminate.load(Ordering::Relaxed) { //! let work = Work::fetch(); //! let config = config.load(); //! work.perform(&config); //! } //! } //! })); //! } //! //! // Terminate gracefully //! terminate.store(true, Ordering::Relaxed); //! for thread in threads { //! thread.join().unwrap(); //! } //! ``` //! //! # Consistent snapshots //! //! While one probably wants to get a fresh instance every time a work chunk is available, //! therefore there would be one [`load`] for each work chunk, it is often also important that the //! configuration doesn't change in the *middle* of processing of one chunk. Therefore, one //! commonly wants *exactly* one [`load`] for the work chunk, not *at least* one. If the processing //! had multiple phases, one would use something like this: //! //! ```rust //! # use std::sync::Arc; //! # //! # use arc_swap::ArcSwap; //! # struct Config; //! # struct Work; //! # impl Work { //! # fn fetch() -> Self { Work } //! # fn phase_1(&self, _: &Config) {} //! # fn phase_2(&self, _: &Config) {} //! # } //! # let config = Arc::new(ArcSwap::from_pointee(Config)); //! let work = Work::fetch(); //! let config = config.load(); //! work.phase_1(&config); //! // We keep the same config value here //! work.phase_2(&config); //! ``` //! //! Over this: //! //! ```rust //! # use std::sync::Arc; //! # //! # use arc_swap::ArcSwap; //! # struct Config; //! # struct Work; //! # impl Work { //! # fn fetch() -> Self { Work } //! # fn phase_1(&self, _: &Config) {} //! # fn phase_2(&self, _: &Config) {} //! # } //! # let config = Arc::new(ArcSwap::from_pointee(Config)); //! let work = Work::fetch(); //! work.phase_1(&config.load()); //! // WARNING!! This is broken, because in between phase_1 and phase_2, the other thread could //! // have replaced the config. Then each phase would be performed with a different one and that //! // could lead to surprises. //! work.phase_2(&config.load()); //! ``` //! //! # Caching of the configuration //! //! Let's say that the work chunks are really small, but there's *a lot* of them to work on. Maybe //! we are routing packets and the configuration is the routing table that can sometimes change, //! but mostly doesn't. //! //! There's an overhead to [`load`]. If the work chunks are small enough, that could be measurable. //! We can reach for [`Cache`]. It makes loads much faster (in the order of accessing local //! variables) in case nothing has changed. It has two costs, it makes the load slightly slower in //! case the thing *did* change (which is rare) and if the worker is inactive, it holds the old //! cached value alive. //! //! This is OK for our use case, because the routing table is usually small enough so some stale //! instances taking a bit of memory isn't an issue. //! //! The part that takes care of updates stays the same as above. //! //! ```rust //! # use std::sync::Arc; //! # use std::thread; //! # use std::sync::atomic::{AtomicBool, Ordering}; //! # use arc_swap::{ArcSwap, Cache}; //! # struct Packet; impl Packet { fn receive() -> Self { Packet } } //! //! #[derive(Debug, Default)] //! struct RoutingTable { //! // ... Stuff in here ... //! } //! //! impl RoutingTable { //! fn route(&self, _: Packet) { //! // ... Interesting things are done here ... //! } //! } //! //! let routing_table = Arc::new(ArcSwap::from_pointee(RoutingTable::default())); //! //! let terminate = Arc::new(AtomicBool::new(false)); //! let mut threads = Vec::new(); //! //! for _ in 0..10 { //! let t = thread::spawn({ //! let routing_table = Arc::clone(&routing_table); //! let terminate = Arc::clone(&terminate); //! move || { //! let mut routing_table = Cache::new(routing_table); //! while !terminate.load(Ordering::Relaxed) { //! let packet = Packet::receive(); //! // This load is cheaper, because we cache in the private Cache thing. //! // But if the above receive takes a long time, the Cache will keep the stale //! // value alive until this time (when it will get replaced by up to date value). //! let current = routing_table.load(); //! current.route(packet); //! } //! } //! }); //! threads.push(t); //! } //! //! // Shut down properly //! terminate.store(true, Ordering::Relaxed); //! for thread in threads { //! thread.join().unwrap(); //! } //! ``` //! //! # Projecting into configuration field //! //! We have a larger application, composed of multiple components. Each component has its own //! `ComponentConfig` structure. Then, the whole application has a `Config` structure that contains //! a component config for each component: //! //! ```rust //! # struct ComponentConfig; //! //! struct Config { //! component: ComponentConfig, //! // ... Some other components and things ... //! } //! # let c = Config { component: ComponentConfig }; //! # let _ = c.component; //! ``` //! //! We would like to use [`ArcSwap`] to push updates to the components. But for various reasons, //! it's not a good idea to put the whole `ArcSwap` to each component, eg: //! //! * That would make each component depend on the top level config, which feels reversed. //! * It doesn't allow reusing the same component in multiple applications, as these would have //! different `Config` structures. //! * One needs to build the whole `Config` for tests. //! * There's a risk of entanglement, that the component would start looking at configuration of //! different parts of code, which would be hard to debug. //! //! We also could have a separate `ArcSwap` for each component, but that also //! doesn't feel right, as we would have to push updates to multiple places and they could be //! inconsistent for a while and we would have to decompose the `Config` structure into the parts, //! because we need our things in [`Arc`]s to be put into [`ArcSwap`]. //! //! This is where the [`Access`] trait comes into play. The trait abstracts over things that can //! give access to up to date version of specific T. That can be a [`Constant`] (which is useful //! mostly for the tests, where one doesn't care about the updating), it can be an //! [`ArcSwap`][`ArcSwap`] itself, but it also can be an [`ArcSwap`] paired with a closure to //! project into the specific field. The [`DynAccess`] is similar, but allows type erasure. That's //! more convenient, but a little bit slower. //! //! ```rust //! # use std::sync::Arc; //! # use arc_swap::ArcSwap; //! # use arc_swap::access::{DynAccess, Map}; //! //! #[derive(Debug, Default)] //! struct ComponentConfig; //! //! struct Component { //! config: Box>, //! } //! //! #[derive(Debug, Default)] //! struct Config { //! component: ComponentConfig, //! } //! //! let config = Arc::new(ArcSwap::from_pointee(Config::default())); //! //! let component = Component { //! config: Box::new(Map::new(Arc::clone(&config), |config: &Config| &config.component)), //! }; //! # let _ = component.config; //! ``` //! //! One would use `Box::new(Constant(ComponentConfig))` in unittests instead as the `config` field. //! //! The [`Cache`] has its own [`Access`][crate::cache::Access] trait for similar purposes. //! //! [`Arc`]: std::sync::Arc //! [`Guard`]: crate::Guard //! [`load`]: crate::ArcSwapAny::load //! [`ArcSwap`]: crate::ArcSwap //! [`Cache`]: crate::cache::Cache //! [`Access`]: crate::access::Access //! [`DynAccess`]: crate::access::DynAccess //! [`Constant`]: crate::access::Constant