Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing spinlock with Loom #162

Open
cmnord opened this issue Jul 28, 2020 · 10 comments
Open

Testing spinlock with Loom #162

cmnord opened this issue Jul 28, 2020 · 10 comments

Comments

@cmnord
Copy link

cmnord commented Jul 28, 2020

I am trying to model a spinlock with Loom and running into a similar issue to #52 and #115. In particular, if I run my test loom with

RUSTFLAGS="--cfg loom" RUST_BACKTRACE=1 cargo test --test loom -- --test-threads=1 --nocapture

I get thread panicked while panicking. aborting. because Model exeeded maximum number of branches. This is often caused by an algorithm requiring the processor to make progress, e.g. spin locks..

#52 says:

the value of max_branches is to avoid an infinite loop when trying to model concurrent algorithms that are unbounded. For example, if you have a spin lock, if the thread that is spinning never yields, the model can go forever. If you have a legit case, you can increase the value of max_branches.

Does that mean that I can't test my spinlock with Loom, or do I need to add something to my spinlock implementation or my test to make it work with Loom? My spinlock implementation and Loom code are in this branch.

Thanks!

@hawkw
Copy link
Member

hawkw commented Jul 28, 2020

Try increasing the value of max_branches by a bit?

@carllerche
Copy link
Member

You need to yield in the loop before retrying: https://docs.rs/loom/0.3.5/loom/thread/fn.yield_now.html

This tells loom there is a loop going on and to try some other branches.

@tower120
Copy link
Contributor

So how did you test spinlocks in the end? I tried yield_now, but that's incredibly slow. (like tens of minutes)

@Darksonn
Copy link
Contributor

Loom is slow. Tokio's CI setup takes more than an hour because that's how long time our slowest loom test takes. You could try to mess with LOOM_MAX_PREEMPTIONS to have it test less of the state space.

@tower120
Copy link
Contributor

I see, thanks. You should add note about hour-scale "slow" in the docs. I was confident that it hang up.

@tower120
Copy link
Contributor

tower120 commented Sep 13, 2021

BTW, can it, theoretically, become multi-threaded?

@Darksonn
Copy link
Contributor

Yes, but nobody has put in the work.

Generally, loom will print the iteration number as it runs, so you can use that to see whether it has hung up. (If running in test, make sure to enable prints in the test.)

@tower120
Copy link
Contributor

It does show! But for me, it just output everything at once, once test done.
Maybe, that's just how clion+rust console work though...

Thanks, I'll try with "pure" console.

@jneem
Copy link

jneem commented Feb 28, 2025

I had a similar issue involving a spin lock, and when I tried to minimize the problem I ran into some surprising (to me, an atomics novice) difference between the behavior of swap and compare_exchange. Specifically, the following spin lock fails loom tests with "Model exceeded maximum number of branches":

use loom::hint;
use loom::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};

pub struct Lock {
    locked: AtomicBool,
}

impl Lock {
    pub fn lock(&self) {
        while self.locked.swap(true, Acquire) {
            hint::spin_loop();
        }
    }

    pub fn unlock(&self) {
        self.locked.store(false, Release);
    }
}

#[test]
fn test_concurrent_logic() {
    loom::model(|| {
        let v1 = loom::sync::Arc::new(Lock::new());
        let v2 = v1.clone();
        let t1 = thread::spawn(move || {
            v2.lock();
            v2.unlock();
        });

        v1.lock();
        v1.unlock();

        t1.join().unwrap();
    });
}

But if I change while self.locked.swap to while self.locked.compare_exchange_weak(false, true, Acquire, Relaxed).is_err() then the tests succeed. Shouldn't these two things have the same behavior (the Rust Atomics book uses "swap" in its spin lock example)?

@Darksonn
Copy link
Contributor

Darksonn commented Mar 7, 2025

Using swap adds a new value to the history, which a failing compare_exchange_weak does not do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants