diff --git a/src/concurrency/thread.rs b/src/concurrency/thread.rs index 7838bd2143..c7ae335d04 100644 --- a/src/concurrency/thread.rs +++ b/src/concurrency/thread.rs @@ -515,10 +515,13 @@ impl<'tcx> ThreadManager<'tcx> { &mut self.threads[self.active_thread].stack } - pub fn all_stacks( + pub fn all_blocked_stacks( &self, ) -> impl Iterator>])> { - self.threads.iter_enumerated().map(|(id, t)| (id, &t.stack[..])) + self.threads + .iter_enumerated() + .filter(|(_id, t)| matches!(t.state, ThreadState::Blocked { .. })) + .map(|(id, t)| (id, &t.stack[..])) } /// Create a new thread and returns its id. diff --git a/src/diagnostics.rs b/src/diagnostics.rs index 47d60f912c..70543c653b 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -469,7 +469,7 @@ pub fn report_result<'tcx>( eprint!("{extra}"); // newlines are already in the string if show_all_threads { - for (thread, stack) in ecx.machine.threads.all_stacks() { + for (thread, stack) in ecx.machine.threads.all_blocked_stacks() { if thread != ecx.active_thread() { let stacktrace = Frame::generate_stacktrace_from_stack(stack); let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine); diff --git a/src/shims/unix/linux_like/epoll.rs b/src/shims/unix/linux_like/epoll.rs index 16b7ddd8bd..04f4e33e1d 100644 --- a/src/shims/unix/linux_like/epoll.rs +++ b/src/shims/unix/linux_like/epoll.rs @@ -1,5 +1,5 @@ use std::cell::RefCell; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, VecDeque}; use std::io; use std::time::Duration; @@ -22,8 +22,8 @@ struct Epoll { /// "ready" list; instead, a boolean flag in this list tracks which subset is ready. This makes /// `epoll_wait` less efficient, but also requires less bookkeeping. interest_list: RefCell>, - /// A list of thread ids blocked on this epoll instance. - blocked: RefCell>, + /// The queue of threads blocked on this epoll instance, and how many events they'd like to get. + queue: RefCell>, } impl VisitProvenance for Epoll { @@ -459,7 +459,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } }; // Record this thread as blocked. - epfd.blocked.borrow_mut().push(this.active_thread()); + epfd.queue + .borrow_mut() + .push_back((this.active_thread(), maxevents.try_into().unwrap())); // And block it. let dest = dest.clone(); // We keep a strong ref to the underlying `Epoll` to make sure it sticks around. @@ -477,14 +479,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { |this, unblock: UnblockKind| { match unblock { UnblockKind::Ready => { - return_ready_list(&epfd, &dest, &event, this)?; + let events = return_ready_list(&epfd, &dest, &event, this)?; + assert!(events > 0, "we got woken up with no events to deliver"); interp_ok(()) }, UnblockKind::TimedOut => { // Remove the current active thread_id from the blocked thread_id list. epfd - .blocked.borrow_mut() - .retain(|&id| id != this.active_thread()); + .queue.borrow_mut() + .retain(|&(id, _events)| id != this.active_thread()); this.write_int(0, &dest)?; interp_ok(()) }, @@ -550,7 +553,7 @@ fn update_readiness<'tcx>( &mut dyn FnMut(&mut EpollEventInterest) -> InterpResult<'tcx>, ) -> InterpResult<'tcx>, ) -> InterpResult<'tcx> { - let mut wakeup = false; + let mut num_ready = 0u32; // how many events we have ready to deliver for_each_interest(&mut |interest| { // Update the ready events tracked in this interest. let new_readiness = interest.relevant_events & active_events; @@ -565,19 +568,29 @@ fn update_readiness<'tcx>( ecx.release_clock(|clock| { interest.clock.join(clock); })?; - wakeup = true; + num_ready = num_ready.saturating_add(1); } interp_ok(()) })?; - if wakeup { - // Wake up threads that may have been waiting for events on this epoll. - // Do this only once for all the interests. - // Edge-triggered notification only notify one thread even if there are - // multiple threads blocked on the same epoll. - if let Some(thread_id) = epoll.blocked.borrow_mut().pop() { - ecx.unblock_thread(thread_id, BlockReason::Epoll)?; + // Edge-triggered notifications only wake up as many threads as are needed to deliver + // all the events. + while num_ready > 0 + && let Some((thread_id, events)) = epoll.queue.borrow_mut().pop_front() + { + ecx.unblock_thread(thread_id, BlockReason::Epoll)?; + // Keep track of how many events we have left to deliver (except if we saturated; + // in that case we just wake up everybody). + if num_ready != u32::MAX { + num_ready = num_ready.saturating_sub(events); } } + // Sanity-check: if there are threads left to wake up, then there are no more ready events. + if !epoll.queue.borrow().is_empty() { + assert!( + epoll.interest_list.borrow().values().all(|i| !i.ready), + "there are unconsumed ready events and threads ready to take them" + ); + } interp_ok(()) } @@ -589,7 +602,7 @@ fn return_ready_list<'tcx>( dest: &MPlaceTy<'tcx>, events: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, -) -> InterpResult<'tcx> { +) -> InterpResult<'tcx, i32> { let mut interest_list = epfd.interest_list.borrow_mut(); let mut num_of_events: i32 = 0; let mut array_iter = ecx.project_array_fields(events)?; @@ -629,5 +642,5 @@ fn return_ready_list<'tcx>( } } ecx.write_int(num_of_events, dest)?; - interp_ok(()) + interp_ok(num_of_events) } diff --git a/tests/fail-dep/libc/eventfd_block_read_twice.rs b/tests/fail-dep/libc/eventfd_block_read_twice.rs index 9dc554030c..98cc80b6b4 100644 --- a/tests/fail-dep/libc/eventfd_block_read_twice.rs +++ b/tests/fail-dep/libc/eventfd_block_read_twice.rs @@ -1,6 +1,4 @@ //@only-target: linux android illumos -//~^ERROR: deadlocked -//~^^ERROR: deadlocked //@compile-flags: -Zmiri-deterministic-concurrency //@error-in-other-file: deadlock diff --git a/tests/fail-dep/libc/eventfd_block_read_twice.stderr b/tests/fail-dep/libc/eventfd_block_read_twice.stderr index 4730045cfe..a7dfa0b6ea 100644 --- a/tests/fail-dep/libc/eventfd_block_read_twice.stderr +++ b/tests/fail-dep/libc/eventfd_block_read_twice.stderr @@ -14,24 +14,13 @@ note: inside `main` LL | thread2.join().unwrap(); | ^^^^^^^^^^^^^^ -error: the evaluated program deadlocked - | - = note: this thread got stuck here - = note: (no span available) - error: the evaluated program deadlocked --> tests/fail-dep/libc/eventfd_block_read_twice.rs:LL:CC | LL | let res: i64 = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), 8).try_into().unwrap() }; | ^ this thread got stuck here -error: the evaluated program deadlocked - | - = note: this thread got stuck here - = note: (no span available) - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace -error: aborting due to 4 previous errors +error: aborting due to 2 previous errors diff --git a/tests/fail-dep/libc/eventfd_block_write_twice.rs b/tests/fail-dep/libc/eventfd_block_write_twice.rs index 5297a32977..1a1d76eda2 100644 --- a/tests/fail-dep/libc/eventfd_block_write_twice.rs +++ b/tests/fail-dep/libc/eventfd_block_write_twice.rs @@ -1,6 +1,4 @@ //@only-target: linux android illumos -//~^ERROR: deadlocked -//~^^ERROR: deadlocked //@compile-flags: -Zmiri-deterministic-concurrency //@error-in-other-file: deadlock @@ -38,7 +36,7 @@ fn main() { let thread2 = thread::spawn(move || { let sized_8_data = (u64::MAX - 1).to_ne_bytes(); - // Write u64::MAX - 1, so the all subsequent write will block. + // Write u64::MAX - 1, so that all subsequent writes will block. let res: i64 = unsafe { // This `write` will initially blocked, then get unblocked by thread3, then get blocked again // because the `write` in thread1 executes first. diff --git a/tests/fail-dep/libc/eventfd_block_write_twice.stderr b/tests/fail-dep/libc/eventfd_block_write_twice.stderr index c0e38df98d..0e554598ec 100644 --- a/tests/fail-dep/libc/eventfd_block_write_twice.stderr +++ b/tests/fail-dep/libc/eventfd_block_write_twice.stderr @@ -14,24 +14,13 @@ note: inside `main` LL | thread2.join().unwrap(); | ^^^^^^^^^^^^^^ -error: the evaluated program deadlocked - | - = note: this thread got stuck here - = note: (no span available) - error: the evaluated program deadlocked --> tests/fail-dep/libc/eventfd_block_write_twice.rs:LL:CC | LL | libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() | ^ this thread got stuck here -error: the evaluated program deadlocked - | - = note: this thread got stuck here - = note: (no span available) - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace -error: aborting due to 4 previous errors +error: aborting due to 2 previous errors diff --git a/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/tests/fail-dep/libc/libc_epoll_block_two_thread.rs index 054cb812d9..8c51416f5a 100644 --- a/tests/fail-dep/libc/libc_epoll_block_two_thread.rs +++ b/tests/fail-dep/libc/libc_epoll_block_two_thread.rs @@ -1,11 +1,12 @@ //@compile-flags: -Zmiri-deterministic-concurrency -//~^ERROR: deadlocked -//~^^ERROR: deadlocked //@only-target: linux android illumos //@error-in-other-file: deadlock use std::convert::TryInto; -use std::thread::spawn; +use std::thread; + +#[path = "../../utils/libc.rs"] +mod libc_utils; // Using `as` cast since `EPOLLET` wraps around const EPOLL_IN_OUT_ET: u32 = (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as _; @@ -49,39 +50,37 @@ fn main() { 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); + // Create an eventfd instance. + let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC; + let fd1 = unsafe { libc::eventfd(0, flags) }; + // Make a duplicate so that we have two file descriptors for the same file description. + let fd2 = unsafe { libc::dup(fd1) }; - // 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) }; + // Register both with epoll. + let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd1 as u64 }; + let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd1, &mut ev) }; + assert_eq!(res, 0); + let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd2 as u64 }; + let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd2, &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); + // Consume the initial events. + let expected = [(libc::EPOLLOUT as u32, fd1 as u64), (libc::EPOLLOUT as u32, fd2 as u64)]; + check_epoll_wait::<8>(epfd, &expected, -1); - let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); - let expected_value = fds[0] as u64; - let thread1 = spawn(move || { - check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], -1); - //~^ERROR: deadlocked + let thread1 = thread::spawn(move || { + check_epoll_wait::<2>(epfd, &expected, -1); }); - let thread2 = spawn(move || { - check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], -1); + let thread2 = thread::spawn(move || { + check_epoll_wait::<2>(epfd, &expected, -1); + //~^ERROR: deadlocked }); + // Yield so the threads are both blocked. + thread::yield_now(); - let thread3 = spawn(move || { - // Just a single write, so we only wake up one of them. - let data = "abcde".as_bytes().as_ptr(); - let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) }; - assert!(res > 0 && res <= 5); - }); + // Create two events at once. + libc_utils::write_all_from_slice(fd1, &0_u64.to_ne_bytes()).unwrap(); thread1.join().unwrap(); thread2.join().unwrap(); - thread3.join().unwrap(); } diff --git a/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr b/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr index c3cd04c0b5..6740faedb3 100644 --- a/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr +++ b/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr @@ -1,8 +1,3 @@ -error: the evaluated program deadlocked - | - = note: this thread got stuck here - = note: (no span available) - error: the evaluated program deadlocked --> RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC | @@ -16,22 +11,16 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; note: inside `main` --> tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC | -LL | thread1.join().unwrap(); +LL | thread2.join().unwrap(); | ^^^^^^^^^^^^^^ error: the evaluated program deadlocked --> tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC | -LL | check_epoll_wait::(epfd, &[(expected_event, expected_value)], -1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this thread got stuck here - -error: the evaluated program deadlocked - | - = note: this thread got stuck here - = note: (no span available) - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +LL | check_epoll_wait::(epfd, &expected, -1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this thread got stuck here note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace -error: aborting due to 4 previous errors +error: aborting due to 2 previous errors diff --git a/tests/fail-dep/libc/socketpair-close-while-blocked.rs b/tests/fail-dep/libc/socketpair-close-while-blocked.rs index 0699dec655..a975e404a9 100644 --- a/tests/fail-dep/libc/socketpair-close-while-blocked.rs +++ b/tests/fail-dep/libc/socketpair-close-while-blocked.rs @@ -1,6 +1,5 @@ //! This is a regression test for : we had some //! faulty logic around `release_clock` that led to this code not reporting a data race. -//~^^ERROR: deadlock //@ignore-target: windows # no libc socketpair on Windows //@compile-flags: -Zmiri-deterministic-concurrency //@error-in-other-file: deadlock diff --git a/tests/fail-dep/libc/socketpair-close-while-blocked.stderr b/tests/fail-dep/libc/socketpair-close-while-blocked.stderr index ac2346f606..1e802209fd 100644 --- a/tests/fail-dep/libc/socketpair-close-while-blocked.stderr +++ b/tests/fail-dep/libc/socketpair-close-while-blocked.stderr @@ -20,12 +20,7 @@ error: the evaluated program deadlocked LL | libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) | ^ this thread got stuck here -error: the evaluated program deadlocked - | - = note: this thread got stuck here - = note: (no span available) - note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors diff --git a/tests/fail-dep/libc/socketpair_block_read_twice.rs b/tests/fail-dep/libc/socketpair_block_read_twice.rs index 0fecfb8f66..37aa459064 100644 --- a/tests/fail-dep/libc/socketpair_block_read_twice.rs +++ b/tests/fail-dep/libc/socketpair_block_read_twice.rs @@ -1,6 +1,4 @@ //@ignore-target: windows # No libc socketpair on Windows -//~^ERROR: deadlocked -//~^^ERROR: deadlocked // test_race depends on a deterministic schedule. //@compile-flags: -Zmiri-deterministic-concurrency //@error-in-other-file: deadlock diff --git a/tests/fail-dep/libc/socketpair_block_read_twice.stderr b/tests/fail-dep/libc/socketpair_block_read_twice.stderr index c599432ff1..3f7bbc7796 100644 --- a/tests/fail-dep/libc/socketpair_block_read_twice.stderr +++ b/tests/fail-dep/libc/socketpair_block_read_twice.stderr @@ -14,24 +14,13 @@ note: inside `main` LL | thread2.join().unwrap(); | ^^^^^^^^^^^^^^ -error: the evaluated program deadlocked - | - = note: this thread got stuck here - = note: (no span available) - error: the evaluated program deadlocked --> tests/fail-dep/libc/socketpair_block_read_twice.rs:LL:CC | LL | libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) | ^ this thread got stuck here -error: the evaluated program deadlocked - | - = note: this thread got stuck here - = note: (no span available) - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace -error: aborting due to 4 previous errors +error: aborting due to 2 previous errors diff --git a/tests/fail-dep/libc/socketpair_block_write_twice.rs b/tests/fail-dep/libc/socketpair_block_write_twice.rs index 048938c091..34eab8aaa0 100644 --- a/tests/fail-dep/libc/socketpair_block_write_twice.rs +++ b/tests/fail-dep/libc/socketpair_block_write_twice.rs @@ -1,6 +1,4 @@ //@ignore-target: windows # No libc socketpair on Windows -//~^ERROR: deadlocked -//~^^ERROR: deadlocked // test_race depends on a deterministic schedule. //@compile-flags: -Zmiri-deterministic-concurrency //@error-in-other-file: deadlock diff --git a/tests/fail-dep/libc/socketpair_block_write_twice.stderr b/tests/fail-dep/libc/socketpair_block_write_twice.stderr index 2230d99fc6..b8dbb513da 100644 --- a/tests/fail-dep/libc/socketpair_block_write_twice.stderr +++ b/tests/fail-dep/libc/socketpair_block_write_twice.stderr @@ -14,24 +14,13 @@ note: inside `main` LL | thread2.join().unwrap(); | ^^^^^^^^^^^^^^ -error: the evaluated program deadlocked - | - = note: this thread got stuck here - = note: (no span available) - error: the evaluated program deadlocked --> tests/fail-dep/libc/socketpair_block_write_twice.rs:LL:CC | LL | let res = unsafe { libc::write(fds[0], data.as_ptr() as *const libc::c_void, data.len()) }; | ^ this thread got stuck here -error: the evaluated program deadlocked - | - = note: this thread got stuck here - = note: (no span available) - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace -error: aborting due to 4 previous errors +error: aborting due to 2 previous errors diff --git a/tests/pass-dep/libc/libc-epoll-blocking.rs b/tests/pass-dep/libc/libc-epoll-blocking.rs index 81d4e501c4..c67386b4f8 100644 --- a/tests/pass-dep/libc/libc-epoll-blocking.rs +++ b/tests/pass-dep/libc/libc-epoll-blocking.rs @@ -4,7 +4,6 @@ use std::convert::TryInto; use std::thread; -use std::thread::spawn; #[path = "../../utils/libc.rs"] mod libc_utils; @@ -17,6 +16,7 @@ fn main() { test_notification_after_timeout(); test_epoll_race(); wakeup_on_new_interest(); + multiple_events_wake_multiple_threads(); } // Using `as` cast since `EPOLLET` wraps around @@ -90,7 +90,7 @@ fn test_epoll_block_then_unblock() { // epoll_wait before triggering notification so it will block then get unblocked before timeout. let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); let expected_value = fds[0] as u64; - let thread1 = spawn(move || { + let thread1 = thread::spawn(move || { thread::yield_now(); let data = "abcde".as_bytes().as_ptr(); let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) }; @@ -210,3 +210,54 @@ fn wakeup_on_new_interest() { // This should wake up the thread. t.join().unwrap(); } + +/// Ensure that if a single operation triggers multiple events, we wake up enough threads +/// to consume them all. +fn multiple_events_wake_multiple_threads() { + // Create an epoll instance. + let epfd = unsafe { libc::epoll_create1(0) }; + assert_ne!(epfd, -1); + + // Create an eventfd instance. + let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC; + let fd1 = unsafe { libc::eventfd(0, flags) }; + // Make a duplicate so that we have two file descriptors for the same file description. + let fd2 = unsafe { libc::dup(fd1) }; + + // Register both with epoll. + let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd1 as u64 }; + let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd1, &mut ev) }; + assert_eq!(res, 0); + let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd2 as u64 }; + let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd2, &mut ev) }; + assert_eq!(res, 0); + + // Consume the initial events. + let expected = [(libc::EPOLLOUT as u32, fd1 as u64), (libc::EPOLLOUT as u32, fd2 as u64)]; + check_epoll_wait::<8>(epfd, &expected, -1); + + // Block two threads on the epoll, both wanting to get just one event. + let t1 = thread::spawn(move || { + let mut e = libc::epoll_event { events: 0, u64: 0 }; + let res = unsafe { libc::epoll_wait(epfd, &raw mut e, 1, -1) }; + assert!(res == 1); + (e.events, e.u64) + }); + let t2 = thread::spawn(move || { + let mut e = libc::epoll_event { events: 0, u64: 0 }; + let res = unsafe { libc::epoll_wait(epfd, &raw mut e, 1, -1) }; + assert!(res == 1); + (e.events, e.u64) + }); + // Yield so both threads are waiting now. + thread::yield_now(); + + // Trigger the eventfd. This triggers two events at once! + libc_utils::write_all_from_slice(fd1, &0_u64.to_ne_bytes()).unwrap(); + + // Both threads should have been woken up so that both events can be consumed. + let e1 = t1.join().unwrap(); + let e2 = t2.join().unwrap(); + // Ensure that across the two threads we got both events. + assert!(expected == [e1, e2] || expected == [e2, e1]); +} diff --git a/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/tests/pass-dep/libc/libc-epoll-no-blocking.rs index b689bb697a..2c55df853a 100644 --- a/tests/pass-dep/libc/libc-epoll-no-blocking.rs +++ b/tests/pass-dep/libc/libc-epoll-no-blocking.rs @@ -262,10 +262,8 @@ fn test_epoll_eventfd() { let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC; let fd = unsafe { libc::eventfd(0, flags) }; - // Write to the eventfd instance. - let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); - let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) }; - assert_eq!(res, 8); + // Write 1 to the eventfd instance. + libc_utils::write_all_from_slice(fd, &1_u64.to_ne_bytes()).unwrap(); // Create an epoll instance. let epfd = unsafe { libc::epoll_create1(0) }; @@ -281,18 +279,15 @@ fn test_epoll_eventfd() { let expected_value = u64::try_from(fd).unwrap(); check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); - // Write to the eventfd again. - let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) }; - assert_eq!(res, 8); + // Write 0 to the eventfd. + libc_utils::write_all_from_slice(fd, &0_u64.to_ne_bytes()).unwrap(); // This does not change the status, so we should get no event. // However, Linux performs a spurious wakeup. check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); // Read from the eventfd. - let mut buf = [0u8; 8]; - let res = unsafe { libc_utils::read_all(fd, buf.as_mut_ptr().cast(), 8) }; - assert_eq!(res, 8); + libc_utils::read_all_into_array::<8>(fd).unwrap(); // This consumes the event, so the read status is gone. However, deactivation // does not trigger an event. diff --git a/tests/utils/libc.rs b/tests/utils/libc.rs index 4757a5a268..ceeb840f3b 100644 --- a/tests/utils/libc.rs +++ b/tests/utils/libc.rs @@ -22,6 +22,18 @@ pub unsafe fn read_all( return read_so_far as libc::ssize_t; } +#[track_caller] +pub fn read_all_into_array(fd: libc::c_int) -> Result<[u8; N], libc::ssize_t> { + let mut buf = [0; N]; + let res = unsafe { read_all(fd, buf.as_mut_ptr().cast(), buf.len()) }; + if res >= 0 { + assert_eq!(res as usize, buf.len()); + Ok(buf) + } else { + Err(res) + } +} + pub unsafe fn write_all( fd: libc::c_int, buf: *const libc::c_void, @@ -39,3 +51,14 @@ pub unsafe fn write_all( } return written_so_far as libc::ssize_t; } + +#[track_caller] +pub fn write_all_from_slice(fd: libc::c_int, buf: &[u8]) -> Result<(), libc::ssize_t> { + let res = unsafe { write_all(fd, buf.as_ptr().cast(), buf.len()) }; + if res >= 0 { + assert_eq!(res as usize, buf.len()); + Ok(()) + } else { + Err(res) + } +}