Skip to main content

fast_stm/
lib.rs

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