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 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
//! Epoch-based memory management //! //! This module provides fast, easy to use memory management for lock free data //! structures. It's inspired by [Keir Fraser's *epoch-based //! reclamation*](https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf). //! //! The basic problem this is solving is the fact that when one thread has //! removed a node from a data structure, other threads may still have pointers //! to that node (in the form of snapshots that will be validated through things //! like compare-and-swap), so the memory cannot be immediately freed. Put differently: //! //! 1. There are two sources of reachability at play -- the data structure, and //! the snapshots in threads accessing it. Before we delete a node, we need to know //! that it cannot be reached in either of these ways. //! //! 2. Once a node has been unliked from the data structure, no *new* snapshots //! reaching it will be created. //! //! Using the epoch scheme is fairly straightforward, and does not require //! understanding any of the implementation details: //! //! - When operating on a shared data structure, a thread must "pin the current //! epoch", which is done by calling `pin()`. This function returns a `Guard` //! which unpins the epoch when destroyed. //! //! - When the thread subsequently reads from a lock-free data structure, the //! pointers it extracts act like references with lifetime tied to the //! `Guard`. This allows threads to safely read from snapshotted data, being //! guaranteed that the data will remain allocated until they exit the epoch. //! //! To put the `Guard` to use, Crossbeam provides a set of three pointer types meant to work together: //! //! - `Owned<T>`, akin to `Box<T>`, which points to uniquely-owned data that has //! not yet been published in a concurrent data structure. //! //! - `Shared<'a, T>`, akin to `&'a T`, which points to shared data that may or may //! not be reachable from a data structure, but it guaranteed not to be freed //! during lifetime `'a`. //! //! - `Atomic<T>`, akin to `std::sync::atomic::AtomicPtr`, which provides atomic //! updates to a pointer using the `Owned` and `Shared` types, and connects them //! to a `Guard`. //! //! Each of these types provides further documentation on usage. //! //! # Example //! //! ``` //! use std::sync::atomic::Ordering::{Acquire, Release, Relaxed}; //! use std::ptr; //! //! use crossbeam::mem::epoch::{self, Atomic, Owned}; //! //! struct TreiberStack<T> { //! head: Atomic<Node<T>>, //! } //! //! struct Node<T> { //! data: T, //! next: Atomic<Node<T>>, //! } //! //! impl<T> TreiberStack<T> { //! fn new() -> TreiberStack<T> { //! TreiberStack { //! head: Atomic::null() //! } //! } //! //! fn push(&self, t: T) { //! // allocate the node via Owned //! let mut n = Owned::new(Node { //! data: t, //! next: Atomic::null(), //! }); //! //! // become active //! let guard = epoch::pin(); //! //! loop { //! // snapshot current head //! let head = self.head.load(Relaxed, &guard); //! //! // update `next` pointer with snapshot //! n.next.store_shared(head, Relaxed); //! //! // if snapshot is still good, link in the new node //! match self.head.cas_and_ref(head, n, Release, &guard) { //! Ok(_) => return, //! Err(owned) => n = owned, //! } //! } //! } //! //! fn pop(&self) -> Option<T> { //! // become active //! let guard = epoch::pin(); //! //! loop { //! // take a snapshot //! match self.head.load(Acquire, &guard) { //! // the stack is non-empty //! Some(head) => { //! // read through the snapshot, *safely*! //! let next = head.next.load(Relaxed, &guard); //! //! // if snapshot is still good, update from `head` to `next` //! if self.head.cas_shared(Some(head), next, Release) { //! unsafe { //! // mark the node as unlinked //! guard.unlinked(head); //! //! // extract out the data from the now-unlinked node //! return Some(ptr::read(&(*head).data)) //! } //! } //! } //! //! // we observed the stack empty //! None => return None //! } //! } //! } //! } //! ``` // FIXME: document implementation details mod atomic; mod garbage; mod global; mod guard; mod local; mod participant; mod participants; pub use self::atomic::Atomic; pub use self::guard::{pin, Guard}; use std::ops::{Deref, DerefMut}; use std::ptr; use std::mem; /// Like `Box<T>`: an owned, heap-allocated data value of type `T`. #[derive(Debug)] pub struct Owned<T> { data: Box<T>, } impl<T> Owned<T> { /// Move `t` to a new heap allocation. pub fn new(t: T) -> Owned<T> { Owned { data: Box::new(t) } } fn as_raw(&self) -> *mut T { self.deref() as *const _ as *mut _ } /// Move data out of the owned box, deallocating the box. pub fn into_inner(self) -> T { *self.data } } impl<T> Deref for Owned<T> { type Target = T; fn deref(&self) -> &T { &self.data } } impl<T> DerefMut for Owned<T> { fn deref_mut(&mut self) -> &mut T { &mut self.data } } #[derive(PartialEq, Eq)] /// Like `&'a T`: a shared reference valid for lifetime `'a`. #[derive(Debug)] pub struct Shared<'a, T: 'a> { data: &'a T, } impl<'a, T> Copy for Shared<'a, T> {} impl<'a, T> Clone for Shared<'a, T> { fn clone(&self) -> Shared<'a, T> { Shared { data: self.data } } } impl<'a, T> Deref for Shared<'a, T> { type Target = &'a T; fn deref(&self) -> &&'a T { &self.data } } impl<'a, T> Shared<'a, T> { unsafe fn from_raw(raw: *mut T) -> Option<Shared<'a, T>> { if raw == ptr::null_mut() { None } else { Some(Shared { data: mem::transmute::<*mut T, &T>(raw) }) } } unsafe fn from_ref(r: &T) -> Shared<'a, T> { Shared { data: mem::transmute(r) } } unsafe fn from_owned(owned: Owned<T>) -> Shared<'a, T> { let ret = Shared::from_ref(owned.deref()); mem::forget(owned); ret } pub fn as_raw(&self) -> *mut T { self.data as *const _ as *mut _ } } #[cfg(test)] mod test { use std::sync::atomic::Ordering; use super::*; use mem::epoch; #[test] fn test_no_drop() { static mut DROPS: i32 = 0; struct Test; impl Drop for Test { fn drop(&mut self) { unsafe { DROPS += 1; } } } let g = pin(); let x = Atomic::null(); x.store(Some(Owned::new(Test)), Ordering::Relaxed); x.store_and_ref(Owned::new(Test), Ordering::Relaxed, &g); let y = x.load(Ordering::Relaxed, &g); let z = x.cas_and_ref(y, Owned::new(Test), Ordering::Relaxed, &g).ok(); let _ = x.cas(z, Some(Owned::new(Test)), Ordering::Relaxed); x.swap(Some(Owned::new(Test)), Ordering::Relaxed, &g); unsafe { assert_eq!(DROPS, 0); } } #[test] fn test_new() { let guard = epoch::pin(); let my_atomic = Atomic::new(42); assert_eq!(**my_atomic.load(Ordering::Relaxed, &guard).unwrap(), 42); } }