From 8ecd7b0b8ab5fe2ed79574dbe3dd180c45f5c16f Mon Sep 17 00:00:00 2001 From: jtnunley Date: Mon, 19 Dec 2022 14:16:32 -0800 Subject: [PATCH 1/5] Rework epoll API --- .gitignore | 1 + src/backend/libc/io/epoll.rs | 142 ++++++++++---------------- src/backend/linux_raw/io/epoll.rs | 144 ++++++++++---------------- src/io/context.rs | 163 ------------------------------ src/io/mod.rs | 2 - tests/io/epoll.rs | 34 ++++--- 6 files changed, 129 insertions(+), 357 deletions(-) delete mode 100644 src/io/context.rs diff --git a/.gitignore b/.gitignore index a9d37c560..cd23c4561 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +/.vscode diff --git a/src/backend/libc/io/epoll.rs b/src/backend/libc/io/epoll.rs index b221e0fe0..36255f6f1 100644 --- a/src/backend/libc/io/epoll.rs +++ b/src/backend/libc/io/epoll.rs @@ -17,6 +17,7 @@ //! accept, bind_v4, listen, socket, AddressFamily, Ipv4Addr, Protocol, SocketAddrV4, //! SocketType, //! }; +//! use std::collections::HashMap; //! use std::os::unix::io::AsRawFd; //! //! // Create a socket and listen on it. @@ -26,29 +27,35 @@ //! //! // Create an epoll object. Using `Owning` here means the epoll object will //! // take ownership of the file descriptors registered with it. -//! let epoll = Epoll::new(epoll::CreateFlags::CLOEXEC, epoll::Owning::new())?; -//! -//! // Remember the socket raw fd, which we use for comparisons only. -//! let raw_listen_sock = listen_sock.as_fd().as_raw_fd(); +//! let epoll = Epoll::new(epoll::CreateFlags::CLOEXEC)?; //! //! // Register the socket with the epoll object. -//! epoll.add(listen_sock, epoll::EventFlags::IN)?; +//! epoll.add(&listen_sock, 1, epoll::EventFlags::IN)?; +//! +//! // Keep track of the sockets we've opened. +//! let mut next_id = 2; +//! let mut sockets = HashMap::new(); //! //! // Process events. //! let mut event_list = epoll::EventVec::with_capacity(4); //! loop { //! epoll.wait(&mut event_list, -1)?; //! for (_event_flags, target) in &event_list { -//! if target.as_raw_fd() == raw_listen_sock { +//! if target == 1 { //! // Accept a new connection, set it to non-blocking, and //! // register to be notified when it's ready to write to. -//! let conn_sock = accept(&*target)?; +//! let conn_sock = accept(&listen_sock)?; //! ioctl_fionbio(&conn_sock, true)?; -//! epoll.add(conn_sock, epoll::EventFlags::OUT | epoll::EventFlags::ET)?; +//! epoll.add(&conn_sock, next_id, epoll::EventFlags::OUT | epoll::EventFlags::ET)?; +//! +//! // Keep track of the socket. +//! sockets.insert(next_id, conn_sock); +//! next_id += 1; //! } else { //! // Write a message to the stream and then unregister it. -//! write(&*target, b"hello\n")?; -//! let _ = epoll.del(target)?; +//! let target = sockets.remove(&target).unwrap(); +//! write(&target, b"hello\n")?; +//! let _ = epoll.del(&target)?; //! } //! } //! } @@ -66,11 +73,7 @@ use crate::io; use alloc::vec::Vec; use bitflags::bitflags; use core::convert::TryInto; -use core::marker::PhantomData; -use core::ptr::{null, null_mut}; - -#[doc(inline)] -pub use crate::io::context::*; +use core::ptr::null_mut; bitflags! { /// `EPOLL_*` for use with [`Epoll::new`]. @@ -116,25 +119,23 @@ bitflags! { /// An "epoll", an interface to an OS object allowing one to repeatedly wait /// for events from a set of file descriptors efficiently. -pub struct Epoll { +pub struct Epoll { epoll_fd: OwnedFd, - context: Context, } -impl Epoll { +impl Epoll { /// `epoll_create1(flags)`—Creates a new `Epoll`. /// /// Use the [`CreateFlags::CLOEXEC`] flag to prevent the resulting file /// descriptor from being implicitly passed across `exec` boundaries. #[inline] #[doc(alias = "epoll_create1")] - pub fn new(flags: CreateFlags, context: Context) -> io::Result { + pub fn new(flags: CreateFlags) -> io::Result { // Safety: We're calling `epoll_create1` via FFI and we know how it // behaves. unsafe { Ok(Self { epoll_fd: ret_owned_fd(c::epoll_create1(flags.bits()))?, - context, }) } } @@ -145,27 +146,20 @@ impl Epoll { /// This registers interest in any of the events set in `events` occurring /// on the file descriptor associated with `data`. #[doc(alias = "epoll_ctl")] - pub fn add( - &self, - data: Context::Data, - event_flags: EventFlags, - ) -> io::Result> { + pub fn add(&self, source: &impl AsFd, data: u64, event_flags: EventFlags) -> io::Result<()> { // Safety: We're calling `epoll_ctl` via FFI and we know how it // behaves. unsafe { - let target = self.context.acquire(data); - let raw_fd = target.as_fd().as_raw_fd(); - let encoded = self.context.encode(target); + let raw_fd = source.as_fd().as_raw_fd(); ret(c::epoll_ctl( self.epoll_fd.as_fd().as_raw_fd(), c::EPOLL_CTL_ADD, raw_fd, &mut c::epoll_event { events: event_flags.bits(), - r#u64: encoded, + r#u64: data, }, - ))?; - Ok(self.context.decode(encoded)) + )) } } @@ -174,13 +168,9 @@ impl Epoll { /// /// This sets the events of interest with `target` to `events`. #[doc(alias = "epoll_ctl")] - pub fn mod_( - &self, - target: Ref<'_, Context::Target>, - event_flags: EventFlags, - ) -> io::Result<()> { - let raw_fd = target.as_fd().as_raw_fd(); - let encoded = self.context.encode(target); + pub fn mod_(&self, source: &impl AsFd, data: u64, event_flags: EventFlags) -> io::Result<()> { + let raw_fd = source.as_fd().as_raw_fd(); + // Safety: We're calling `epoll_ctl` via FFI and we know how it // behaves. unsafe { @@ -190,7 +180,7 @@ impl Epoll { raw_fd, &mut c::epoll_event { events: event_flags.bits(), - r#u64: encoded, + r#u64: data, }, )) } @@ -201,19 +191,18 @@ impl Epoll { /// /// This also returns the owning `Data`. #[doc(alias = "epoll_ctl")] - pub fn del(&self, target: Ref<'_, Context::Target>) -> io::Result { + pub fn del(&self, source: &impl AsFd) -> io::Result<()> { // Safety: We're calling `epoll_ctl` via FFI and we know how it // behaves. unsafe { - let raw_fd = target.as_fd().as_raw_fd(); + let raw_fd = source.as_fd().as_raw_fd(); ret(c::epoll_ctl( self.epoll_fd.as_fd().as_raw_fd(), c::EPOLL_CTL_DEL, raw_fd, null_mut(), - ))?; + )) } - Ok(self.context.release(target)) } /// `epoll_wait(self, events, timeout)`—Waits for registered events of @@ -222,11 +211,7 @@ impl Epoll { /// For each event of interest, an element is written to `events`. On /// success, this returns the number of written elements. #[doc(alias = "epoll_wait")] - pub fn wait<'context>( - &'context self, - event_list: &mut EventVec<'context, Context>, - timeout: c::c_int, - ) -> io::Result<()> { + pub fn wait(&self, event_list: &mut EventVec, timeout: c::c_int) -> io::Result<()> { // Safety: We're calling `epoll_wait` via FFI and we know how it // behaves. unsafe { @@ -238,7 +223,6 @@ impl Epoll { timeout, ))?; event_list.events.set_len(nfds as usize); - event_list.context = &self.context; } Ok(()) @@ -246,76 +230,64 @@ impl Epoll { } #[cfg(not(feature = "rustc-dep-of-std"))] -impl<'context, T: AsFd + Into + From> AsRawFd for Epoll> { +impl AsRawFd for Epoll { fn as_raw_fd(&self) -> RawFd { self.epoll_fd.as_raw_fd() } } #[cfg(not(feature = "rustc-dep-of-std"))] -impl<'context, T: AsFd + Into + From> IntoRawFd for Epoll> { +impl IntoRawFd for Epoll { fn into_raw_fd(self) -> RawFd { self.epoll_fd.into_raw_fd() } } #[cfg(not(feature = "rustc-dep-of-std"))] -impl<'context, T: AsFd + Into + From> FromRawFd for Epoll> { +impl FromRawFd for Epoll { unsafe fn from_raw_fd(fd: RawFd) -> Self { Self { epoll_fd: OwnedFd::from_raw_fd(fd), - context: Owning::new(), } } } #[cfg(not(feature = "rustc-dep-of-std"))] -impl<'context, T: AsFd + Into + From> AsFd for Epoll> { +impl AsFd for Epoll { fn as_fd(&self) -> BorrowedFd<'_> { self.epoll_fd.as_fd() } } #[cfg(not(feature = "rustc-dep-of-std"))] -impl<'context, T: AsFd + Into + From> From>> - for OwnedFd -{ - fn from(epoll: Epoll>) -> Self { +impl From for OwnedFd { + fn from(epoll: Epoll) -> Self { epoll.epoll_fd } } #[cfg(not(feature = "rustc-dep-of-std"))] -impl<'context, T: AsFd + Into + From> From - for Epoll> -{ +impl From for Epoll { fn from(fd: OwnedFd) -> Self { - Self { - epoll_fd: fd, - context: Owning::new(), - } + Self { epoll_fd: fd } } } /// An iterator over the `Event`s in an `EventVec`. -pub struct Iter<'context, Context: self::Context> { - iter: core::slice::Iter<'context, Event>, - context: *const Context, - _phantom: PhantomData<&'context Context>, +pub struct Iter<'a> { + iter: core::slice::Iter<'a, Event>, } -impl<'context, Context: self::Context> Iterator for Iter<'context, Context> { - type Item = (EventFlags, Ref<'context, Context::Target>); +impl<'a> Iterator for Iter<'a> { + type Item = (EventFlags, u64); fn next(&mut self) -> Option { // Safety: `self.context` is guaranteed to be valid because we hold // `'context` for it. And we know this event is associated with this // context because `wait` sets both. - self.iter.next().map(|event| { - (event.event_flags, unsafe { - (*self.context).decode(event.encoded) - }) - }) + self.iter + .next() + .map(|event| (event.event_flags, event.encoded)) } } @@ -341,20 +313,16 @@ struct Event { } /// A vector of `Event`s, plus context for interpreting them. -pub struct EventVec<'context, Context: self::Context> { +pub struct EventVec { events: Vec, - context: *const Context, - _phantom: PhantomData<&'context Context>, } -impl<'context, Context: self::Context> EventVec<'context, Context> { +impl EventVec { /// Constructs an `EventVec` with memory for `capacity` `Event`s. #[inline] pub fn with_capacity(capacity: usize) -> Self { Self { events: Vec::with_capacity(capacity), - context: null(), - _phantom: PhantomData, } } @@ -390,11 +358,9 @@ impl<'context, Context: self::Context> EventVec<'context, Context> { /// Returns an iterator over the `Event`s in this `EventVec`. #[inline] - pub fn iter(&self) -> Iter<'_, Context> { + pub fn iter(&self) -> Iter<'_> { Iter { iter: self.events.iter(), - context: self.context, - _phantom: PhantomData, } } @@ -411,9 +377,9 @@ impl<'context, Context: self::Context> EventVec<'context, Context> { } } -impl<'context, Context: self::Context> IntoIterator for &'context EventVec<'context, Context> { - type IntoIter = Iter<'context, Context>; - type Item = (EventFlags, Ref<'context, Context::Target>); +impl<'a> IntoIterator for &'a EventVec { + type IntoIter = Iter<'a>; + type Item = (EventFlags, u64); #[inline] fn into_iter(self) -> Self::IntoIter { diff --git a/src/backend/linux_raw/io/epoll.rs b/src/backend/linux_raw/io/epoll.rs index 1f59ddb02..29b408d2a 100644 --- a/src/backend/linux_raw/io/epoll.rs +++ b/src/backend/linux_raw/io/epoll.rs @@ -17,6 +17,7 @@ //! accept, bind_v4, listen, socket, AddressFamily, Ipv4Addr, Protocol, SocketAddrV4, //! SocketType, //! }; +//! use std::collections::HashMap; //! use std::os::unix::io::AsRawFd; //! //! // Create a socket and listen on it. @@ -26,29 +27,35 @@ //! //! // Create an epoll object. Using `Owning` here means the epoll object will //! // take ownership of the file descriptors registered with it. -//! let epoll = Epoll::new(epoll::CreateFlags::CLOEXEC, epoll::Owning::new())?; -//! -//! // Remember the socket raw fd, which we use for comparisons only. -//! let raw_listen_sock = listen_sock.as_fd().as_raw_fd(); +//! let epoll = Epoll::new(epoll::CreateFlags::CLOEXEC)?; //! //! // Register the socket with the epoll object. -//! epoll.add(listen_sock, epoll::EventFlags::IN)?; +//! epoll.add(&listen_sock, 1, epoll::EventFlags::IN)?; +//! +//! // Keep track of the sockets we've opened. +//! let mut next_id = 2; +//! let mut sockets = HashMap::new(); //! //! // Process events. //! let mut event_list = epoll::EventVec::with_capacity(4); //! loop { //! epoll.wait(&mut event_list, -1)?; //! for (_event_flags, target) in &event_list { -//! if target.as_raw_fd() == raw_listen_sock { +//! if target == 1 { //! // Accept a new connection, set it to non-blocking, and //! // register to be notified when it's ready to write to. -//! let conn_sock = accept(&*target)?; +//! let conn_sock = accept(&listen_sock)?; //! ioctl_fionbio(&conn_sock, true)?; -//! epoll.add(conn_sock, epoll::EventFlags::OUT | epoll::EventFlags::ET)?; +//! epoll.add(&conn_sock, next_id, epoll::EventFlags::OUT | epoll::EventFlags::ET)?; +//! +//! // Keep track of the socket. +//! sockets.insert(next_id, conn_sock); +//! next_id += 1; //! } else { //! // Write a message to the stream and then unregister it. -//! write(&*target, b"hello\n")?; -//! let _ = epoll.del(target)?; +//! let target = sockets.remove(&target).unwrap(); +//! write(&target, b"hello\n")?; +//! let _ = epoll.del(&target)?; //! } //! } //! } @@ -67,11 +74,6 @@ use crate::fd::{BorrowedFd, FromRawFd, IntoRawFd, RawFd}; use crate::io; use alloc::vec::Vec; use bitflags::bitflags; -use core::marker::PhantomData; -use core::ptr::null; - -#[doc(inline)] -pub use crate::io::context::*; bitflags! { /// `EPOLL_*` for use with [`Epoll::new`]. @@ -116,24 +118,22 @@ bitflags! { /// An "epoll", an interface to an OS object allowing one to repeatedly wait /// for events from a set of file descriptors efficiently. -pub struct Epoll { +pub struct Epoll { epoll_fd: OwnedFd, - context: Context, } -impl Epoll { +impl Epoll { /// `epoll_create1(flags)`—Creates a new `Epoll`. /// /// Use the [`CreateFlags::CLOEXEC`] flag to prevent the resulting file /// descriptor from being implicitly passed across `exec` boundaries. #[inline] #[doc(alias = "epoll_create1")] - pub fn new(flags: CreateFlags, context: Context) -> io::Result { + pub fn new(flags: CreateFlags) -> io::Result { // Safety: We're calling `epoll_create1` via FFI and we know how it // behaves. Ok(Self { epoll_fd: epoll_create(flags)?, - context, }) } @@ -143,26 +143,18 @@ impl Epoll { /// This registers interest in any of the events set in `events` occurring /// on the file descriptor associated with `data`. #[doc(alias = "epoll_ctl")] - pub fn add( - &self, - data: Context::Data, - event_flags: EventFlags, - ) -> io::Result> { + pub fn add(&self, source: &impl AsFd, data: u64, event_flags: EventFlags) -> io::Result<()> { // Safety: We're calling `epoll_ctl` via FFI and we know how it // behaves. unsafe { - let target = self.context.acquire(data); - let raw_fd = target.as_fd().as_raw_fd(); - let encoded = self.context.encode(target); epoll_add( self.epoll_fd.as_fd(), - raw_fd, + source.as_fd().as_raw_fd(), &linux_raw_sys::general::epoll_event { events: event_flags.bits(), - data: encoded, + data, }, - )?; - Ok(self.context.decode(encoded)) + ) } } @@ -171,22 +163,17 @@ impl Epoll { /// /// This sets the events of interest with `target` to `events`. #[doc(alias = "epoll_ctl")] - pub fn mod_( - &self, - target: Ref<'_, Context::Target>, - event_flags: EventFlags, - ) -> io::Result<()> { - let raw_fd = target.as_fd().as_raw_fd(); - let encoded = self.context.encode(target); + pub fn mod_(&self, source: &impl AsFd, data: u64, event_flags: EventFlags) -> io::Result<()> { // Safety: We're calling `epoll_ctl` via FFI and we know how it // behaves. unsafe { + let raw_fd = source.as_fd().as_raw_fd(); epoll_mod( self.epoll_fd.as_fd(), raw_fd, &linux_raw_sys::general::epoll_event { events: event_flags.bits(), - data: encoded, + data, }, ) } @@ -197,14 +184,13 @@ impl Epoll { /// /// This also returns the owning `Data`. #[doc(alias = "epoll_ctl")] - pub fn del(&self, target: Ref<'_, Context::Target>) -> io::Result { + pub fn del(&self, source: &impl AsFd) -> io::Result<()> { // Safety: We're calling `epoll_ctl` via FFI and we know how it // behaves. unsafe { - let raw_fd = target.as_fd().as_raw_fd(); - epoll_del(self.epoll_fd.as_fd(), raw_fd)?; + let raw_fd = source.as_fd().as_raw_fd(); + epoll_del(self.epoll_fd.as_fd(), raw_fd) } - Ok(self.context.release(target)) } /// `epoll_wait(self, events, timeout)`—Waits for registered events of @@ -213,11 +199,7 @@ impl Epoll { /// For each event of interest, an element is written to `events`. On /// success, this returns the number of written elements. #[doc(alias = "epoll_wait")] - pub fn wait<'context>( - &'context self, - event_list: &mut EventVec<'context, Context>, - timeout: c::c_int, - ) -> io::Result<()> { + pub fn wait(&self, event_list: &mut EventVec, timeout: c::c_int) -> io::Result<()> { // Safety: We're calling `epoll_wait` via FFI and we know how it // behaves. unsafe { @@ -229,7 +211,6 @@ impl Epoll { timeout, )?; event_list.events.set_len(nfds); - event_list.context = &self.context; } Ok(()) @@ -237,76 +218,61 @@ impl Epoll { } #[cfg(feature = "std")] -impl<'context, T: AsFd + Into + From> AsRawFd for Epoll> { +impl AsRawFd for Epoll { fn as_raw_fd(&self) -> RawFd { self.epoll_fd.as_raw_fd() } } #[cfg(feature = "std")] -impl<'context, T: AsFd + Into + From> IntoRawFd for Epoll> { +impl IntoRawFd for Epoll { fn into_raw_fd(self) -> RawFd { self.epoll_fd.into_raw_fd() } } #[cfg(feature = "std")] -impl<'context, T: AsFd + Into + From> FromRawFd for Epoll> { +impl FromRawFd for Epoll { unsafe fn from_raw_fd(fd: RawFd) -> Self { Self { epoll_fd: OwnedFd::from_raw_fd(fd), - context: Owning::new(), } } } #[cfg(feature = "std")] -impl<'context, T: AsFd + Into + From> AsFd for Epoll> { +impl AsFd for Epoll { fn as_fd(&self) -> BorrowedFd<'_> { self.epoll_fd.as_fd() } } #[cfg(feature = "std")] -impl<'context, T: AsFd + Into + From> From>> - for OwnedFd -{ - fn from(epoll: Epoll>) -> Self { +impl From for OwnedFd { + fn from(epoll: Epoll) -> Self { epoll.epoll_fd } } #[cfg(feature = "std")] -impl<'context, T: AsFd + Into + From> From - for Epoll> -{ +impl From for Epoll { fn from(fd: OwnedFd) -> Self { - Self { - epoll_fd: fd, - context: Owning::new(), - } + Self { epoll_fd: fd } } } /// An iterator over the `Event`s in an `EventVec`. -pub struct Iter<'context, Context: self::Context> { - iter: core::slice::Iter<'context, Event>, - context: *const Context, - _phantom: PhantomData<&'context Context>, +pub struct Iter<'a> { + iter: core::slice::Iter<'a, Event>, } -impl<'context, Context: self::Context> Iterator for Iter<'context, Context> { - type Item = (EventFlags, Ref<'context, Context::Target>); +impl<'a> Iterator for Iter<'a> { + type Item = (EventFlags, u64); fn next(&mut self) -> Option { - self.iter.next().map(|event| { - // Safety: `self.context` is guaranteed to be valid because we hold - // `'context` for it. And we know this event is associated with this - // context because `wait` sets both. - let decoded = unsafe { (*self.context).decode(event.encoded) }; - - (event.event_flags, decoded) - }) + self.iter + .next() + .map(|event| (event.event_flags, event.encoded)) } } @@ -323,20 +289,16 @@ struct Event { } /// A vector of `Event`s, plus context for interpreting them. -pub struct EventVec<'context, Context: self::Context> { +pub struct EventVec { events: Vec, - context: *const Context, - _phantom: PhantomData<&'context Context>, } -impl<'context, Context: self::Context> EventVec<'context, Context> { +impl EventVec { /// Constructs an `EventVec` with memory for `capacity` `Event`s. #[inline] pub fn with_capacity(capacity: usize) -> Self { Self { events: Vec::with_capacity(capacity), - context: null(), - _phantom: PhantomData, } } @@ -372,11 +334,9 @@ impl<'context, Context: self::Context> EventVec<'context, Context> { /// Returns an iterator over the `Event`s in this `EventVec`. #[inline] - pub fn iter(&self) -> Iter<'_, Context> { + pub fn iter(&self) -> Iter<'_> { Iter { iter: self.events.iter(), - context: self.context, - _phantom: PhantomData, } } @@ -393,9 +353,9 @@ impl<'context, Context: self::Context> EventVec<'context, Context> { } } -impl<'context, Context: self::Context> IntoIterator for &'context EventVec<'context, Context> { - type IntoIter = Iter<'context, Context>; - type Item = (EventFlags, Ref<'context, Context::Target>); +impl<'a> IntoIterator for &'a EventVec { + type IntoIter = Iter<'a>; + type Item = (EventFlags, u64); #[inline] fn into_iter(self) -> Self::IntoIter { diff --git a/src/io/context.rs b/src/io/context.rs deleted file mode 100644 index cf2a1a89a..000000000 --- a/src/io/context.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! Context types for polling systems, e.g. kqueue and epoll. - -#![allow(unsafe_code)] - -use crate::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; - -use core::fmt; -use core::marker::PhantomData; -use core::ops::Deref; - -/// A reference to a `T`. -pub struct Ref<'a, T> { - t: T, - _phantom: PhantomData<&'a T>, -} - -impl<'a, T> Ref<'a, T> { - #[inline] - fn new(t: T) -> Self { - Self { - t, - _phantom: PhantomData, - } - } - - #[inline] - fn consume(self) -> T { - self.t - } -} - -impl<'a, T> Deref for Ref<'a, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - &self.t - } -} - -impl<'a, T: fmt::Debug> fmt::Debug for Ref<'a, T> { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - self.t.fmt(fmt) - } -} - -/// A trait for data stored within an [`Epoll`] instance. -/// -/// [`Epoll`]: crate::io::epoll::Epoll -pub trait Context { - /// The type of an element owned by this context. - type Data; - - /// The type of a value used to refer to an element owned by this context. - type Target: AsFd; - - /// Assume ownership of `data`, and returning a `Target`. - fn acquire<'call>(&self, data: Self::Data) -> Ref<'call, Self::Target>; - - /// Encode `target` as a `u64`. The only requirement on this value is that - /// it be decodable by `decode`. - fn encode(&self, target: Ref<'_, Self::Target>) -> u64; - - /// Decode `raw`, which is a value encoded by `encode`, into a `Target`. - /// - /// # Safety - /// - /// `raw` must be a `u64` value returned from `encode`, from the same - /// context, and within the context's lifetime. - unsafe fn decode<'call>(&self, raw: u64) -> Ref<'call, Self::Target>; - - /// Release ownership of the value referred to by `target` and return it. - fn release(&self, target: Ref<'_, Self::Target>) -> Self::Data; -} - -/// A type implementing [`Context`] where the `Data` type is `BorrowedFd<'a>`. -pub struct Borrowing<'a> { - _phantom: PhantomData>, -} - -impl<'a> Context for Borrowing<'a> { - type Data = BorrowedFd<'a>; - type Target = BorrowedFd<'a>; - - #[inline] - fn acquire<'call>(&self, data: Self::Data) -> Ref<'call, Self::Target> { - Ref::new(data) - } - - #[inline] - fn encode(&self, target: Ref<'_, Self::Target>) -> u64 { - target.as_raw_fd() as u64 - } - - #[inline] - unsafe fn decode<'call>(&self, raw: u64) -> Ref<'call, Self::Target> { - Ref::new(BorrowedFd::<'a>::borrow_raw(raw as RawFd)) - } - - #[inline] - fn release(&self, target: Ref<'_, Self::Target>) -> Self::Data { - target.consume() - } -} - -/// A type implementing [`Context`] where the `Data` type is `T`, a type -/// implementing `From` and `From for OwnedFd`. -/// -/// This may be used with [`OwnedFd`], or higher-level types like -/// [`std::fs::File`] or [`std::net::TcpStream`]. -#[cfg(not(feature = "rustc-dep-of-std"))] -pub struct Owning<'context, T: Into + From> { - _phantom: PhantomData<&'context T>, -} - -#[cfg(not(feature = "rustc-dep-of-std"))] -impl<'context, T: Into + From> Owning<'context, T> { - /// Creates a new empty `Owning`. - #[allow(clippy::new_without_default)] // This is a specialized type that doesn't need to be generically constructible. - #[inline] - pub fn new() -> Self { - Self { - _phantom: PhantomData, - } - } -} - -#[cfg(not(feature = "rustc-dep-of-std"))] -impl<'context, T: AsFd + Into + From> Context for Owning<'context, T> { - type Data = T; - type Target = BorrowedFd<'context>; - - #[inline] - fn acquire<'call>(&self, data: Self::Data) -> Ref<'call, Self::Target> { - let fd: OwnedFd = data.into(); - let raw_fd = fd.into_raw_fd(); - // Safety: `epoll` will assign ownership of the file descriptor to the - // kernel epoll object. We use `Into`+`IntoRawFd` to consume - // the `Data` and extract the raw file descriptor and then "borrow" it - // with `borrow_raw` knowing that the borrow won't outlive the - // kernel epoll object. - unsafe { Ref::new(BorrowedFd::<'context>::borrow_raw(raw_fd)) } - } - - #[inline] - fn encode(&self, target: Ref<'_, Self::Target>) -> u64 { - target.as_fd().as_raw_fd() as u64 - } - - #[inline] - unsafe fn decode<'call>(&self, raw: u64) -> Ref<'call, Self::Target> { - Ref::new(BorrowedFd::<'context>::borrow_raw(raw as RawFd)) - } - - #[inline] - fn release(&self, target: Ref<'_, Self::Target>) -> Self::Data { - // The file descriptor was held by the kernel epoll object and is now - // being released, so we can create a new `OwnedFd` that assumes - // ownership. - let raw_fd = target.consume().as_raw_fd(); - unsafe { T::from(OwnedFd::from_raw_fd(raw_fd).into()) } - } -} diff --git a/src/io/mod.rs b/src/io/mod.rs index cd29a3a33..03f06c4ac 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -1,8 +1,6 @@ //! I/O operations. mod close; -#[cfg(any(target_os = "android", target_os = "linux"))] -pub(crate) mod context; #[cfg(not(windows))] mod dup; mod errno; diff --git a/tests/io/epoll.rs b/tests/io/epoll.rs index 87090a6c8..da6c24766 100644 --- a/tests/io/epoll.rs +++ b/tests/io/epoll.rs @@ -1,13 +1,14 @@ #![cfg(any(target_os = "android", target_os = "linux"))] -use rustix::fd::{AsFd, OwnedFd}; +use rustix::fd::OwnedFd; use rustix::io::epoll::{self, Epoll}; use rustix::io::{ioctl_fionbio, read, write}; use rustix::net::{ accept, bind_v4, connect_v4, getsockname, listen, socket, AddressFamily, Ipv4Addr, Protocol, SocketAddrAny, SocketAddrV4, SocketType, }; -use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; +use std::collections::HashMap; +use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd}; use std::sync::{Arc, Condvar, Mutex}; use std::thread; @@ -30,30 +31,39 @@ fn server(ready: Arc<(Mutex, Condvar)>) { cvar.notify_all(); } - let epoll = Epoll::new(epoll::CreateFlags::CLOEXEC, epoll::Owning::::new()).unwrap(); + let epoll = Epoll::new(epoll::CreateFlags::CLOEXEC).unwrap(); // Test into conversions. let fd: OwnedFd = epoll.into(); - let epoll: Epoll> = fd.into(); + let epoll: Epoll = fd.into(); let fd: RawFd = epoll.into_raw_fd(); - let epoll = unsafe { Epoll::>::from_raw_fd(fd) }; + let epoll = unsafe { Epoll::from_raw_fd(fd) }; - let raw_listen_sock = listen_sock.as_fd().as_raw_fd(); - epoll.add(listen_sock, epoll::EventFlags::IN).unwrap(); + epoll.add(&listen_sock, 1, epoll::EventFlags::IN).unwrap(); + + let mut next_data = 2; + let mut targets = HashMap::new(); let mut event_list = epoll::EventVec::with_capacity(4); loop { epoll.wait(&mut event_list, -1).unwrap(); for (_event_flags, target) in &event_list { - if target.as_raw_fd() == raw_listen_sock { - let conn_sock = accept(&*target).unwrap(); + if target == 1 { + let conn_sock = accept(&listen_sock).unwrap(); ioctl_fionbio(&conn_sock, true).unwrap(); epoll - .add(conn_sock, epoll::EventFlags::OUT | epoll::EventFlags::ET) + .add( + &conn_sock, + next_data, + epoll::EventFlags::OUT | epoll::EventFlags::ET, + ) .unwrap(); + targets.insert(next_data, conn_sock); + next_data += 1; } else { - write(&*target, b"hello\n").unwrap(); - let _ = epoll.del(target).unwrap(); + let target = targets.remove(&target).unwrap(); + write(&target, b"hello\n").unwrap(); + epoll.del(&target).unwrap(); } } } From 011390e1f025c8accdb58e500bba39f313e7f2d6 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Thu, 22 Dec 2022 10:47:24 -0800 Subject: [PATCH 2/5] Remove the `Epoll` type --- src/backend/libc/io/epoll.rs | 252 ++++++++++++------------------ src/backend/linux_raw/io/epoll.rs | 237 ++++++++++++---------------- tests/io/epoll.rs | 32 ++-- 3 files changed, 209 insertions(+), 312 deletions(-) diff --git a/src/backend/libc/io/epoll.rs b/src/backend/libc/io/epoll.rs index 36255f6f1..e7112f893 100644 --- a/src/backend/libc/io/epoll.rs +++ b/src/backend/libc/io/epoll.rs @@ -11,7 +11,7 @@ //! # #[cfg(feature = "net")] //! # fn main() -> std::io::Result<()> { //! use io_lifetimes::AsFd; -//! use rustix::io::epoll::{self, Epoll}; +//! use rustix::io::epoll; //! use rustix::io::{ioctl_fionbio, read, write}; //! use rustix::net::{ //! accept, bind_v4, listen, socket, AddressFamily, Ipv4Addr, Protocol, SocketAddrV4, @@ -27,10 +27,10 @@ //! //! // Create an epoll object. Using `Owning` here means the epoll object will //! // take ownership of the file descriptors registered with it. -//! let epoll = Epoll::new(epoll::CreateFlags::CLOEXEC)?; +//! let epoll = epoll::epoll_create(epoll::CreateFlags::CLOEXEC)?; //! //! // Register the socket with the epoll object. -//! epoll.add(&listen_sock, 1, epoll::EventFlags::IN)?; +//! epoll::epoll_add(&epoll, &listen_sock, 1, epoll::EventFlags::IN)?; //! //! // Keep track of the sockets we've opened. //! let mut next_id = 2; @@ -39,14 +39,14 @@ //! // Process events. //! let mut event_list = epoll::EventVec::with_capacity(4); //! loop { -//! epoll.wait(&mut event_list, -1)?; +//! epoll::epoll_wait(&epoll, &mut event_list, -1)?; //! for (_event_flags, target) in &event_list { //! if target == 1 { //! // Accept a new connection, set it to non-blocking, and //! // register to be notified when it's ready to write to. //! let conn_sock = accept(&listen_sock)?; //! ioctl_fionbio(&conn_sock, true)?; -//! epoll.add(&conn_sock, next_id, epoll::EventFlags::OUT | epoll::EventFlags::ET)?; +//! epoll::epoll_add(&epoll, &conn_sock, next_id, epoll::EventFlags::OUT | epoll::EventFlags::ET)?; //! //! // Keep track of the socket. //! sockets.insert(next_id, conn_sock); @@ -55,7 +55,7 @@ //! // Write a message to the stream and then unregister it. //! let target = sockets.remove(&target).unwrap(); //! write(&target, b"hello\n")?; -//! let _ = epoll.del(&target)?; +//! let _ = epoll::epoll_del(&epoll, &target)?; //! } //! } //! } @@ -66,9 +66,7 @@ use super::super::c; use super::super::conv::{ret, ret_owned_fd, ret_u32}; -use crate::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}; -#[cfg(not(feature = "rustc-dep-of-std"))] -use crate::fd::{FromRawFd, IntoRawFd}; +use crate::fd::{AsFd, AsRawFd, OwnedFd}; use crate::io; use alloc::vec::Vec; use bitflags::bitflags; @@ -117,160 +115,112 @@ bitflags! { } } -/// An "epoll", an interface to an OS object allowing one to repeatedly wait -/// for events from a set of file descriptors efficiently. -pub struct Epoll { - epoll_fd: OwnedFd, +/// `epoll_create1(flags)`—Creates a new `Epoll`. +/// +/// Use the [`CreateFlags::CLOEXEC`] flag to prevent the resulting file +/// descriptor from being implicitly passed across `exec` boundaries. +#[inline] +#[doc(alias = "epoll_create1")] +pub fn epoll_create(flags: CreateFlags) -> io::Result { + // Safety: We're calling `epoll_create1` via FFI and we know how it + // behaves. + unsafe { ret_owned_fd(c::epoll_create1(flags.bits())) } } -impl Epoll { - /// `epoll_create1(flags)`—Creates a new `Epoll`. - /// - /// Use the [`CreateFlags::CLOEXEC`] flag to prevent the resulting file - /// descriptor from being implicitly passed across `exec` boundaries. - #[inline] - #[doc(alias = "epoll_create1")] - pub fn new(flags: CreateFlags) -> io::Result { - // Safety: We're calling `epoll_create1` via FFI and we know how it - // behaves. - unsafe { - Ok(Self { - epoll_fd: ret_owned_fd(c::epoll_create1(flags.bits()))?, - }) - } - } - - /// `epoll_ctl(self, EPOLL_CTL_ADD, data, event)`—Adds an element to an - /// `Epoll`. - /// - /// This registers interest in any of the events set in `events` occurring - /// on the file descriptor associated with `data`. - #[doc(alias = "epoll_ctl")] - pub fn add(&self, source: &impl AsFd, data: u64, event_flags: EventFlags) -> io::Result<()> { - // Safety: We're calling `epoll_ctl` via FFI and we know how it - // behaves. - unsafe { - let raw_fd = source.as_fd().as_raw_fd(); - ret(c::epoll_ctl( - self.epoll_fd.as_fd().as_raw_fd(), - c::EPOLL_CTL_ADD, - raw_fd, - &mut c::epoll_event { - events: event_flags.bits(), - r#u64: data, - }, - )) - } - } - - /// `epoll_ctl(self, EPOLL_CTL_MOD, target, event)`—Modifies an element in - /// this `Epoll`. - /// - /// This sets the events of interest with `target` to `events`. - #[doc(alias = "epoll_ctl")] - pub fn mod_(&self, source: &impl AsFd, data: u64, event_flags: EventFlags) -> io::Result<()> { +/// `epoll_ctl(self, EPOLL_CTL_ADD, data, event)`—Adds an element to an +/// `Epoll`. +#[doc(alias = "epoll_ctl")] +pub fn epoll_add( + epoll: impl AsFd, + source: impl AsFd, + data: u64, + event_flags: EventFlags, +) -> io::Result<()> { + // Safety: We're calling `epoll_ctl` via FFI and we know how it + // behaves. + unsafe { let raw_fd = source.as_fd().as_raw_fd(); - - // Safety: We're calling `epoll_ctl` via FFI and we know how it - // behaves. - unsafe { - ret(c::epoll_ctl( - self.epoll_fd.as_fd().as_raw_fd(), - c::EPOLL_CTL_MOD, - raw_fd, - &mut c::epoll_event { - events: event_flags.bits(), - r#u64: data, - }, - )) - } - } - - /// `epoll_ctl(self, EPOLL_CTL_DEL, target, NULL)`—Removes an element in - /// this `Epoll`. - /// - /// This also returns the owning `Data`. - #[doc(alias = "epoll_ctl")] - pub fn del(&self, source: &impl AsFd) -> io::Result<()> { - // Safety: We're calling `epoll_ctl` via FFI and we know how it - // behaves. - unsafe { - let raw_fd = source.as_fd().as_raw_fd(); - ret(c::epoll_ctl( - self.epoll_fd.as_fd().as_raw_fd(), - c::EPOLL_CTL_DEL, - raw_fd, - null_mut(), - )) - } - } - - /// `epoll_wait(self, events, timeout)`—Waits for registered events of - /// interest. - /// - /// For each event of interest, an element is written to `events`. On - /// success, this returns the number of written elements. - #[doc(alias = "epoll_wait")] - pub fn wait(&self, event_list: &mut EventVec, timeout: c::c_int) -> io::Result<()> { - // Safety: We're calling `epoll_wait` via FFI and we know how it - // behaves. - unsafe { - event_list.events.set_len(0); - let nfds = ret_u32(c::epoll_wait( - self.epoll_fd.as_fd().as_raw_fd(), - event_list.events.as_mut_ptr().cast::(), - event_list.events.capacity().try_into().unwrap_or(i32::MAX), - timeout, - ))?; - event_list.events.set_len(nfds as usize); - } - - Ok(()) - } -} - -#[cfg(not(feature = "rustc-dep-of-std"))] -impl AsRawFd for Epoll { - fn as_raw_fd(&self) -> RawFd { - self.epoll_fd.as_raw_fd() - } -} - -#[cfg(not(feature = "rustc-dep-of-std"))] -impl IntoRawFd for Epoll { - fn into_raw_fd(self) -> RawFd { - self.epoll_fd.into_raw_fd() + ret(c::epoll_ctl( + epoll.as_fd().as_raw_fd(), + c::EPOLL_CTL_ADD, + raw_fd, + &mut c::epoll_event { + events: event_flags.bits(), + r#u64: data, + }, + )) } } -#[cfg(not(feature = "rustc-dep-of-std"))] -impl FromRawFd for Epoll { - unsafe fn from_raw_fd(fd: RawFd) -> Self { - Self { - epoll_fd: OwnedFd::from_raw_fd(fd), - } +/// `epoll_ctl(self, EPOLL_CTL_MOD, target, event)`—Modifies an element in +/// this `Epoll`. +/// +/// This sets the events of interest with `target` to `events`. +#[doc(alias = "epoll_ctl")] +pub fn epoll_mod( + epoll: impl AsFd, + source: impl AsFd, + data: u64, + event_flags: EventFlags, +) -> io::Result<()> { + let raw_fd = source.as_fd().as_raw_fd(); + + // Safety: We're calling `epoll_ctl` via FFI and we know how it + // behaves. + unsafe { + ret(c::epoll_ctl( + epoll.as_fd().as_raw_fd(), + c::EPOLL_CTL_MOD, + raw_fd, + &mut c::epoll_event { + events: event_flags.bits(), + r#u64: data, + }, + )) } } -#[cfg(not(feature = "rustc-dep-of-std"))] -impl AsFd for Epoll { - fn as_fd(&self) -> BorrowedFd<'_> { - self.epoll_fd.as_fd() +/// `epoll_ctl(self, EPOLL_CTL_DEL, target, NULL)`—Removes an element in +/// this `Epoll`. +#[doc(alias = "epoll_ctl")] +pub fn epoll_del(epoll: impl AsFd, source: impl AsFd) -> io::Result<()> { + // Safety: We're calling `epoll_ctl` via FFI and we know how it + // behaves. + unsafe { + let raw_fd = source.as_fd().as_raw_fd(); + ret(c::epoll_ctl( + epoll.as_fd().as_raw_fd(), + c::EPOLL_CTL_DEL, + raw_fd, + null_mut(), + )) } } -#[cfg(not(feature = "rustc-dep-of-std"))] -impl From for OwnedFd { - fn from(epoll: Epoll) -> Self { - epoll.epoll_fd +/// `epoll_wait(self, events, timeout)`—Waits for registered events of +/// interest. +/// +/// For each event of interest, an element is written to `events`. On +/// success, this returns the number of written elements. +pub fn epoll_wait( + epoll: impl AsFd, + event_list: &mut EventVec, + timeout: c::c_int, +) -> io::Result<()> { + // Safety: We're calling `epoll_wait` via FFI and we know how it + // behaves. + unsafe { + event_list.events.set_len(0); + let nfds = ret_u32(c::epoll_wait( + epoll.as_fd().as_raw_fd(), + event_list.events.as_mut_ptr().cast::(), + event_list.events.capacity().try_into().unwrap_or(i32::MAX), + timeout, + ))?; + event_list.events.set_len(nfds as usize); } -} -#[cfg(not(feature = "rustc-dep-of-std"))] -impl From for Epoll { - fn from(fd: OwnedFd) -> Self { - Self { epoll_fd: fd } - } + Ok(()) } /// An iterator over the `Event`s in an `EventVec`. @@ -287,7 +237,7 @@ impl<'a> Iterator for Iter<'a> { // context because `wait` sets both. self.iter .next() - .map(|event| (event.event_flags, event.encoded)) + .map(|event| (event.event_flags, event.data)) } } @@ -309,7 +259,7 @@ struct Event { // the full union; `Context` implementations will simply need to deal with // casting the value into and out of the `u64` themselves. event_flags: EventFlags, - encoded: u64, + data: u64, } /// A vector of `Event`s, plus context for interpreting them. diff --git a/src/backend/linux_raw/io/epoll.rs b/src/backend/linux_raw/io/epoll.rs index 29b408d2a..86df6b095 100644 --- a/src/backend/linux_raw/io/epoll.rs +++ b/src/backend/linux_raw/io/epoll.rs @@ -11,7 +11,7 @@ //! # #[cfg(feature = "net")] //! # fn main() -> std::io::Result<()> { //! use io_lifetimes::AsFd; -//! use rustix::io::epoll::{self, Epoll}; +//! use rustix::io::epoll; //! use rustix::io::{ioctl_fionbio, read, write}; //! use rustix::net::{ //! accept, bind_v4, listen, socket, AddressFamily, Ipv4Addr, Protocol, SocketAddrV4, @@ -27,10 +27,10 @@ //! //! // Create an epoll object. Using `Owning` here means the epoll object will //! // take ownership of the file descriptors registered with it. -//! let epoll = Epoll::new(epoll::CreateFlags::CLOEXEC)?; +//! let epoll = epoll::epoll_create(epoll::CreateFlags::CLOEXEC)?; //! //! // Register the socket with the epoll object. -//! epoll.add(&listen_sock, 1, epoll::EventFlags::IN)?; +//! epoll::epoll_add(&epoll, &listen_sock, 1, epoll::EventFlags::IN)?; //! //! // Keep track of the sockets we've opened. //! let mut next_id = 2; @@ -39,14 +39,14 @@ //! // Process events. //! let mut event_list = epoll::EventVec::with_capacity(4); //! loop { -//! epoll.wait(&mut event_list, -1)?; +//! epoll::epoll_wait(&epoll, &mut event_list, -1)?; //! for (_event_flags, target) in &event_list { //! if target == 1 { //! // Accept a new connection, set it to non-blocking, and //! // register to be notified when it's ready to write to. //! let conn_sock = accept(&listen_sock)?; //! ioctl_fionbio(&conn_sock, true)?; -//! epoll.add(&conn_sock, next_id, epoll::EventFlags::OUT | epoll::EventFlags::ET)?; +//! epoll::epoll_add(&epoll, &conn_sock, next_id, epoll::EventFlags::OUT | epoll::EventFlags::ET)?; //! //! // Keep track of the socket. //! sockets.insert(next_id, conn_sock); @@ -55,7 +55,7 @@ //! // Write a message to the stream and then unregister it. //! let target = sockets.remove(&target).unwrap(); //! write(&target, b"hello\n")?; -//! let _ = epoll.del(&target)?; +//! let _ = epoll::epoll_del(&epoll, &target)?; //! } //! } //! } @@ -67,10 +67,8 @@ #![allow(unsafe_code)] use super::super::c; -use crate::backend::io::syscalls::{epoll_add, epoll_create, epoll_del, epoll_mod, epoll_wait}; +use crate::backend::io::syscalls; use crate::fd::{AsFd, AsRawFd, OwnedFd}; -#[cfg(feature = "std")] -use crate::fd::{BorrowedFd, FromRawFd, IntoRawFd, RawFd}; use crate::io; use alloc::vec::Vec; use bitflags::bitflags; @@ -116,149 +114,106 @@ bitflags! { } } -/// An "epoll", an interface to an OS object allowing one to repeatedly wait -/// for events from a set of file descriptors efficiently. -pub struct Epoll { - epoll_fd: OwnedFd, +/// `epoll_create1(flags)`—Creates a new `Epoll`. +/// +/// Use the [`CreateFlags::CLOEXEC`] flag to prevent the resulting file +/// descriptor from being implicitly passed across `exec` boundaries. +#[inline] +#[doc(alias = "epoll_create1")] +pub fn epoll_create(flags: CreateFlags) -> io::Result { + syscalls::epoll_create(flags) } -impl Epoll { - /// `epoll_create1(flags)`—Creates a new `Epoll`. - /// - /// Use the [`CreateFlags::CLOEXEC`] flag to prevent the resulting file - /// descriptor from being implicitly passed across `exec` boundaries. - #[inline] - #[doc(alias = "epoll_create1")] - pub fn new(flags: CreateFlags) -> io::Result { - // Safety: We're calling `epoll_create1` via FFI and we know how it - // behaves. - Ok(Self { - epoll_fd: epoll_create(flags)?, - }) - } - - /// `epoll_ctl(self, EPOLL_CTL_ADD, data, event)`—Adds an element to an - /// `Epoll`. - /// - /// This registers interest in any of the events set in `events` occurring - /// on the file descriptor associated with `data`. - #[doc(alias = "epoll_ctl")] - pub fn add(&self, source: &impl AsFd, data: u64, event_flags: EventFlags) -> io::Result<()> { - // Safety: We're calling `epoll_ctl` via FFI and we know how it - // behaves. - unsafe { - epoll_add( - self.epoll_fd.as_fd(), - source.as_fd().as_raw_fd(), - &linux_raw_sys::general::epoll_event { - events: event_flags.bits(), - data, - }, - ) - } - } - - /// `epoll_ctl(self, EPOLL_CTL_MOD, target, event)`—Modifies an element in - /// this `Epoll`. - /// - /// This sets the events of interest with `target` to `events`. - #[doc(alias = "epoll_ctl")] - pub fn mod_(&self, source: &impl AsFd, data: u64, event_flags: EventFlags) -> io::Result<()> { - // Safety: We're calling `epoll_ctl` via FFI and we know how it - // behaves. - unsafe { - let raw_fd = source.as_fd().as_raw_fd(); - epoll_mod( - self.epoll_fd.as_fd(), - raw_fd, - &linux_raw_sys::general::epoll_event { - events: event_flags.bits(), - data, - }, - ) - } - } - - /// `epoll_ctl(self, EPOLL_CTL_DEL, target, NULL)`—Removes an element in - /// this `Epoll`. - /// - /// This also returns the owning `Data`. - #[doc(alias = "epoll_ctl")] - pub fn del(&self, source: &impl AsFd) -> io::Result<()> { - // Safety: We're calling `epoll_ctl` via FFI and we know how it - // behaves. - unsafe { - let raw_fd = source.as_fd().as_raw_fd(); - epoll_del(self.epoll_fd.as_fd(), raw_fd) - } - } - - /// `epoll_wait(self, events, timeout)`—Waits for registered events of - /// interest. - /// - /// For each event of interest, an element is written to `events`. On - /// success, this returns the number of written elements. - #[doc(alias = "epoll_wait")] - pub fn wait(&self, event_list: &mut EventVec, timeout: c::c_int) -> io::Result<()> { - // Safety: We're calling `epoll_wait` via FFI and we know how it - // behaves. - unsafe { - event_list.events.set_len(0); - let nfds = epoll_wait( - self.epoll_fd.as_fd(), - event_list.events[..].as_mut_ptr().cast(), - event_list.events.capacity(), - timeout, - )?; - event_list.events.set_len(nfds); - } - - Ok(()) - } -} - -#[cfg(feature = "std")] -impl AsRawFd for Epoll { - fn as_raw_fd(&self) -> RawFd { - self.epoll_fd.as_raw_fd() - } -} - -#[cfg(feature = "std")] -impl IntoRawFd for Epoll { - fn into_raw_fd(self) -> RawFd { - self.epoll_fd.into_raw_fd() +/// `epoll_ctl(self, EPOLL_CTL_ADD, data, event)`—Adds an element to an +/// `Epoll`. +/// +/// This registers interest in any of the events set in `events` occurring +/// on the file descriptor associated with `data`. +#[doc(alias = "epoll_ctl")] +pub fn epoll_add( + epoll: impl AsFd, + source: impl AsFd, + data: u64, + event_flags: EventFlags, +) -> io::Result<()> { + // Safety: We're calling `epoll_ctl` via FFI and we know how it + // behaves. + unsafe { + syscalls::epoll_add( + epoll.as_fd(), + source.as_fd().as_raw_fd(), + &linux_raw_sys::general::epoll_event { + events: event_flags.bits(), + data, + }, + ) } } -#[cfg(feature = "std")] -impl FromRawFd for Epoll { - unsafe fn from_raw_fd(fd: RawFd) -> Self { - Self { - epoll_fd: OwnedFd::from_raw_fd(fd), - } +/// `epoll_ctl(self, EPOLL_CTL_MOD, target, event)`—Modifies an element in +/// this `Epoll`. +/// +/// This sets the events of interest with `target` to `events`. +#[doc(alias = "epoll_ctl")] +pub fn epoll_mod( + epoll: impl AsFd, + source: impl AsFd, + data: u64, + event_flags: EventFlags, +) -> io::Result<()> { + // Safety: We're calling `epoll_ctl` via FFI and we know how it + // behaves. + unsafe { + let raw_fd = source.as_fd().as_raw_fd(); + syscalls::epoll_mod( + epoll.as_fd(), + raw_fd, + &linux_raw_sys::general::epoll_event { + events: event_flags.bits(), + data, + }, + ) } } -#[cfg(feature = "std")] -impl AsFd for Epoll { - fn as_fd(&self) -> BorrowedFd<'_> { - self.epoll_fd.as_fd() +/// `epoll_ctl(self, EPOLL_CTL_DEL, target, NULL)`—Removes an element in +/// this `Epoll`. +/// +/// This also returns the owning `Data`. +#[doc(alias = "epoll_ctl")] +pub fn epoll_del(epoll: impl AsFd, source: impl AsFd) -> io::Result<()> { + // Safety: We're calling `epoll_ctl` via FFI and we know how it + // behaves. + unsafe { + let raw_fd = source.as_fd().as_raw_fd(); + syscalls::epoll_del(epoll.as_fd(), raw_fd) } } -#[cfg(feature = "std")] -impl From for OwnedFd { - fn from(epoll: Epoll) -> Self { - epoll.epoll_fd +/// `epoll_wait(self, events, timeout)`—Waits for registered events of +/// interest. +/// +/// For each event of interest, an element is written to `events`. On +/// success, this returns the number of written elements. +pub fn epoll_wait( + epoll: impl AsFd, + event_list: &mut EventVec, + timeout: c::c_int, +) -> io::Result<()> { + // Safety: We're calling `epoll_wait` via FFI and we know how it + // behaves. + unsafe { + event_list.events.set_len(0); + let nfds = syscalls::epoll_wait( + epoll.as_fd(), + event_list.events[..].as_mut_ptr().cast(), + event_list.events.capacity(), + timeout, + )?; + event_list.events.set_len(nfds); } -} -#[cfg(feature = "std")] -impl From for Epoll { - fn from(fd: OwnedFd) -> Self { - Self { epoll_fd: fd } - } + Ok(()) } /// An iterator over the `Event`s in an `EventVec`. @@ -272,7 +227,7 @@ impl<'a> Iterator for Iter<'a> { fn next(&mut self) -> Option { self.iter .next() - .map(|event| (event.event_flags, event.encoded)) + .map(|event| (event.event_flags, event.data)) } } @@ -285,7 +240,7 @@ struct Event { // need to deal with casting the value into and out of the `u64` // themselves. event_flags: EventFlags, - encoded: u64, + data: u64, } /// A vector of `Event`s, plus context for interpreting them. diff --git a/tests/io/epoll.rs b/tests/io/epoll.rs index da6c24766..2efdfc5fe 100644 --- a/tests/io/epoll.rs +++ b/tests/io/epoll.rs @@ -1,14 +1,12 @@ #![cfg(any(target_os = "android", target_os = "linux"))] -use rustix::fd::OwnedFd; -use rustix::io::epoll::{self, Epoll}; +use rustix::io::epoll; use rustix::io::{ioctl_fionbio, read, write}; use rustix::net::{ accept, bind_v4, connect_v4, getsockname, listen, socket, AddressFamily, Ipv4Addr, Protocol, SocketAddrAny, SocketAddrV4, SocketType, }; use std::collections::HashMap; -use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd}; use std::sync::{Arc, Condvar, Mutex}; use std::thread; @@ -31,39 +29,33 @@ fn server(ready: Arc<(Mutex, Condvar)>) { cvar.notify_all(); } - let epoll = Epoll::new(epoll::CreateFlags::CLOEXEC).unwrap(); + let epoll = epoll::epoll_create(epoll::CreateFlags::CLOEXEC).unwrap(); - // Test into conversions. - let fd: OwnedFd = epoll.into(); - let epoll: Epoll = fd.into(); - let fd: RawFd = epoll.into_raw_fd(); - let epoll = unsafe { Epoll::from_raw_fd(fd) }; - - epoll.add(&listen_sock, 1, epoll::EventFlags::IN).unwrap(); + epoll::epoll_add(&epoll, &listen_sock, 1, epoll::EventFlags::IN).unwrap(); let mut next_data = 2; let mut targets = HashMap::new(); let mut event_list = epoll::EventVec::with_capacity(4); loop { - epoll.wait(&mut event_list, -1).unwrap(); + epoll::epoll_wait(&epoll, &mut event_list, -1).unwrap(); for (_event_flags, target) in &event_list { if target == 1 { let conn_sock = accept(&listen_sock).unwrap(); ioctl_fionbio(&conn_sock, true).unwrap(); - epoll - .add( - &conn_sock, - next_data, - epoll::EventFlags::OUT | epoll::EventFlags::ET, - ) - .unwrap(); + epoll::epoll_add( + &epoll, + &conn_sock, + next_data, + epoll::EventFlags::OUT | epoll::EventFlags::ET, + ) + .unwrap(); targets.insert(next_data, conn_sock); next_data += 1; } else { let target = targets.remove(&target).unwrap(); write(&target, b"hello\n").unwrap(); - epoll.del(&target).unwrap(); + epoll::epoll_del(&epoll, &target).unwrap(); } } } From 970b49ae4360fa25a2db7ffc8a853532a9156908 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Tue, 3 Jan 2023 09:54:26 -0800 Subject: [PATCH 3/5] remove vscode from gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index cd23c4561..a9d37c560 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ target Cargo.lock -/.vscode From 8766639e32cbc295130864c777b842c0eb647ab9 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Tue, 3 Jan 2023 09:58:56 -0800 Subject: [PATCH 4/5] Add documentation for error-prone epoll_add behavior --- src/backend/libc/io/epoll.rs | 5 +++++ src/backend/linux_raw/io/epoll.rs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/backend/libc/io/epoll.rs b/src/backend/libc/io/epoll.rs index e7112f893..2d76b7560 100644 --- a/src/backend/libc/io/epoll.rs +++ b/src/backend/libc/io/epoll.rs @@ -129,6 +129,11 @@ pub fn epoll_create(flags: CreateFlags) -> io::Result { /// `epoll_ctl(self, EPOLL_CTL_ADD, data, event)`—Adds an element to an /// `Epoll`. +/// +/// Note that if `epoll_del` is not called on the I/O source passed into +/// this function before the I/O source is `close`d, then the `epoll` will +/// act as if the I/O source is still registered with it. This can lead to +/// spurious events being returned from `epoll_wait`. #[doc(alias = "epoll_ctl")] pub fn epoll_add( epoll: impl AsFd, diff --git a/src/backend/linux_raw/io/epoll.rs b/src/backend/linux_raw/io/epoll.rs index 86df6b095..6977bc978 100644 --- a/src/backend/linux_raw/io/epoll.rs +++ b/src/backend/linux_raw/io/epoll.rs @@ -129,6 +129,11 @@ pub fn epoll_create(flags: CreateFlags) -> io::Result { /// /// This registers interest in any of the events set in `events` occurring /// on the file descriptor associated with `data`. +/// +/// Note that if `epoll_del` is not called on the I/O source passed into +/// this function before the I/O source is `close`d, then the `epoll` will +/// act as if the I/O source is still registered with it. This can lead to +/// spurious events being returned from `epoll_wait`. #[doc(alias = "epoll_ctl")] pub fn epoll_add( epoll: impl AsFd, From b4f35b5210577ac930e5f0c589dd8811569d8d0f Mon Sep 17 00:00:00 2001 From: jtnunley Date: Tue, 3 Jan 2023 13:43:25 -0800 Subject: [PATCH 5/5] Add documentation mentioning "weak dup" --- src/backend/libc/io/epoll.rs | 4 +++- src/backend/linux_raw/io/epoll.rs | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/backend/libc/io/epoll.rs b/src/backend/libc/io/epoll.rs index 2d76b7560..e9647dbb8 100644 --- a/src/backend/libc/io/epoll.rs +++ b/src/backend/libc/io/epoll.rs @@ -133,7 +133,9 @@ pub fn epoll_create(flags: CreateFlags) -> io::Result { /// Note that if `epoll_del` is not called on the I/O source passed into /// this function before the I/O source is `close`d, then the `epoll` will /// act as if the I/O source is still registered with it. This can lead to -/// spurious events being returned from `epoll_wait`. +/// spurious events being returned from `epoll_wait`. If a file descriptor +/// is an `Arc`, then `epoll` can be thought to maintain +/// a `Weak` to the file descriptor. #[doc(alias = "epoll_ctl")] pub fn epoll_add( epoll: impl AsFd, diff --git a/src/backend/linux_raw/io/epoll.rs b/src/backend/linux_raw/io/epoll.rs index 6977bc978..c89517e25 100644 --- a/src/backend/linux_raw/io/epoll.rs +++ b/src/backend/linux_raw/io/epoll.rs @@ -129,11 +129,13 @@ pub fn epoll_create(flags: CreateFlags) -> io::Result { /// /// This registers interest in any of the events set in `events` occurring /// on the file descriptor associated with `data`. -/// +/// /// Note that if `epoll_del` is not called on the I/O source passed into /// this function before the I/O source is `close`d, then the `epoll` will /// act as if the I/O source is still registered with it. This can lead to -/// spurious events being returned from `epoll_wait`. +/// spurious events being returned from `epoll_wait`. If a file descriptor +/// is an `Arc`, then `epoll` can be thought to maintain +/// a `Weak` to the file descriptor. #[doc(alias = "epoll_ctl")] pub fn epoll_add( epoll: impl AsFd,