Skip to main content

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