1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
//! A library for high concurrency reads.
//!
//! This library is named after the 2 (identical) tables that are held internally:
//! - Active - this is the table that all Readers view. This table will never be
//! write locked, so readers never face contention.
//! - Standby - this is the table that writers mutate. A writer should face
//! minimal contention retrieving this table since Readers move to the Active
//! table whenever calling `.read()`.
//!
//! There are 2 ways to use this crate:
//! 1. Direct interaction with `AsLock`/`AsLockHandle`. This is more flexible
//! since users can pass in any struct they want and mutate it however they
//! choose. All updates though, will need to be done by passing a function
//! instead of via mutable methods (`UpdateTables` trait).
//! 2. Using collections which are built out of the primitives but which provide an
//! API similar to `RwLock<T>`; writers can directly call to methods without
//! having to provide a mutator function.
//!
//! There are 2 flavors/modules:
//! 1. Lockless - this variant trades off increased performance against changing the
//! API to be less like a `RwLock`. This centers around the `AsLockHandle`, which
//! is conceptually similar to `Arc<RwLock>` (requires a separate `AsLockHandle`
//! per thread/task).
//! 2. Sync - this centers around using an `AsLock`, which is meant to feel like a
//! `RwLock`. The main difference is that you still cannot gain direct write
//! access to the underlying table due to the need to keep them identical.
//!
//! The cost of minimizing contention is:
//! 1. Memory - Internally there are 2 copies of the underlying type the user
//! created. This is needed to allow there to always be a table that Readers can
//! access out without contention.
//! 2. CPU - The writer must apply all updates twice, once to each table. Lock
//! contention for the writer should be less than with a plain RwLock due to
//! Readers using the active_table, so it's possible that write times themselves
//! will drop.
//!
//! ### Example
//! Example of the 3 usage patterns: build your own wrapper, use prebuilt
//! collections, and use the primitives. Each of these can be done with both
//! sync and lockless.
//! ```rust
//! use std::thread::sleep;
//! use std::time::Duration;
//! use std::sync::Arc;
//!
//! // Create wrapper class so that users can interact with the active_standby
//! // struct via a RwLock-like interface. See the implementation of the
//! // collections for more examples.
//! mod wrapper {
//! use active_standby::UpdateTables;
//!
//! active_standby::generate_lockless_aslockhandle!(i32);
//!
//! struct AddOne {}
//!
//! impl<'a> UpdateTables<'a, i32, ()> for AddOne {
//! fn apply_first(&mut self, table: &'a mut i32) {
//! *table = *table + 1;
//! }
//! fn apply_second(mut self, table: &mut i32) {
//! self.apply_first(table);
//! }
//! }
//!
//! // Client's must implement the mutable interface that they want to
//! // offer users. Non mutable functions are automatic via Deref.
//! impl<'w> AsLockWriteGuard<'w> {
//! pub fn add_one(&mut self) {
//! self.guard.update_tables(AddOne {})
//! }
//! }
//! }
//!
//! pub fn run_wrapper() {
//! let table = wrapper::AsLockHandle::new(0);
//! let table2 = table.clone();
//!
//! let handle = std::thread::spawn(move || {
//! while *table2.read() != 1 {
//! sleep(Duration::from_micros(100));
//! }
//! });
//!
//! table.write().add_one();
//! handle.join();
//! }
//!
//! // Use a premade collection which wraps `AsLock<Vec<T>>`, to provide an
//! // interface akin to `RwLock<Vec<T>>`.
//! pub fn run_collection() {
//! use active_standby::sync::collections::AsVec;
//!
//! let table = Arc::new(AsVec::default());
//! let table2 = Arc::clone(&table);
//!
//! let handle = std::thread::spawn(move || {
//! while *table2.read() != vec![1] {
//! sleep(Duration::from_micros(100));
//! }
//! });
//!
//! table.write().push(1);
//! handle.join();
//! }
//!
//! // Use the raw AsLock interface to update the underlying data.
//! pub fn run_primitive() {
//! use active_standby::sync::AsLock;
//!
//! // If the entries in your table are large, you may want to hold only
//! // 1 copy shared by both tables. This is safe so long as you never
//! // mutate the shared data; only remove and replace it in the table.
//! let table = Arc::new(AsLock::new(vec![Arc::new(1)]));
//! let table2 = Arc::clone(&table);
//!
//! let handle = std::thread::spawn(move || {
//! while *table2.read() != vec![Arc::new(2)] {
//! sleep(Duration::from_micros(100));
//! }
//! });
//!
//! table.write().update_tables_closure(|table| {
//! // Update the entry in the table, not the shared value behind the
//! // Arc.
//! table[0] = Arc::new(2);
//! });
//! handle.join();
//! }
//!
//! fn main() {
//! run_wrapper();
//! run_collection();
//! run_primitive();
//! }
//! ```
//!
//! ## Testing
//! There are a number of tests that come with active_standby (see
//! tests/tests_script.sh for examples):
//!
//! [unittests](https://doc.rust-lang.org/book/ch11-01-writing-tests.html)
//!
//! [benchmarks](https://doc.rust-lang.org/unstable-book/library-features/test.html)
//!
//! [loom](https://crates.io/crates/loom)
//!
//! [LLVM Sanitizers](https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html)
//!
//! [Miri](https://github.com/rust-lang/miri)
//!
//! [Rudra](https://github.com/sslab-gatech/Rudra)
mod macros;
pub(crate) mod types;
mod collections;
mod primitives;
pub use crate::types::UpdateTables;
pub mod lockless {
/// Premade structs which wrap standard collection in the active standby
/// model. This allows for the API to match RwLock<T> while under the hood
/// the active standby model works its magic. AsLockReadGuard is generic
/// since only writes require a special API for active_standby.
pub mod collections {
// Inline the re-export to make rustdocs more readable.
#[doc(inline)]
pub use crate::collections::btreemap::lockless::{
AsLockHandle as AsBTreeMapHandle, AsLockWriteGuard as AsBTreeMapWriteGuard,
};
#[doc(inline)]
pub use crate::collections::btreeset::lockless::{
AsLockHandle as AsBTreeSetHandle, AsLockWriteGuard as AsBTreeSetWriteGuard,
};
#[doc(inline)]
pub use crate::collections::hashmap::lockless::{
AsLockHandle as AsHashMapHandle, AsLockWriteGuard as AsHashMapWriteGuard,
};
#[doc(inline)]
pub use crate::collections::hashset::lockless::{
AsLockHandle as AsHashSetHandle, AsLockWriteGuard as AsHashSetWriteGuard,
};
#[doc(inline)]
pub use crate::collections::vec::lockless::{
AsLockHandle as AsVecHandle, AsLockWriteGuard as AsVecWriteGuard,
};
}
pub use crate::primitives::lockless::{AsLockHandle, AsLockReadGuard, AsLockWriteGuard};
}
pub mod sync {
/// Premade structs which wrap standard collection in the active standby
/// model. This allows for the API to match RwLock<T> while under the hood
/// the active standby model works its magic. AsLockReadGuard is generic
/// since only writes require a special API for active_standby.
pub mod collections {
// Inline the re-export to make rustdocs more readable.
#[doc(inline)]
pub use crate::collections::btreemap::sync::{
AsLock as AsBTreeMap, AsLockWriteGuard as AsBTreeMapWriteGuard,
};
#[doc(inline)]
pub use crate::collections::btreeset::sync::{
AsLock as AsBTreeSet, AsLockWriteGuard as AsBTreeSetWriteGuard,
};
#[doc(inline)]
pub use crate::collections::hashmap::sync::{
AsLock as AsHashMap, AsLockWriteGuard as AsHashMapWriteGuard,
};
#[doc(inline)]
pub use crate::collections::hashset::sync::{
AsLock as AsHashSet, AsLockWriteGuard as AsHashSetWriteGuard,
};
#[doc(inline)]
pub use crate::collections::vec::sync::{
AsLock as AsVec, AsLockWriteGuard as AsVecWriteGuard,
};
}
pub use crate::primitives::sync::{AsLock, AsLockReadGuard, AsLockWriteGuard};
}