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
// Copyright 2015-2016 rust-stm Developers // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! This library implements [software transactional memory] //! (https://en.wikipedia.org/wiki/Software_transactional_memory), //! often abbreviated with STM. //! //! It is designed closely to haskells STM library. Read Simon Marlow's //! [Parallel and Concurrent Programming in Haskell] //! (http://chimera.labs.oreilly.com/books/1230000000929/ch10.html) //! for more info. Especially the chapter about [Performance] //! (http://chimera.labs.oreilly.com/books/1230000000929/ch10.html#sec_stm-cost) //! is also important for using STM in rust. //! //! With locks the sequence //! of two threadsafe actions is no longer threadsafe because //! other threads may interfer in between of these actions. //! Applying another lock may lead to common sources of errors //! like deadlocks and forgotten locks. //! //! Unlike locks Software transactional memory is composable. //! It is typically implemented by writing all read and write //! operations in a log. When the action has finished, the reads //! are checked for consistency and depending on the result //! either the writes are committed in a single atomic operation //! or the result is discarded and the computation run again. //! //! # Usage //! //! STM operations are safed inside the type `STM<T>` where T //! is the return type of the inner operation. They are created with `stm!`: //! //! ``` //! # #[macro_use] extern crate stm; //! # fn main() { //! let transaction = stm!({ //! // some action //! // return value (or empty for unit) //! }); //! # } //! ``` //! and can then be run by calling. //! //! ``` //! # #[macro_use] extern crate stm; //! # fn main() { //! # let transaction = stm!({ //! # }); //! transaction.atomically(); //! # } //! //! ``` //! //! For running an STM-Block inside of another //! use the macro `stm_call!`: //! //! ``` //! # #[macro_use] extern crate stm; //! # fn main() { //! use stm::Var; //! let var = Var::new(0); //! let modify = stm!({ //! var.write(42); //! }); //! //! let x = stm!({ //! stm_call!(modify); //! var.read() // return the value saved in var //! }).atomically(); //! //! println!("var = {}", x); //! # } //! //! ``` //! //! # STM safety //! //! Software transactional memory is completely safe in the terms //! that rust considers safe. Still there are multiple rules that //! you should obey when dealing with software transactional memory: //! //! * Don't run code with side effects, especially no IO-code, //! because stm is designed to be run multiple times. Return a //! closure if you have to. //! * Don't run an STM-Block by calling `STM::atomically` inside of //! another because your thread will //! immediately panic. When you use STM in the inner of a function then //! return a STM-Object instead so that callers can safely compose it into //! larger blocks. //! * Don't mix locks and STM. Your code will easily deadlock and slow //! down on unpredictably. //! * When you put an `Arc` into a `Var` don't use inner mutability //! to modify it since the inner still points to the original value. //! * Don't call `Var::read` or `Var::write` from outside of a STM block. //! Instead start a STM or use `Var::read_atomic` for it. //! //! //! # Speed //! Generally keep your atomic blocks as small as possible bacause //! the more time you spend the more likely it is to collide with //! other threads. For STM reading vars is quite slow because it //! need to look them up in the log every time they are written to //! and every used var increases the chance of collisions. You should //! keep the amount of accessed variables as low as needed. #[macro_use] mod macros; mod stm; mod log; mod var; pub use stm::{STM, StmResult, retry}; pub use var::Var; #[test] fn test_stm_macro() { let var = Var::new(0); let stm = stm!({ var.write(42); 0 }); stm.atomically(); } #[test] fn test_stm_nested() { let var = Var::new(0); let inner_stm = stm!(var.write(42)); let stm = stm!({ stm_call!(inner_stm); var.read() }); assert_eq!(42, stm.atomically()); } #[test] fn test_threaded() { use std::time::Duration; use std::thread; use std::sync::mpsc::channel; let var = Var::new(0); let (tx, rx) = channel(); let var_ref = var.clone(); thread::spawn(move || { let stm = stm!({ let x = var_ref.read(); if x==0 { stm_call!(retry()); } x }); let _ = tx.send(stm.atomically()); }); thread::sleep(Duration::from_millis(100)); stm!({ var.write(42); }).atomically(); let x = rx.recv().unwrap(); assert_eq!(42, x); } /// test if a STM calculation is rerun when a Var changes while executing #[test] fn test_read_write_interfere() { use std::thread; use std::time::Duration; // create var let var = Var::new(0); let var_ref = var.clone(); // spawn a thread let t = thread::spawn(move || { stm!({ // read the var let x = var_ref.read(); // ensure that x var_ref changes in between thread::sleep(Duration::from_millis(200)); // write back modified data this should only // happen when the value has not changed var_ref.write(x+10); }).atomically(); }); // ensure that the thread has started and already read the var thread::sleep(Duration::from_millis(10)); // now change it stm!({ var.write(32); }).atomically(); // finish and compare let _ = t.join(); assert_eq!(42, var.read_atomic()); } /* #[bench] fn bench_counter_stm(bench: &mut Bencher) { let var = Var::new(0); let stm = stm!({ let x = var.read(); var.write(x+1); }); b.iter(|| stm.atomically()); } #[bench] fn bench_counter_lock(bench: &mut Bencher) { let var = Mutex::new(0); b.iter(|| { let guard = var.lock().unwrap(); *guard += 1; }); } */