Skip to content

Weird failing assertions #389

@ZobyTwo

Description

@ZobyTwo

The following code fails the assertion and I am not sure, why.

Whenever the mutex is held by the second thread, either the first did its update (a == b == false) or it didn't (a == b == true).
The second thread may have run the compare-exchange (or the swap) after the first thread changed both values, but if either of them is successful, the assertion isn't run. If the function continues, both a and b must still have their old value.
Strangely, this logic appears to hold with the compare-exchange, but not with the swap (which should have the same semantics because these values are just booleans?).

#[cfg(test)]
#[cfg(feature = "loom")]
mod test {
    use std::sync::Arc;
    use loom::{
        thread,
        sync::{
            Mutex,
            atomic::{AtomicBool, Ordering}
        }
    };

    #[test]
    fn failing_test() {
        pub struct Foo
        {
            a: Mutex<bool>,
            b: AtomicBool,
        }

        loom::model(|| {
            let foo = Arc::new(Foo{
                a: Mutex::new(true),
                b: AtomicBool::new(true),
            });

            let thread_1 = {
                let foo = Arc::clone(&foo);
                thread::spawn(move || {
                    let maybe_guard = (*foo).a.lock();
                    let mut a = maybe_guard.unwrap();
                    foo.b.store(false, Ordering::Relaxed); // Was Release before, but that should not matter.
                    *a = false;
                })
            };

            let thread_2 = {
                let foo = Arc::clone(&foo);
                thread::spawn(move || {
                    // Either those don't have an effect (so the values remain equal), 
                    // or I am exiting the function (not running the assertion):
                    {
                        // works:
                        // if foo.b.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed).is_ok() { return; }

                        // causes assertion:
                        if !foo.b.swap(true, Ordering::Relaxed) { return; }
                    }

                    {
                        let maybe_guard = (*foo).a.lock();
                        let a = maybe_guard.unwrap();
                        let b = (*foo).b.load(Ordering::Relaxed);
                        assert!(*a == b);
                    }
                })
            };

            thread_1.join().unwrap();
            thread_2.join().unwrap();
        })
    }
}

Edit: This also happens when we replace the mutex by a typical spinlock and only do try_lock instead of lock. It appears as though the AtomicBool::swap is not actually atomic, but does separate read and write operations, and thereby loses the value written by the first thread.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions