about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs96
-rw-r--r--src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr41
2 files changed, 137 insertions, 0 deletions
diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs
new file mode 100644
index 00000000000..7f5ec477e19
--- /dev/null
+++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs
@@ -0,0 +1,96 @@
+//@compile-flags: -Zmiri-preemption-rate=0
+//~^ERROR: deadlocked
+//~^^ERROR: deadlocked
+//@only-target: linux
+//@error-in-other-file: deadlock
+
+use std::convert::TryInto;
+use std::thread;
+use std::thread::spawn;
+
+// Using `as` cast since `EPOLLET` wraps around
+const EPOLL_IN_OUT_ET: u32 = (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as _;
+
+#[track_caller]
+fn check_epoll_wait<const N: usize>(
+    epfd: i32,
+    expected_notifications: &[(u32, u64)],
+    timeout: i32,
+) {
+    let epoll_event = libc::epoll_event { events: 0, u64: 0 };
+    let mut array: [libc::epoll_event; N] = [epoll_event; N];
+    let maxsize = N;
+    let array_ptr = array.as_mut_ptr();
+    let res = unsafe { libc::epoll_wait(epfd, array_ptr, maxsize.try_into().unwrap(), timeout) };
+    if res < 0 {
+        panic!("epoll_wait failed: {}", std::io::Error::last_os_error());
+    }
+    assert_eq!(
+        res,
+        expected_notifications.len().try_into().unwrap(),
+        "got wrong number of notifications"
+    );
+    let slice = unsafe { std::slice::from_raw_parts(array_ptr, res.try_into().unwrap()) };
+    for (return_event, expected_event) in slice.iter().zip(expected_notifications.iter()) {
+        let event = return_event.events;
+        let data = return_event.u64;
+        assert_eq!(event, expected_event.0, "got wrong events");
+        assert_eq!(data, expected_event.1, "got wrong data");
+    }
+}
+
+// Test if only one thread is unblocked if multiple threads blocked on same epfd.
+// Expected execution:
+// 1. Thread 2 blocks.
+// 2. Thread 3 blocks.
+// 3. Thread 1 unblocks thread 3.
+// 4. Thread 2 deadlocks.
+fn main() {
+    // Create an epoll instance.
+    let epfd = unsafe { libc::epoll_create1(0) };
+    assert_ne!(epfd, -1);
+
+    // Create a socketpair instance.
+    let mut fds = [-1, -1];
+    let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
+    assert_eq!(res, 0);
+
+    // Register one side of the socketpair with epoll.
+    let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[0] as u64 };
+    let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
+    assert_eq!(res, 0);
+
+    // epoll_wait to clear notification.
+    let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
+    let expected_value = fds[0] as u64;
+    check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], 0);
+
+    let thread1 = spawn(move || {
+        thread::park();
+        let data = "abcde".as_bytes().as_ptr();
+        let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+        assert_eq!(res, 5);
+    });
+
+    let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
+    let expected_value = fds[0] as u64;
+    let thread2 = spawn(move || {
+        thread::park();
+        check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], -1);
+        //~^ERROR: deadlocked
+    });
+    let thread3 = spawn(move || {
+        thread::park();
+        check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], -1);
+    });
+
+    thread2.thread().unpark();
+    thread::yield_now();
+    thread3.thread().unpark();
+    thread::yield_now();
+    thread1.thread().unpark();
+
+    thread1.join().unwrap();
+    thread2.join().unwrap();
+    thread3.join().unwrap();
+}
diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr
new file mode 100644
index 00000000000..010dabc1364
--- /dev/null
+++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr
@@ -0,0 +1,41 @@
+error: deadlock: the evaluated program deadlocked
+  --> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
+   |
+LL |         let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) };
+   |                                                                  ^ the evaluated program deadlocked
+   |
+   = note: BACKTRACE:
+   = note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
+   = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
+   = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
+note: inside `main`
+  --> tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC
+   |
+LL |     thread2.join().unwrap();
+   |     ^^^^^^^^^^^^^^
+
+error: deadlock: the evaluated program deadlocked
+   |
+   = note: the evaluated program deadlocked
+   = note: (no span available)
+   = note: BACKTRACE on thread `unnamed-ID`:
+
+error: deadlock: the evaluated program deadlocked
+  --> tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC
+   |
+LL |         check_epoll_wait::<TAG>(epfd, &[(expected_event, expected_value)], -1);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program deadlocked
+   |
+   = note: BACKTRACE on thread `unnamed-ID`:
+   = note: inside closure at tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC
+
+error: deadlock: the evaluated program deadlocked
+   |
+   = note: the evaluated program deadlocked
+   = note: (no span available)
+   = note: BACKTRACE on thread `unnamed-ID`:
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 4 previous errors
+