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}