From 33608537d18f092938872fe41ec992d9eb401637 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 4 Feb 2025 06:41:42 -0800 Subject: [PATCH 1/5] Add an `epoll::wait_uninit`. This is like `epoll::wait`, except that it can read into an uninitialized buffer, and it returns a pair of slices, similar to `read_uninit`. This also means it doesn't require "alloc". --- src/backend/libc/event/syscalls.rs | 4 +- src/backend/linux_raw/event/syscalls.rs | 2 - src/buffer.rs | 14 ++--- src/event/epoll.rs | 26 +++++++++ tests/event/epoll.rs | 77 +++++++++++++++++++++++++ 5 files changed, 111 insertions(+), 12 deletions(-) diff --git a/src/backend/libc/event/syscalls.rs b/src/backend/libc/event/syscalls.rs index f55310cb0..987c3edce 100644 --- a/src/backend/libc/event/syscalls.rs +++ b/src/backend/libc/event/syscalls.rs @@ -4,7 +4,6 @@ use crate::backend::c; #[cfg(any(linux_kernel, solarish, target_os = "redox"))] use crate::backend::conv::ret; use crate::backend::conv::ret_c_int; -#[cfg(feature = "alloc")] #[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))] use crate::backend::conv::ret_u32; #[cfg(solarish)] @@ -27,7 +26,7 @@ use crate::utils::as_ptr; #[cfg(any( all(feature = "alloc", bsd), solarish, - all(feature = "alloc", any(linux_kernel, target_os = "redox")), + all(any(linux_kernel, target_os = "redox")), ))] use core::mem::MaybeUninit; #[cfg(any( @@ -512,7 +511,6 @@ pub(crate) fn epoll_del(epoll: BorrowedFd<'_>, source: BorrowedFd<'_>) -> io::Re } #[inline] -#[cfg(feature = "alloc")] #[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))] pub(crate) fn epoll_wait( epoll: BorrowedFd<'_>, diff --git a/src/backend/linux_raw/event/syscalls.rs b/src/backend/linux_raw/event/syscalls.rs index f0e5004e7..77e327d5b 100644 --- a/src/backend/linux_raw/event/syscalls.rs +++ b/src/backend/linux_raw/event/syscalls.rs @@ -15,7 +15,6 @@ use crate::io; use crate::utils::as_mut_ptr; #[cfg(feature = "linux_5_11")] use crate::utils::option_as_ptr; -#[cfg(feature = "alloc")] use core::mem::MaybeUninit; use core::ptr::null_mut; use linux_raw_sys::general::{kernel_sigset_t, EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD}; @@ -174,7 +173,6 @@ pub(crate) fn epoll_del(epfd: BorrowedFd<'_>, fd: BorrowedFd<'_>) -> io::Result< } } -#[cfg(feature = "alloc")] #[inline] pub(crate) fn epoll_wait( epfd: BorrowedFd<'_>, diff --git a/src/buffer.rs b/src/buffer.rs index bb31ac056..fd3b2f5b1 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -5,21 +5,21 @@ use core::mem::MaybeUninit; use core::slice; -/// Split an uninitialized byte slice into initialized and uninitialized parts. +/// Split an uninitialized slice into initialized and uninitialized parts. /// /// # Safety /// /// `init_len` must not be greater than `buf.len()`, and at least `init_len` -/// bytes must be initialized. +/// elements must be initialized. #[inline] -pub(super) unsafe fn split_init( - buf: &mut [MaybeUninit], +pub(super) unsafe fn split_init( + buf: &mut [MaybeUninit], init_len: usize, -) -> (&mut [u8], &mut [MaybeUninit]) { +) -> (&mut [T], &mut [MaybeUninit]) { debug_assert!(init_len <= buf.len()); let buf_ptr = buf.as_mut_ptr(); let uninit_len = buf.len() - init_len; - let init = slice::from_raw_parts_mut(buf_ptr.cast::(), init_len); + let init = slice::from_raw_parts_mut(buf_ptr.cast::(), init_len); let uninit = slice::from_raw_parts_mut(buf_ptr.add(init_len), uninit_len); (init, uninit) } @@ -66,7 +66,7 @@ mod tests { #[test] fn test_split_init_empty() { unsafe { - let (init, uninit) = split_init(&mut [], 0); + let (init, uninit) = split_init::(&mut [], 0); assert!(init.is_empty()); assert!(uninit.is_empty()); } diff --git a/src/event/epoll.rs b/src/event/epoll.rs index 6afd96e1a..483109071 100644 --- a/src/event/epoll.rs +++ b/src/event/epoll.rs @@ -75,6 +75,7 @@ use super::epoll; pub use crate::backend::event::epoll::*; use crate::backend::event::syscalls; +use crate::buffer::split_init; use crate::fd::{AsFd, OwnedFd}; use crate::io; #[cfg(feature = "alloc")] @@ -83,6 +84,7 @@ use crate::timespec::Timespec; use alloc::vec::Vec; use core::ffi::c_void; use core::hash::{Hash, Hasher}; +use core::mem::MaybeUninit; /// `epoll_create1(flags)`—Creates a new epoll object. /// @@ -198,6 +200,9 @@ pub fn delete(epoll: EpollFd, source: SourceFd) - /// [`io::Errno::INVAL`]. Enable the "linux_5_11" feature to enable the full /// range of timeouts. /// +/// This takes a `&mut [Event]` which Rust requires to contain initialized +/// memory. To use an uninitialized buffer, use [`wait_uninit`]. +/// /// # References /// - [Linux] /// - [illumos] @@ -223,6 +228,27 @@ pub fn wait( Ok(()) } +/// `epoll_wait(self, events, timeout)`—Waits for registered events of +/// interest. +/// +/// This is identical to [`wait`], except that it can write the events into +/// uninitialized memory. It returns the slice that was initialized by this +/// function and the slice that remains uninitialized. +#[doc(alias = "epoll_wait")] +#[inline] +pub fn wait_uninit( + epoll: EpollFd, + event_list: &mut [MaybeUninit], + timeout: crate::ffi::c_int, +) -> io::Result<(&mut [Event], &mut [MaybeUninit])> { + // SAFETY: We're calling `epoll_wait` via FFI and we know how it + // behaves. + unsafe { + let nfds = syscalls::epoll_wait(epoll.as_fd(), event_list, timeout)?; + Ok(split_init(event_list, nfds)) + } +} + /// A record of an event that occurred. #[repr(C)] #[cfg_attr(all(not(libc), target_arch = "x86_64"), repr(packed))] diff --git a/tests/event/epoll.rs b/tests/event/epoll.rs index 5a3d3a046..98fb1f21c 100644 --- a/tests/event/epoll.rs +++ b/tests/event/epoll.rs @@ -6,6 +6,7 @@ use rustix::net::{ }; use std::collections::HashMap; use std::ffi::c_void; +use std::mem::MaybeUninit; use std::sync::{Arc, Condvar, Mutex}; use std::thread; @@ -64,6 +65,62 @@ fn server(ready: Arc<(Mutex, Condvar)>) { } } +fn server_uninit(ready: Arc<(Mutex, Condvar)>) { + let listen_sock = socket(AddressFamily::INET, SocketType::STREAM, None).unwrap(); + bind_v4(&listen_sock, &SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).unwrap(); + listen(&listen_sock, 1).unwrap(); + + let who = match getsockname(&listen_sock).unwrap() { + SocketAddrAny::V4(addr) => addr, + _ => panic!(), + }; + + { + let (lock, cvar) = &*ready; + let mut port = lock.lock().unwrap(); + *port = who.port(); + cvar.notify_all(); + } + + let epoll = epoll::create(epoll::CreateFlags::CLOEXEC).unwrap(); + + epoll::add( + &epoll, + &listen_sock, + epoll::EventData::new_u64(1), + epoll::EventFlags::IN, + ) + .unwrap(); + + let mut next_data = epoll::EventData::new_u64(2); + let mut targets = HashMap::new(); + + let mut event_list = [MaybeUninit::uninit(); 4]; + loop { + let (init, _) = epoll::wait_uninit(&epoll, &mut event_list, -1).unwrap(); + for event in init { + let target = event.data; + if target.u64() == 1 { + let conn_sock = accept(&listen_sock).unwrap(); + ioctl_fionbio(&conn_sock, true).unwrap(); + epoll::add( + &epoll, + &conn_sock, + next_data, + epoll::EventFlags::OUT | epoll::EventFlags::ET, + ) + .unwrap(); + targets.insert(next_data, conn_sock); + next_data = epoll::EventData::new_u64(next_data.u64() + 1); + } else { + let target = targets.remove(&target).unwrap(); + write(&target, b"hello\n").unwrap(); + epoll::delete(&epoll, &target).unwrap(); + } + } + } +} + fn client(ready: Arc<(Mutex, Condvar)>) { let port = { let (lock, cvar) = &*ready; @@ -106,6 +163,26 @@ fn test_epoll() { client.join().unwrap(); } +#[test] +fn test_epoll_uninit() { + let ready = Arc::new((Mutex::new(0_u16), Condvar::new())); + let ready_clone = Arc::clone(&ready); + + let _server = thread::Builder::new() + .name("server".to_string()) + .spawn(move || { + server_uninit(ready); + }) + .unwrap(); + let client = thread::Builder::new() + .name("client".to_string()) + .spawn(move || { + client(ready_clone); + }) + .unwrap(); + client.join().unwrap(); +} + #[test] fn test_epoll_event_data() { let d = epoll::EventData::new_u64(0); From 8d9dc617a37c70af4fa34577029c1f77cef8b44a Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 14 Feb 2025 11:21:38 -0800 Subject: [PATCH 2/5] Update for the `Option<&Timespec>` timeout. --- src/event/epoll.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/event/epoll.rs b/src/event/epoll.rs index 483109071..8ea3d5427 100644 --- a/src/event/epoll.rs +++ b/src/event/epoll.rs @@ -78,7 +78,6 @@ use crate::backend::event::syscalls; use crate::buffer::split_init; use crate::fd::{AsFd, OwnedFd}; use crate::io; -#[cfg(feature = "alloc")] use crate::timespec::Timespec; #[cfg(feature = "alloc")] use alloc::vec::Vec; @@ -239,7 +238,7 @@ pub fn wait( pub fn wait_uninit( epoll: EpollFd, event_list: &mut [MaybeUninit], - timeout: crate::ffi::c_int, + timeout: Option<&Timespec>, ) -> io::Result<(&mut [Event], &mut [MaybeUninit])> { // SAFETY: We're calling `epoll_wait` via FFI and we know how it // behaves. From f762331530b171cc01114197cd9cf25637517183 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 14 Feb 2025 11:26:06 -0800 Subject: [PATCH 3/5] Fix lifetimes. --- src/event/epoll.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/event/epoll.rs b/src/event/epoll.rs index 8ea3d5427..a5a01ee1f 100644 --- a/src/event/epoll.rs +++ b/src/event/epoll.rs @@ -235,11 +235,11 @@ pub fn wait( /// function and the slice that remains uninitialized. #[doc(alias = "epoll_wait")] #[inline] -pub fn wait_uninit( +pub fn wait_uninit<'a, EpollFd: AsFd>( epoll: EpollFd, - event_list: &mut [MaybeUninit], + event_list: &'a mut [MaybeUninit], timeout: Option<&Timespec>, -) -> io::Result<(&mut [Event], &mut [MaybeUninit])> { +) -> io::Result<(&'a mut [Event], &'a mut [MaybeUninit])> { // SAFETY: We're calling `epoll_wait` via FFI and we know how it // behaves. unsafe { From 5cdad64e7c7ee9799ddf935ad7fded62b2b49b36 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 14 Feb 2025 11:38:58 -0800 Subject: [PATCH 4/5] Fix test compilation. --- tests/event/epoll.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/event/epoll.rs b/tests/event/epoll.rs index 98fb1f21c..23c4dbd4b 100644 --- a/tests/event/epoll.rs +++ b/tests/event/epoll.rs @@ -97,7 +97,7 @@ fn server_uninit(ready: Arc<(Mutex, Condvar)>) { let mut event_list = [MaybeUninit::uninit(); 4]; loop { - let (init, _) = epoll::wait_uninit(&epoll, &mut event_list, -1).unwrap(); + let (init, _) = epoll::wait_uninit(&epoll, &mut event_list, None).unwrap(); for event in init { let target = event.data; if target.u64() == 1 { From 5db2cd241232a87d0e966be0f1de587a22feb65d Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 14 Feb 2025 11:44:27 -0800 Subject: [PATCH 5/5] More test updates. --- tests/event/epoll.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/event/epoll.rs b/tests/event/epoll.rs index 23c4dbd4b..9aab1090e 100644 --- a/tests/event/epoll.rs +++ b/tests/event/epoll.rs @@ -67,13 +67,10 @@ fn server(ready: Arc<(Mutex, Condvar)>) { fn server_uninit(ready: Arc<(Mutex, Condvar)>) { let listen_sock = socket(AddressFamily::INET, SocketType::STREAM, None).unwrap(); - bind_v4(&listen_sock, &SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).unwrap(); + bind(&listen_sock, &SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).unwrap(); listen(&listen_sock, 1).unwrap(); - let who = match getsockname(&listen_sock).unwrap() { - SocketAddrAny::V4(addr) => addr, - _ => panic!(), - }; + let who = SocketAddrV4::try_from(getsockname(&listen_sock).unwrap()).unwrap(); { let (lock, cvar) = &*ready;