Skip to content

Review Linux futex implementation for weak memory #2223

Closed
@cbeuw

Description

@cbeuw

This appears to be the smoking gun of std::sync::mpsc matklad/once_cell#182 and crossbeam channel rust-lang/rust#55005 (comment) deadlocks

// Read an `i32` through the pointer, regardless of any wrapper types.
// It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`.
// FIXME: this fails if `addr` is not a pointer type.
// The atomic ordering for futex(https://man7.org/linux/man-pages/man2/futex.2.html):
// "The load of the value of the futex word is an
// atomic memory access (i.e., using atomic machine instructions
// of the respective architecture). This load, the comparison
// with the expected value, and starting to sleep are performed
// atomically and totally ordered with respect to other futex
// operations on the same futex word."
// SeqCst is total order over all operations.
// FIXME: check if this should be changed when weak memory orders are added.
let futex_val = this
.read_scalar_at_offset_atomic(
&addr.into(),
0,
this.machine.layouts.i32,
AtomicReadOp::SeqCst,
)?
.to_i32()?;

The deadlock can be seen running this

use std::{sync::mpsc::channel, thread};

fn test() {
    let (tx1, rx1) = channel();
    let (tx2, rx2) = channel();
    let t1 = thread::spawn(move || {
        tx1.send(()).unwrap();
        rx2.recv().unwrap();
    });

    rx1.recv().unwrap();

    tx2.send(()).unwrap();

    assert!(t1.join().is_ok());
}

pub fn main() {
    for i in 0.. {
        dbg!(i);
        test();
    }
}
[src/main.rs:20] i = 0
[src/main.rs:20] i = 1
[2022-06-10T22:00:00Z TRACE miri::concurrency::weak_memory] outdated value read at /home/cbeuw/.rustup/toolchains/miri/lib/rustlib/src/rust/library/std/src/sys/unix/futex.rs:61:21: 69:22 (#0)
error: deadlock: the evaluated program deadlocked
   --> /home/cbeuw/.rustup/toolchains/miri/lib/rustlib/src/rust/library/std/src/sys/unix/thread.rs:250:66
    |
250 |             let ret = libc::pthread_join(self.id, ptr::null_mut());
    |                                                                  ^ the evaluated program deadlocked
    |
    = note: inside `std::sys::unix::thread::Thread::join` at /home/cbeuw/.rustup/toolchains/miri/lib/rustlib/src/rust/library/std/src/sys/unix/thread.rs:250:66
    = note: inside `std::thread::JoinInner::<()>::join` at /home/cbeuw/.rustup/toolchains/miri/lib/rustlib/src/rust/library/std/src/thread/mod.rs:1352:9
    = note: inside `std::thread::JoinHandle::<()>::join` at /home/cbeuw/.rustup/toolchains/miri/lib/rustlib/src/rust/library/std/src/thread/mod.rs:1485:9
note: inside `test` at src/main.rs:15:10
   --> src/main.rs:15:10
    |
15  |     assert!(t1.join().is_ok());
    |             ^^^^^^^^^
note: inside `main` at src/main.rs:21:9
   --> src/main.rs:21:9
    |
21  |         test();
    |         ^^^^^^

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

error: aborting due to previous error

(That TRACE line was added locally. I'll make a separate PR to add weak memory logging)

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