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//! <div class="warning">
108//!
109//! **This implementation does not guarantee opacity.** Live transactions can observe
110//! inconsistent intermediate states. This has to be accounted for when writing transactional
111//! code segments. For more details on opacity, see
112//! [On the Correctness of Transactional Memory](https://infoscience.epfl.ch/server/api/core/bitstreams/9f16872d-7c62-4a6f-bdb9-21df82549c71/content).
113//!
114//! </div>
115//!
116//! Software transactional memory is completely safe in the rust sense, so
117//! undefined behavior will never occur.
118//! Still there are multiple rules that
119//! you should obey when dealing with software transactional memory.
120//!
121//! * Don't run code with side effects, especially no IO-code.
122//! Transactions repeat in failure cases. Using IO would repeat this IO-code.
123//! Return a closure if you have to.
124//! * Don't handle `StmResult` yourself.
125//! Use `Transaction::or` to combine alternative paths and `optionally` to check if an inner
126//! function has failed. Always use `?` and
127//! never ignore a `StmResult`.
128//! * Don't run `atomically` inside of another. `atomically` is designed to have side effects
129//! and will therefore break transaction safety.
130//! Nested calls are detected at runtime and handled with panicking.
131//! When you use STM in the inner of a function, then
132//! express it in the public interface, by taking `&mut Transaction` as parameter and
133//! returning `StmResult<T>`. Callers can safely compose it into
134//! larger blocks.
135//! * Don't mix locks and transactions. Your code will easily deadlock or slow
136//! down unpredictably.
137//! * Don't use inner mutability to change the content of a `TVar`.
138//!
139//! Panicking in a transaction is transaction-safe. The transaction aborts and
140//! all changes are discarded. No poisoning or half written transactions happen.
141//!
142//! # Speed
143//!
144//! Generally keep your atomic blocks as small as possible, because
145//! the more time you spend, the more likely it is, to collide with
146//! other threads. For STM, reading `TVar`s is quite slow, because it
147//! needs to look them up in the log every time.
148//! Every used `TVar` increases the chance of collisions. Therefore you should
149//! keep the amount of accessed variables as low as needed.
150//!
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}