From 74b4fe0bb1052477d7449bfcd9a1b24ada66d5ca Mon Sep 17 00:00:00 2001 From: Bert Peters Date: Sun, 12 Nov 2023 18:36:53 +0100 Subject: [PATCH] Rewrite example to show potential deadlock The example originally showed a certain deadlock, which was not as clear as it could be. The new version shows intentionally racy code that may result in a successful execution but may also deadlock. --- examples/mutex_cycle.rs | 70 +++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/examples/mutex_cycle.rs b/examples/mutex_cycle.rs index 94bf075..cdff9e9 100644 --- a/examples/mutex_cycle.rs +++ b/examples/mutex_cycle.rs @@ -1,26 +1,62 @@ //! Show what a crash looks like //! -//! This shows what a traceback of a cycle detection looks like. It is expected to crash. +//! This shows what a traceback of a cycle detection looks like. It is expected to crash when run in +//! debug mode, because it might deadlock. In release mode, no tracing is used and the program may +//! do any of the following: +//! +//! - Return a random valuation of `a`, `b`, and `c`. The implementation has a race-condition by +//! design. I have observed (4, 3, 6), but also (6, 3, 5). +//! - Deadlock forever. +//! +//! One can increase the SLEEP_TIME constant to increase the likelihood of a deadlock to occur. On +//! my machine, 1ns of sleep time gives about a 50/50 chance of the program deadlocking. +use std::thread; +use std::time::Duration; + use tracing_mutex::stdsync::Mutex; fn main() { - let a = Mutex::new(()); - let b = Mutex::new(()); - let c = Mutex::new(()); + let a = Mutex::new(1); + let b = Mutex::new(2); + let c = Mutex::new(3); - // Create an edge from a to b - { - let _a = a.lock(); - let _b = b.lock(); - } + // Increase this time to increase the likelihood of a deadlock. + const SLEEP_TIME: Duration = Duration::from_nanos(1); - // Create an edge from b to c - { - let _b = b.lock(); - let _c = c.lock(); - } + // Depending on random CPU performance, this section may deadlock, or may return a result. With + // tracing enabled, the potential deadlock is always detected and a backtrace should be + // produced. + thread::scope(|s| { + // Create an edge from a to b + s.spawn(|| { + let a = a.lock().unwrap(); + thread::sleep(SLEEP_TIME); + *b.lock().unwrap() += *a; + }); - // Now crash by trying to add an edge from c to a - let _c = c.lock(); - let _a = a.lock(); // This line will crash + // Create an edge from b to c + s.spawn(|| { + let b = b.lock().unwrap(); + thread::sleep(SLEEP_TIME); + *c.lock().unwrap() += *b; + }); + + // Create an edge from c to a + // + // N.B. the program can crash on any of the three edges, as there is no guarantee which + // thread will execute first. Nevertheless, any one of them is guaranteed to panic with + // tracing enabled. + s.spawn(|| { + let c = c.lock().unwrap(); + thread::sleep(SLEEP_TIME); + *a.lock().unwrap() += *c; + }); + }); + + println!( + "{}, {}, {}", + a.into_inner().unwrap(), + b.into_inner().unwrap(), + c.into_inner().unwrap() + ); }