tokio-rs / loom

Concurrency permutation testing tool for Rust.
MIT License
2.14k stars 111 forks source link

Loom iterates forever without finishing on this copy of the CPPref SeqCst example #191

Open jsdw opened 3 years ago

jsdw commented 3 years ago

The below code is a rust port of the Sequential Consistency example at the bottom of https://en.cppreference.com/w/cpp/atomic/memory_order which fails to run in loom:

use loom::sync::atomic::{ AtomicUsize, AtomicBool, Ordering };
use loom::thread;
use loom::sync::Arc;
use loom::cell::UnsafeCell;

#[test]
fn requires_seqcst() {
    loom::model(|| {
        let x = Arc::new(AtomicBool::new(false));
        let y = Arc::new(AtomicBool::new(false));
        let z = Arc::new(AtomicUsize::new(0));

        // Two threads set x and y to true simultaneously:
        let x1 = Arc::clone(&x);
        let h1 = thread::spawn(move || x1.store(true, Ordering::SeqCst));
        let y1 = Arc::clone(&y);
        let h2 = thread::spawn(move || y1.store(true, Ordering::SeqCst));

        // Next, two threads (the main thread and a spawned one, because in
        // loom we're limited to 4 threads max) read from either x then y, or
        // y then x, incrementing a counter if, when the fst is true, the snd is
        // also true.
        fn reader(fst: Arc<AtomicBool>, snd: Arc<AtomicBool>, cnt: Arc<AtomicUsize>) {
            while !fst.load(Ordering::SeqCst) {
                thread::yield_now();
            }
            if snd.load(Ordering::SeqCst) {
                cnt.fetch_add(1, Ordering::SeqCst);
            }
        }
        let (x2, y2, z2) = (Arc::clone(&x), Arc::clone(&y), Arc::clone(&z));
        let h3 = thread::spawn(move || reader(x2,y2,z2)); // note ordering: fst is x, snd is y

        let (x3, y3, z3) = (Arc::clone(&x), Arc::clone(&y), Arc::clone(&z));
        reader(y3,x3,z3); // note ordering: fst is y, snd is x

        h1.join().unwrap();
        h2.join().unwrap();
        h3.join().unwrap();

        assert_ne!(z.load(Ordering::Relaxed), 0);
    })
}

I've had success running some things that feel more branchy/complex than this in loom, but I left this test running for a couple of hours and it eventually panicked, resulting in console output like:

 ================== Iteration 136320000 ==================

 ================== Iteration 136340000 ==================

 ================== Iteration 136360000 ==================

 ================== Iteration 136380000 ==================

 ================== Iteration 136400000 ==================

 ================== Iteration 136420000 ==================

thread 'test_seqcst::requires_seqcst' panicked at 'Model exeeded maximum number of branches. This is often caused by an algorithm requiring the processor to make progress, e.g. spin locks.', /Users/james.wilson/.cargo/registry/src/github.com-1ecc6299db9ec823/loom-0.4.0/src/rt/path.rs:199:13
test test_seqcst::requires_seqcst ... FAILED

I tried replacing all of the Ordering::SeqCsts with Ordering::Relaxed to see whether it would run faster or something, but it seemed to keep going for many millions of iterations before I gave up.

Is there a way to have this example (or something practically the same) run in a reasonable number of iterations? Is this a sign of a bug in loom, or is something about my code leading to much more complexity than I had assumed?

(also a nit: exeeded appears to be a typo in the panic)