fast_stm/
lib.rs

1//! This library implements
2//! [software transactional memory](https://en.wikipedia.org/wiki/Software_transactional_memory),
3//! often abbreviated with STM.
4//!
5//! It is designed closely to haskells STM library. Read Simon Marlow's
6//! *Parallel and Concurrent Programming in Haskell*
7//! for more info. Especially the chapter about
8//! Performance is also important for using STM in rust.
9//!
10//! With locks the sequential composition of two
11//! two threadsafe actions is no longer threadsafe because
12//! other threads may interfer in between of these actions.
13//! Applying a third lock to protect both may lead to common sources of errors
14//! like deadlocks or race conditions.
15//!
16//! Unlike locks Software transactional memory is composable.
17//! It is typically implemented by writing all read and write
18//! operations in a log. When the action has finished and
19//! all the used `TVar`s are consistent, the writes are commited as
20//! a single atomic operation.
21//! Otherwise the computation repeats. This may lead to starvation,
22//! but avoids common sources of bugs.
23//!
24//! Panicing within STM does not poison the `TVar`s. STM ensures consistency by
25//! never committing on panic.
26//!
27//! # Features
28//!
29//! This library has features that can be used to tweak the behavior of the implementation:
30//!
31//! - `early-conflict-detection` -- when reading a variable that was already read before in a
32//!   transaction, check if the value has changed to detect inconsistency before the commit routine
33//! - `hash-registers` -- implement internal read and write registers using a `HashMap` instead of
34//!   a `BTreeMap`
35//!   - this may lead to improved performance if your transactions are longer / read-heavy, due to
36//!     lookup computational complexity
37//!   - the hash algorithm is provided by the `rustc-hash` crate, not the `std`
38//! - `wait-on-retry` -- if `retry` is called explictly in a transaction, the thread will go to
39//!   sleep and wait for one of the variables read in the initial transaction to change before
40//!   re-attempting computation
41//!
42//! By default, only the `wait-on-retry` feature is enabled, to keep the behavior identical to the
43//! original library.
44//!
45//! # Usage
46//!
47//! You should only use the functions that are transaction-safe.
48//! Transaction-safe functions don't have side effects, except those provided by `TVar`.
49//! Mutexes and other blocking mechanisms are especially dangerous, because they can
50//! interfere with the internal locking scheme of the transaction and therefore
51//! cause deadlocks.
52//!
53//! Note, that Transaction-safety does *not* mean safety in the rust sense, but is a
54//! subset of allowed behavior. Even if code is not transaction-safe, no segmentation
55//! faults will happen.
56//!
57//! You can run the top-level atomic operation by calling `atomically`.
58//!
59//!
60//! ```
61//! # use fast_stm::atomically;
62//! atomically(|trans| {
63//!     // some action
64//!     // return value as `Result`, for example
65//!     Ok(42)
66//! });
67//! ```
68//!
69//! Nested calls to `atomically` are not allowed. A run-time check prevents this.
70//! Instead of using atomically internally, add a `&mut Transaction` parameter and
71//! return `StmResult`.
72//!
73//! Use ? on `StmResult`, to propagate a transaction error through the system.
74//! Do not handle the error yourself.
75//!
76//! ```
77//! # use fast_stm::{atomically, TVar};
78//! let var = TVar::new(0);
79//!
80//! let x = atomically(|trans| {
81//!     var.write(trans, 42)?; // Pass failure to parent.
82//!     var.read(trans) // Return the value saved in var.
83//! });
84//!
85//! println!("var = {}", x);
86//! // var = 42
87//!
88//! ```
89//!
90//! # Transaction safety
91//!
92//! Software transactional memory is completely safe in the rust sense, so
93//! undefined behavior will never occur.
94//! Still there are multiple rules that
95//! you should obey when dealing with software transactional memory.
96//!
97//! * Don't run code with side effects, especially no IO-code.
98//!   Transactions repeat in failure cases. Using IO would repeat this IO-code.
99//!   Return a closure if you have to.
100//! * Don't handle `StmResult` yourself.
101//!   Use `Transaction::or` to combine alternative paths and `optionally` to check if an inner
102//!   function has failed. Always use `?` and
103//!   never ignore a `StmResult`.
104//! * Don't run `atomically` inside of another. `atomically` is designed to have side effects
105//!   and will therefore break transaction safety.
106//!   Nested calls are detected at runtime and handled with panicking.
107//!   When you use STM in the inner of a function, then
108//!   express it in the public interface, by taking `&mut Transaction` as parameter and
109//!   returning `StmResult<T>`. Callers can safely compose it into
110//!   larger blocks.
111//! * Don't mix locks and transactions. Your code will easily deadlock or slow
112//!   down unpredictably.
113//! * Don't use inner mutability to change the content of a `TVar`.
114//!
115//! Panicking in a transaction is transaction-safe. The transaction aborts and
116//! all changes are discarded. No poisoning or half written transactions happen.
117//!
118//! # Speed
119//!
120//! Generally keep your atomic blocks as small as possible, because
121//! the more time you spend, the more likely it is, to collide with
122//! other threads. For STM, reading `TVar`s is quite slow, because it
123//! needs to look them up in the log every time.
124//! Every used `TVar` increases the chance of collisions. Therefore you should
125//! keep the amount of accessed variables as low as needed.
126//!
127
128// document features
129#![allow(unexpected_cfgs)]
130#![cfg_attr(nightly, feature(doc_cfg))]
131// Extra linting with exceptions
132#![warn(clippy::pedantic)]
133#![allow(clippy::missing_errors_doc)]
134#![allow(clippy::module_name_repetitions)]
135#![allow(clippy::must_use_candidate)]
136#![allow(clippy::should_panic_without_expect)]
137
138extern crate parking_lot;
139
140mod result;
141mod transaction;
142mod tvar;
143
144#[cfg(test)]
145mod test;
146
147pub use result::*;
148pub use transaction::Transaction;
149pub use transaction::TransactionControl;
150pub use tvar::TVar;
151
152/// Convert a `TransactionClosureResult<T, E_A>` to `TransactionClosureResult<T, E_B>`.
153///
154/// This macro is used to cleanly write transactions where multiple kind of errors are
155/// possible during execution. The macro will not fail as long as the specified target
156/// error `$to` implements `From<E>`, `E` being the error possibly returned by `$op`.
157/// It expands to:
158///
159/// ```ignore
160/// $op.map_err(|e| match e {
161///         fast_stm::TransactionError::Abort(e) => fast_stm::TransactionError::Abort($to::from(e)),
162///         fast_stm::TransactionError::Stm(e) => fast_stm::TransactionError::Stm(e),
163///     })?
164/// ```
165///
166/// # Example
167///
168/// ```rust
169/// # use fast_stm::{abort, atomically_with_err, try_or_coerce, Transaction, TransactionClosureResult};
170///
171/// struct Error1;
172/// struct Error2;
173///
174/// impl From<Error1> for Error2 {
175///     fn from(e: Error1) -> Self {
176///         Error2
177///     }
178/// }
179///
180/// fn op1(trans: &mut Transaction) -> TransactionClosureResult<(), Error1> {
181///     // ...
182///     Ok(())
183/// }
184///
185/// fn op2(trans: &mut Transaction) -> TransactionClosureResult<(), Error2> {
186///     // ...
187///     Ok(())
188/// }
189///
190/// let res: Result<(), Error2> = atomically_with_err(|trans| {
191///     try_or_coerce!(op1(trans), Error2);
192///     op2(trans)?;   
193///     Ok(())
194/// });
195/// ```
196#[macro_export]
197macro_rules! try_or_coerce {
198    ($op: expr, $to: ident) => {
199        $op.map_err(|e| match e {
200            $crate::TransactionError::Abort(e) => $crate::TransactionError::Abort($to::from(e)),
201            $crate::TransactionError::Stm(e) => $crate::TransactionError::Stm(e),
202        })?
203    };
204}
205
206#[inline]
207/// Call `abort` to abort a transaction and pass the error as the return value.
208///
209/// # Examples
210///
211/// ```
212/// # use fast_stm::*;
213/// struct MyError;
214///
215/// let execute_once: Result<u32, _> = atomically_with_err(|_| {
216///     abort(MyError)
217/// });
218///
219/// assert!(execute_once.is_err());
220/// ```
221pub fn abort<T, E>(e: E) -> TransactionClosureResult<T, E> {
222    Err(TransactionError::Abort(e))
223}
224
225#[inline]
226/// Call `retry` to abort an operation and run the whole transaction again.
227///
228/// Semantically `retry` allows spin-lock-like behavior, but the library
229/// blocks until one of the used `TVar`s has changed, to keep CPU-usage low.
230///
231/// `Transaction::or` allows to define alternatives. If the first function
232/// wants to retry, then the second one has a chance to run.
233///
234/// # Examples
235///
236/// ```no_run
237/// # use fast_stm::*;
238/// let infinite_retry: i32 = atomically(|_| retry());
239/// ```
240pub fn retry<T>() -> StmClosureResult<T> {
241    Err(StmError::Retry)
242}
243
244/// Run a function atomically by using Software Transactional Memory.
245/// It calls to `Transaction::with` internally, but is more explicit.
246pub fn atomically<T, F>(f: F) -> T
247where
248    F: Fn(&mut Transaction) -> StmClosureResult<T>,
249{
250    Transaction::with(f)
251}
252
253/// Run a function atomically by using Software Transactional Memory.
254/// It calls to `Transaction::with_err` internally, but is more explicit.
255pub fn atomically_with_err<T, E, F>(f: F) -> Result<T, E>
256where
257    F: Fn(&mut Transaction) -> TransactionClosureResult<T, E>,
258{
259    Transaction::with_err(f)
260}
261
262#[inline]
263/// Unwrap `Option` or call retry if it is `None`.
264///
265/// `optionally` is the inverse of `unwrap_or_retry`.
266///
267/// # Example
268///
269/// ```
270/// # use fast_stm::*;
271/// let x = TVar::new(Some(42));
272///
273/// atomically(|tx| {
274///         let inner = unwrap_or_retry(x.read(tx)?)?;
275///         assert_eq!(inner, 42); // inner is always 42.
276///         Ok(inner)
277///     }
278/// );
279/// ```
280pub fn unwrap_or_retry<T>(option: Option<T>) -> StmClosureResult<T> {
281    match option {
282        Some(x) => Ok(x),
283        None => retry(),
284    }
285}
286
287#[inline]
288/// Retry until `cond` is true.
289///
290/// # Example
291///
292/// ```
293/// # use fast_stm::*;
294/// let var = TVar::new(42);
295///
296/// let x = atomically(|tx| {
297///     let v = var.read(tx)?;
298///     guard(v==42)?;
299///     // v is now always 42.
300///     Ok(v)
301/// });
302/// assert_eq!(x, 42);
303/// ```
304pub fn guard(cond: bool) -> StmClosureResult<()> {
305    if cond {
306        Ok(())
307    } else {
308        retry()
309    }
310}
311
312#[inline]
313/// Optionally run a transaction `f`. If `f` fails with a `retry()`, it does
314/// not cancel the whole transaction, but returns `None`.
315///
316/// Note that `optionally` does not always recover the function, if
317/// inconsistencies where found.
318///
319/// `unwrap_or_retry` is the inverse of `optionally`.
320///
321/// # Example
322///
323/// ```
324/// # use fast_stm::*;
325/// let x:Option<i32> = atomically(|tx|
326///     optionally(tx, |_| retry()));
327/// assert_eq!(x, None);
328/// ```
329pub fn optionally<T, F>(tx: &mut Transaction, f: F) -> StmClosureResult<Option<T>>
330where
331    F: Fn(&mut Transaction) -> StmClosureResult<T>,
332{
333    tx.or(|t| f(t).map(Some), |_| Ok(None))
334}
335
336#[cfg(test)]
337mod test_lib {
338    use super::*;
339
340    #[test]
341    fn infinite_retry() {
342        let terminated = test::terminates(300, || {
343            let _infinite_retry: i32 = atomically(|_| retry());
344        });
345        assert!(!terminated);
346    }
347
348    #[test]
349    fn stm_nested() {
350        let var = TVar::new(0);
351
352        let x = atomically(|tx| {
353            var.write(tx, 42)?;
354            var.read(tx)
355        });
356
357        assert_eq!(42, x);
358    }
359
360    /// Run multiple threads.
361    ///
362    /// Thread 1: Read a var, block until it is not 0 and then
363    /// return that value.
364    ///
365    /// Thread 2: Wait a bit. Then write a value.
366    ///
367    /// Check if Thread 1 is woken up correctly and then check for
368    /// correctness.
369    #[test]
370    fn threaded() {
371        use std::thread;
372        use std::time::Duration;
373
374        let var = TVar::new(0);
375        // Clone for other thread.
376        let varc = var.clone();
377
378        let x = test::async_test(
379            800,
380            move || {
381                atomically(|tx| {
382                    let x = varc.read(tx)?;
383                    if x == 0 {
384                        retry()
385                    } else {
386                        Ok(x)
387                    }
388                })
389            },
390            || {
391                thread::sleep(Duration::from_millis(100));
392
393                atomically(|tx| var.write(tx, 42));
394            },
395        )
396        .unwrap();
397
398        assert_eq!(42, x);
399    }
400
401    /// test if a STM calculation is rerun when a Var changes while executing
402    #[test]
403    fn read_write_interfere() {
404        use std::thread;
405        use std::time::Duration;
406
407        // create var
408        let var = TVar::new(0);
409        let varc = var.clone(); // Clone for other thread.
410
411        // spawn a thread
412        let t = thread::spawn(move || {
413            atomically(|tx| {
414                // read the var
415                let x = varc.read(tx)?;
416                // ensure that x varc changes in between
417                thread::sleep(Duration::from_millis(500));
418
419                // write back modified data this should only
420                // happen when the value has not changed
421                varc.write(tx, x + 10)
422            });
423        });
424
425        // ensure that the thread has started and already read the var
426        thread::sleep(Duration::from_millis(100));
427
428        // now change it
429        atomically(|tx| var.write(tx, 32));
430
431        // finish and compare
432        let _ = t.join();
433        assert_eq!(42, var.read_atomic());
434    }
435
436    #[test]
437    fn or_simple() {
438        let var = TVar::new(42);
439
440        let x = atomically(|tx| tx.or(|_| retry(), |tx| var.read(tx)));
441
442        assert_eq!(x, 42);
443    }
444
445    /// A variable should not be written,
446    /// when another branch was taken
447    #[test]
448    fn or_nocommit() {
449        let var = TVar::new(42);
450
451        let x = atomically(|tx| {
452            tx.or(
453                |tx| {
454                    var.write(tx, 23)?;
455                    retry()
456                },
457                |tx| var.read(tx),
458            )
459        });
460
461        assert_eq!(x, 42);
462    }
463
464    #[test]
465    fn or_nested_first() {
466        let var = TVar::new(42);
467
468        let x = atomically(|tx| tx.or(|tx| tx.or(|_| retry(), |_| retry()), |tx| var.read(tx)));
469
470        assert_eq!(x, 42);
471    }
472
473    #[test]
474    fn or_nested_second() {
475        let var = TVar::new(42);
476
477        let x = atomically(|tx| tx.or(|_| retry(), |t| t.or(|t2| var.read(t2), |_| retry())));
478
479        assert_eq!(x, 42);
480    }
481
482    #[test]
483    fn unwrap_some() {
484        let x = Some(42);
485        let y = atomically(|_| unwrap_or_retry(x));
486        assert_eq!(y, 42);
487    }
488
489    #[test]
490    fn unwrap_none() {
491        let x: Option<i32> = None;
492        assert_eq!(unwrap_or_retry(x), retry());
493    }
494
495    #[test]
496    fn guard_true() {
497        let x = guard(true);
498        assert_eq!(x, Ok(()));
499    }
500
501    #[test]
502    fn guard_false() {
503        let x = guard(false);
504        assert_eq!(x, retry());
505    }
506
507    #[test]
508    fn optionally_succeed() {
509        let x = atomically(|t| optionally(t, |_| Ok(42)));
510        assert_eq!(x, Some(42));
511    }
512
513    #[test]
514    fn optionally_fail() {
515        let x: Option<i32> = atomically(|t| optionally(t, |_| retry()));
516        assert_eq!(x, None);
517    }
518}