1 //! Strategies for protecting the reference counts. 2 //! 3 //! There are multiple algorithms how to protect the reference counts while they're being updated 4 //! by multiple threads, each with its own set of pros and cons. The [`DefaultStrategy`] is used by 5 //! default and should generally be the least surprising option. It is possible to pick a different 6 //! strategy. 7 //! 8 //! For now, the traits in here are sealed and don't expose any methods to the users of the crate. 9 //! This is because we are not confident about the details just yet. In the future it may be 10 //! possible for downstream users to implement their own, but for now it is only so users can 11 //! choose one of the provided. 12 //! 13 //! It is expected that future strategies would come with different capabilities and limitations. 14 //! In particular, some that are not "tight" in the cleanup (delay the cleanup) or not support the 15 //! compare and swap operations. 16 //! 17 //! Currently, we have these strategies: 18 //! 19 //! * [`DefaultStrategy`] (this one is used implicitly) 20 //! * [`RwLock<()>`][std::sync::RwLock] 21 //! 22 //! # Testing 23 //! 24 //! Formally, the [`RwLock<()>`][std::sync::RwLock] may be used as a strategy too. It doesn't have 25 //! the performance characteristics or lock-free guarantees of the others, but it is much simpler 26 //! and contains less `unsafe` code (actually, less code altogether). Therefore, it can be used for 27 //! testing purposes and cross-checking. 28 //! 29 //! Note that generally, using [`RwLock<Arc<T>>`][std::sync::RwLock] is likely to be better 30 //! performance wise. So if the goal is to not use third-party unsafe code, only the one in 31 //! [`std`], that is the better option. This is provided mostly for investigation and testing of 32 //! [`ArcSwap`] itself or algorithms written to use [`ArcSwap`]. 33 //! 34 //! *This is not meant to be used in production code*. 35 //! 36 //! [`ArcSwap`]: crate::ArcSwap 37 //! [`load`]: crate::ArcSwapAny::load 38 39 use std::borrow::Borrow; 40 use std::sync::atomic::AtomicPtr; 41 42 use crate::ref_cnt::RefCnt; 43 44 pub(crate) mod hybrid; 45 mod rw_lock; 46 // Do not use from outside of the crate. 47 #[cfg(feature = "internal-test-strategies")] 48 #[doc(hidden)] 49 pub mod test_strategies; 50 51 use self::hybrid::{DefaultConfig, HybridStrategy}; 52 53 /// The default strategy. 54 /// 55 /// It is used by the type aliases [`ArcSwap`][crate::ArcSwap] and 56 /// [`ArcSwapOption`][crate::ArcSwapOption]. Only the other strategies need to be used explicitly. 57 /// 58 /// # Performance characteristics 59 /// 60 /// * It is optimized for read-heavy situations, with possibly many concurrent read accesses from 61 /// multiple threads. Readers don't contend each other at all. 62 /// * Readers are wait-free (with the exception of at most once in `usize::MAX / 4` accesses, which 63 /// is only lock-free). 64 /// * Writers are lock-free. 65 /// * Reclamation is exact ‒ the resource is released as soon as possible (works like RAII, not 66 /// like a traditional garbage collector; can contain non-`'static` data). 67 /// 68 /// Each thread has a limited number of fast slots (currently 8, but the exact number is not 69 /// guaranteed). If it holds at most that many [`Guard`]s at once, acquiring them is fast. Once 70 /// these slots are used up (by holding to these many [`Guard`]s), acquiring more of them will be 71 /// slightly slower, but still wait-free. 72 /// 73 /// If you expect to hold a lot of "handles" to the data around, or hold onto it for a long time, 74 /// you may want to prefer the [`load_full`][crate::ArcSwapAny::load_full] method. 75 /// 76 /// The speed of the fast slots is in the ballpark of locking an *uncontented* mutex. The advantage 77 /// over the mutex is the stability of speed in the face of contention from other threads ‒ while 78 /// the performance of mutex goes rapidly down, the slowdown of running out of held slots or heavy 79 /// concurrent writer thread in the area of single-digit multiples. 80 /// 81 /// The ballpark benchmark figures (my older computer) are around these, but you're welcome to run 82 /// the benchmarks in the git repository or write your own. 83 /// 84 /// * Load (both uncontented and contented by other loads): ~30ns 85 /// * `load_full`: ~50ns uncontented, goes up a bit with other `load_full` in other threads on the 86 /// same `Arc` value (~80-100ns). 87 /// * Loads after running out of the slots ‒ about 10-20ns slower than `load_full`. 88 /// * Stores: Dependent on number of threads, but generally low microseconds. 89 /// * Loads with heavy concurrent writer (to the same `ArcSwap`): ~250ns. 90 /// 91 /// [`load`]: crate::ArcSwapAny::load 92 /// [`Guard`]: crate::Guard 93 pub type DefaultStrategy = HybridStrategy<DefaultConfig>; 94 95 /// Strategy for isolating instances. 96 /// 97 /// It is similar to [`DefaultStrategy`], however the spin lock is not sharded (therefore multiple 98 /// concurrent threads might get bigger hit when multiple threads have to fall back). Nevertheless, 99 /// each instance has a private spin lock, not influencing the other instances. That also makes 100 /// them bigger in memory. 101 /// 102 /// The hazard pointers are still shared between all instances. 103 /// 104 /// The purpose of this strategy is meant for cases where a single instance is going to be 105 /// "tortured" a lot, so it should not overflow to other instances. 106 /// 107 /// This too may be changed for something else (but with at least as good guarantees, primarily 108 /// that other instances won't get influenced by the "torture"). 109 // Testing if the DefaultStrategy is good enough to replace it fully and then deprecate. 110 #[doc(hidden)] 111 pub type IndependentStrategy = DefaultStrategy; 112 113 // TODO: When we are ready to un-seal, should these traits become unsafe? 114 115 pub(crate) mod sealed { 116 use super::*; 117 use crate::as_raw::AsRaw; 118 119 pub trait Protected<T>: Borrow<T> { into_inner(self) -> T120 fn into_inner(self) -> T; from_inner(ptr: T) -> Self121 fn from_inner(ptr: T) -> Self; 122 } 123 124 pub trait InnerStrategy<T: RefCnt> { 125 // Drop „unlocks“ 126 type Protected: Protected<T>; load(&self, storage: &AtomicPtr<T::Base>) -> Self::Protected127 unsafe fn load(&self, storage: &AtomicPtr<T::Base>) -> Self::Protected; wait_for_readers(&self, old: *const T::Base, storage: &AtomicPtr<T::Base>)128 unsafe fn wait_for_readers(&self, old: *const T::Base, storage: &AtomicPtr<T::Base>); 129 } 130 131 pub trait CaS<T: RefCnt>: InnerStrategy<T> { compare_and_swap<C: AsRaw<T::Base>>( &self, storage: &AtomicPtr<T::Base>, current: C, new: T, ) -> Self::Protected132 unsafe fn compare_and_swap<C: AsRaw<T::Base>>( 133 &self, 134 storage: &AtomicPtr<T::Base>, 135 current: C, 136 new: T, 137 ) -> Self::Protected; 138 } 139 } 140 141 /// A strategy for protecting the reference counted pointer `T`. 142 /// 143 /// This chooses the algorithm for how the reference counts are protected. Note that the user of 144 /// the crate can't implement the trait and can't access any method; this is hopefully temporary 145 /// measure to make sure the interface is not part of the stability guarantees of the crate. Once 146 /// enough experience is gained with implementing various strategies, it will be un-sealed and 147 /// users will be able to provide their own implementation. 148 /// 149 /// For now, the trait works only as a bound to talk about the types that represent strategies. 150 pub trait Strategy<T: RefCnt>: sealed::InnerStrategy<T> {} 151 impl<T: RefCnt, S: sealed::InnerStrategy<T>> Strategy<T> for S {} 152 153 /// An extension of the [`Strategy`], allowing for compare and swap operation. 154 /// 155 /// The compare and swap operation is "advanced" and not all strategies need to support them. 156 /// Therefore, it is a separate trait. 157 /// 158 /// Similarly, it is not yet made publicly usable or implementable and works only as a bound. 159 pub trait CaS<T: RefCnt>: sealed::CaS<T> {} 160 impl<T: RefCnt, S: sealed::CaS<T>> CaS<T> for S {} 161