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

Data race in Arc::into_unique #106

Open
steffahn opened this issue Dec 25, 2024 · 5 comments
Open

Data race in Arc::into_unique #106

steffahn opened this issue Dec 25, 2024 · 5 comments

Comments

@steffahn
Copy link
Contributor

use triomphe::Arc;

fn main() {
    let a = Arc::new(0);
    let b = a.clone();
    std::thread::spawn(move || {
        let _value = *b;
    });
    std::thread::spawn(move || {
        *Arc::into_unique(a).unwrap() += 1;
    });
}
$ cargo +nightly miri run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `/home/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo-miri runner target/miri/x86_64-unknown-linux-gnu/debug/playground`
error: Undefined Behavior: Data race detected between (1) retag read on thread `unnamed-1` and (2) retag write of type `i32` on thread `unnamed-2` at alloc817+0x8. (2) just happened here
   --> /home/…/triomphe/src/unique_arc.rs:238:18
    |
238 |         unsafe { &mut (*self.0.ptr()).data }
    |                  ^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between (1) retag read on thread `unnamed-1` and (2) retag write of type `i32` on thread `unnamed-2` at alloc817+0x8. (2) just happened here
    |
help: and (1) occurred earlier here
   --> src/main.rs:8:5
    |
8   |     });
    |     ^
    = help: retags occur on all (re)borrows and as well as when references are copied or moved
    = help: retags permit optimizations that insert speculative reads or writes
    = help: therefore from the perspective of data races, a retag has the same implications as a read or write
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: BACKTRACE (of the first span) on thread `unnamed-2`:
    = note: inside `<triomphe::UniqueArc<i32> as std::ops::DerefMut>::deref_mut` at /home/…/triomphe/src/unique_arc.rs:238:18: 238:43
note: inside closure
   --> src/main.rs:10:9
    |
10  |         *Arc::into_unique(a).unwrap() += 1;
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

see also my comments in #103

@steffahn
Copy link
Contributor Author

steffahn commented Dec 25, 2024

Ah, even “simpler”, just:

use triomphe::Arc;

fn main() {
    let a = Arc::new(());
    let _a = a.clone();
    std::thread::spawn(move || {
        Arc::into_unique(a);
    });
}

is enough for miri to catch a race.

Manishearth added a commit that referenced this issue Dec 25, 2024
@tobz
Copy link
Contributor

tobz commented Dec 26, 2024

How are you running this, and on what nightly version?

I can't seem to get either the full or simplified reproductions to trigger an error with Miri. 🤔 I tried both as standalone test files (tests/into_unique.rs) on my branch that introduced Arc::into_unique, using 1.85.0-nightly (c86f4b3a1 2024-12-24), and running via cargo +nightly miri test -- --test into_unique.. but no dice.

@steffahn
Copy link
Contributor Author

steffahn commented Dec 26, 2024

rustc +nightly --version
rustc 1.85.0-nightly (bdc6b3de4 2024-12-23)

cargo new repro
cd repro
mkdir triomphe
cd triomphe
git init
git remote add origin https://github.com/Manishearth/triomphe.git
git fetch --depth 1 origin fae2334e83302948b17a7b449858931db0de1ac3
git checkout fae2334e83302948b17a7b449858931db0de1ac3
cd ..
cargo add --path ./triomphe
cat >./src/main.rs <<EOL                                 
use triomphe::Arc;

fn main() {
    let a = Arc::new(0);
    let b = a.clone();
    std::thread::spawn(move || {
        let _value = *b;
    });
    std::thread::spawn(move || {
        *Arc::into_unique(a).unwrap() += 1;
    });
}
EOL
cargo +nightly miri run

@steffahn
Copy link
Contributor Author

steffahn commented Dec 26, 2024

These probably do rely on arbitrary execution order, which might be miri version dependent, or dependent on other factors.

If you try this, try both permutations between

    std::thread::spawn(move || {
        let _value = *b;
    });
    std::thread::spawn(move || {
        *Arc::into_unique(a).unwrap() += 1;
    });

and

    std::thread::spawn(move || {
        *Arc::into_unique(a).unwrap() += 1;
    });
    std::thread::spawn(move || {
        let _value = *b;
    });

Edit: Or a sleep, to make the into_unique succeed:

use triomphe::Arc;

fn main() {
    let a = Arc::new(0);
    let b = a.clone();
    let t1 = std::thread::spawn(move || {
        let _value = *b;
    });
    let t2 = std::thread::spawn(move || {
        std::thread::sleep(std::time::Duration::from_secs(1));
        if let Some(mut u) = Arc::into_unique(a) {
            *u += 1
        }
    });
    t1.join().unwrap();
    t2.join().unwrap();
}

@tobz
Copy link
Contributor

tobz commented Dec 27, 2024

Yeah, weird... maybe it's something about the generated code for running it as a test. I was able to get it to reproduce with your full MVCE; thanks for that. 👍🏻

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

2 participants