//@only-target: linux android illumos // test_race, test_blocking_read and test_blocking_write depend on a deterministic schedule. //@compile-flags: -Zmiri-deterministic-concurrency // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] use std::thread; fn main() { test_read_write(); test_race(); #[cfg(not(target_os = "illumos"))] test_syscall(); test_blocking_read(); test_blocking_write(); test_two_threads_blocked_on_eventfd(); } fn read_bytes(fd: i32, buf: &mut [u8; N]) -> i32 { let res: i32 = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), N).try_into().unwrap() }; return res; } fn write_bytes(fd: i32, data: [u8; N]) -> i32 { let res: i32 = unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, N).try_into().unwrap() }; return res; } fn test_read_write() { let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC; let fd = unsafe { libc::eventfd(0, flags) }; let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); // Write 1 to the counter. let res = write_bytes(fd, sized_8_data); assert_eq!(res, 8); // Read 1 from the counter. let mut buf: [u8; 8] = [0; 8]; let res = read_bytes(fd, &mut buf); // Read returns number of bytes has been read, which is always 8. assert_eq!(res, 8); // Check the value of counter read. let counter = u64::from_ne_bytes(buf); assert_eq!(counter, 1); // After read, the counter is currently 0, read counter 0 should fail with return // value -1. let mut buf: [u8; 8] = [0; 8]; let res = read_bytes(fd, &mut buf); let e = std::io::Error::last_os_error(); assert_eq!(e.raw_os_error(), Some(libc::EAGAIN)); assert_eq!(res, -1); // Write with supplied buffer bigger than 8 bytes should be allowed. let sized_9_data: [u8; 9]; if cfg!(target_endian = "big") { // Adjust the data based on the endianness of host system. sized_9_data = [0, 0, 0, 0, 0, 0, 0, 1, 0]; } else { sized_9_data = [1, 0, 0, 0, 0, 0, 0, 0, 0]; } let res = write_bytes(fd, sized_9_data); assert_eq!(res, 8); // Read with supplied buffer smaller than 8 bytes should fail with return // value -1. let mut buf: [u8; 7] = [1; 7]; let res = read_bytes(fd, &mut buf); let e = std::io::Error::last_os_error(); assert_eq!(e.raw_os_error(), Some(libc::EINVAL)); assert_eq!(res, -1); // Write with supplied buffer smaller than 8 bytes should fail with return // value -1. let size_7_data: [u8; 7] = [1; 7]; let res = write_bytes(fd, size_7_data); let e = std::io::Error::last_os_error(); assert_eq!(e.raw_os_error(), Some(libc::EINVAL)); assert_eq!(res, -1); // Read with supplied buffer bigger than 8 bytes should be allowed. let mut buf: [u8; 9] = [1; 9]; let res = read_bytes(fd, &mut buf); assert_eq!(res, 8); // Write u64::MAX should fail. let u64_max_bytes: [u8; 8] = [255; 8]; let res = write_bytes(fd, u64_max_bytes); let e = std::io::Error::last_os_error(); assert_eq!(e.raw_os_error(), Some(libc::EINVAL)); assert_eq!(res, -1); } fn test_race() { static mut VAL: u8 = 0; let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC; let fd = unsafe { libc::eventfd(0, flags) }; let thread1 = thread::spawn(move || { let mut buf: [u8; 8] = [0; 8]; let res = read_bytes(fd, &mut buf); // read returns number of bytes has been read, which is always 8. assert_eq!(res, 8); let counter = u64::from_ne_bytes(buf); assert_eq!(counter, 1); unsafe { assert_eq!(VAL, 1) }; }); unsafe { VAL = 1 }; let data: [u8; 8] = 1_u64.to_ne_bytes(); let res = write_bytes(fd, data); // write returns number of bytes written, which is always 8. assert_eq!(res, 8); thread::yield_now(); thread1.join().unwrap(); } // This is a test for calling eventfd2 through a syscall. // Illumos supports eventfd, but it has no entry to call it through syscall. #[cfg(not(target_os = "illumos"))] fn test_syscall() { let initval = 0 as libc::c_uint; let flags = (libc::EFD_CLOEXEC | libc::EFD_NONBLOCK) as libc::c_int; let fd = unsafe { libc::syscall(libc::SYS_eventfd2, initval, flags) }; assert_ne!(fd, -1); } // This test will block on eventfd read then get unblocked by `write`. fn test_blocking_read() { // eventfd read will block when EFD_NONBLOCK flag is clear and counter = 0. let flags = libc::EFD_CLOEXEC; let fd = unsafe { libc::eventfd(0, flags) }; let thread1 = thread::spawn(move || { let mut buf: [u8; 8] = [0; 8]; // This will block. let res = read_bytes(fd, &mut buf); // read returns number of bytes has been read, which is always 8. assert_eq!(res, 8); let counter = u64::from_ne_bytes(buf); assert_eq!(counter, 1); }); let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); // Pass control to thread1 so it can block on eventfd `read`. thread::yield_now(); // Write 1 to the counter to unblock thread1. let res = write_bytes(fd, sized_8_data); assert_eq!(res, 8); thread1.join().unwrap(); } /// This test will block on eventfd `write` then get unblocked by `read`. fn test_blocking_write() { // eventfd write will block when EFD_NONBLOCK flag is clear // and the addition caused counter to exceed u64::MAX - 1. let flags = libc::EFD_CLOEXEC; let fd = unsafe { libc::eventfd(0, flags) }; // Write u64 - 1, so the all subsequent write will block. let sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes(); let res: i64 = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() }; assert_eq!(res, 8); let thread1 = thread::spawn(move || { let sized_8_data = 1_u64.to_ne_bytes(); // Write 1 to the counter, this will block. let res: i64 = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() }; // Make sure that write is successful. assert_eq!(res, 8); }); let mut buf: [u8; 8] = [0; 8]; // Pass control to thread1 so it can block on eventfd `write`. thread::yield_now(); // This will unblock previously blocked eventfd read. let res = read_bytes(fd, &mut buf); // read returns number of bytes has been read, which is always 8. assert_eq!(res, 8); let counter = u64::from_ne_bytes(buf); assert_eq!(counter, (u64::MAX - 1)); thread1.join().unwrap(); } // Test two threads blocked on eventfd. // Expected behaviour: // 1. thread1 and thread2 both blocked on `write`. // 2. thread3 unblocks both thread1 and thread2 // 3. The write in thread1 and thread2 return successfully. fn test_two_threads_blocked_on_eventfd() { // eventfd write will block when EFD_NONBLOCK flag is clear // and the addition caused counter to exceed u64::MAX - 1. let flags = libc::EFD_CLOEXEC; let fd = unsafe { libc::eventfd(0, flags) }; // Write u64 - 1, so the all subsequent write will block. let sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes(); let res: i64 = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() }; assert_eq!(res, 8); let thread1 = thread::spawn(move || { let sized_8_data = 1_u64.to_ne_bytes(); let res: i64 = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() }; // Make sure that write is successful. assert_eq!(res, 8); }); let thread2 = thread::spawn(move || { let sized_8_data = 1_u64.to_ne_bytes(); let res: i64 = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() }; // Make sure that write is successful. assert_eq!(res, 8); }); let thread3 = thread::spawn(move || { let mut buf: [u8; 8] = [0; 8]; // This will unblock previously blocked eventfd read. let res = read_bytes(fd, &mut buf); // read returns number of bytes has been read, which is always 8. assert_eq!(res, 8); let counter = u64::from_ne_bytes(buf); assert_eq!(counter, (u64::MAX - 1)); }); thread1.join().unwrap(); thread2.join().unwrap(); thread3.join().unwrap(); }