mirror of https://github.com/smol-rs/polling
Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
John Nunley | cf2d60efca | |
John Nunley | 0b4afcaf0a | |
John Nunley | eb9d92a2e0 | |
Nikolay Arhipov | 9e46c8455c | |
John Nunley | 1c16a1e4af | |
irvingouj @ Devolutions | e25b3b4e4c | |
John Nunley | 50454d1cea | |
John Nunley | 634a77c264 | |
John Nunley | 4d64fdc572 | |
John Nunley | 77b4ed1156 | |
John Nunley | ac7fbcae31 | |
John Nunley | 24e3691794 | |
John Nunley | 62430fd56e | |
John Nunley | ae484a0a12 | |
irvingouj @ Devolutions | cf25dd85f8 |
|
@ -62,16 +62,10 @@ jobs:
|
|||
RUSTFLAGS: ${{ env.RUSTFLAGS }} --cfg polling_test_epoll_pipe
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
- run: cargo hack build --feature-powerset --no-dev-deps
|
||||
- name: Add rust-src
|
||||
if: startsWith(matrix.rust, 'nightly')
|
||||
run: rustup component add rust-src
|
||||
# TODO: broken due to https://github.com/rust-lang/rust/pull/119026.
|
||||
# - name: Check selected Tier 3 targets
|
||||
# if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest'
|
||||
# run: cargo check -Z build-std --target=riscv32imc-esp-espidf
|
||||
- name: Check haiku
|
||||
if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest'
|
||||
run: cargo check -Z build-std --target x86_64-unknown-haiku
|
||||
- name: Clone async-io
|
||||
run: git clone https://github.com/smol-rs/async-io.git
|
||||
# The async-io Cargo.toml already has a patch section at the bottom, so we
|
||||
|
@ -87,12 +81,16 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust: [nightly, stable]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }}
|
||||
- name: Install cross
|
||||
uses: taiki-e/install-action@cross
|
||||
- name: Add rust-src
|
||||
if: startsWith(matrix.rust, 'nightly')
|
||||
run: rustup component add rust-src
|
||||
# We don't test BSDs, since we already test them in Cirrus.
|
||||
- name: Android
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
|
@ -117,6 +115,20 @@ jobs:
|
|||
run: |
|
||||
rustup target add x86_64-unknown-illumos
|
||||
cargo build --target x86_64-unknown-illumos
|
||||
- name: Redox
|
||||
if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
rustup target add x86_64-unknown-redox
|
||||
cargo check --target x86_64-unknown-redox
|
||||
- name: HermitOS
|
||||
if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest'
|
||||
run: cargo check -Z build-std --target x86_64-unknown-hermit
|
||||
- name: Check haiku
|
||||
if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest'
|
||||
run: cargo check -Z build-std --target x86_64-unknown-haiku
|
||||
- name: Check vita
|
||||
if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest'
|
||||
run: cargo check -Z build-std --target armv7-sony-vita-newlibeabihf
|
||||
|
||||
wine:
|
||||
runs-on: ubuntu-22.04
|
||||
|
@ -127,7 +139,6 @@ jobs:
|
|||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
with:
|
||||
target: x86_64-pc-windows-gnu
|
||||
runner: wine@7.13
|
||||
- run: cargo test --target x86_64-pc-windows-gnu
|
||||
|
||||
msrv:
|
||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -1,3 +1,25 @@
|
|||
# Version 3.7.0
|
||||
|
||||
- Add support for the PS Vita as a platform. (#160)
|
||||
|
||||
# Version 3.6.0
|
||||
|
||||
- Add an `is_err` method to `Event` to tell when an error has occurred. (#189)
|
||||
- Deprecate the `is_connect_failed` function. (#189)
|
||||
- Add support for HermitOS to `polling`. (#194)
|
||||
|
||||
# Version 3.5.0
|
||||
|
||||
- Use the `epoll` backend when RedoxOS is enabled. (#190)
|
||||
|
||||
# Version 3.4.0
|
||||
|
||||
- Add the ability to identify whether socket connection has failed. (#185)
|
||||
- On BSD, add the ability to wait on a process by its PID. Previously, it was
|
||||
only possible to wait on a process by a `Child` object. (#180)
|
||||
- On ESP-IDF, annotate `eventfd` initialization failures with a message
|
||||
indicating the source of those failures. (#186)
|
||||
|
||||
# Version 3.3.2
|
||||
|
||||
- When AFD fails to initialize, the resulting error now references
|
||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -3,7 +3,7 @@ name = "polling"
|
|||
# When publishing a new version:
|
||||
# - Update CHANGELOG.md
|
||||
# - Create "v3.x.y" git tag
|
||||
version = "3.3.2"
|
||||
version = "3.7.0"
|
||||
authors = ["Stjepan Glavina <stjepang@gmail.com>", "John Nunley <dev@notgull.net>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.63"
|
||||
|
@ -21,8 +21,10 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||
cfg-if = "1"
|
||||
tracing = { version = "0.1.37", default-features = false }
|
||||
|
||||
[target.'cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))'.dependencies]
|
||||
rustix = { version = "0.38.8", features = ["event", "fs", "pipe", "process", "std", "time"], default-features = false }
|
||||
[target.'cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))'.dependencies.rustix]
|
||||
version = "0.38.31"
|
||||
features = ["event", "fs", "pipe", "process", "std", "time"]
|
||||
default-features = false
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
concurrent-queue = "2.2.0"
|
||||
|
@ -43,10 +45,16 @@ features = [
|
|||
"Win32_System_WindowsProgramming",
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "hermit")'.dependencies.hermit-abi]
|
||||
version = "0.3.9"
|
||||
|
||||
[dev-dependencies]
|
||||
easy-parallel = "3.1.0"
|
||||
fastrand = "2.0.0"
|
||||
socket2 = "0.5.5"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os="vita")))'.dev-dependencies]
|
||||
signal-hook = "0.3.17"
|
||||
|
|
|
@ -12,11 +12,11 @@ https://docs.rs/polling)
|
|||
Portable interface to epoll, kqueue, event ports, and IOCP.
|
||||
|
||||
Supported platforms:
|
||||
- [epoll](https://en.wikipedia.org/wiki/Epoll): Linux, Android
|
||||
- [epoll](https://en.wikipedia.org/wiki/Epoll): Linux, Android, RedoxOS
|
||||
- [kqueue](https://en.wikipedia.org/wiki/Kqueue): macOS, iOS, tvOS, watchOS, FreeBSD, NetBSD, OpenBSD,
|
||||
DragonFly BSD
|
||||
- [event ports](https://illumos.org/man/port_create): illumos, Solaris
|
||||
- [poll](https://en.wikipedia.org/wiki/Poll_(Unix)): VxWorks, Fuchsia, other Unix systems
|
||||
- [poll](https://en.wikipedia.org/wiki/Poll_(Unix)): VxWorks, Fuchsia, HermitOS, other Unix systems
|
||||
- [IOCP](https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports): Windows, Wine (version 7.13+)
|
||||
|
||||
Polling is done in oneshot mode, which means interest in I/O events needs to be reset after
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
use std::{io, net};
|
||||
|
||||
use polling::Event;
|
||||
use socket2::Type;
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let socket = socket2::Socket::new(socket2::Domain::IPV4, Type::STREAM, None)?;
|
||||
let poller = polling::Poller::new()?;
|
||||
unsafe {
|
||||
poller.add(&socket, Event::new(0, true, true))?;
|
||||
}
|
||||
let addr = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 8080);
|
||||
socket.set_nonblocking(true)?;
|
||||
let _ = socket.connect(&addr.into());
|
||||
|
||||
let mut events = polling::Events::new();
|
||||
|
||||
events.clear();
|
||||
poller.wait(&mut events, None)?;
|
||||
|
||||
let event = events.iter().next();
|
||||
let event = match event {
|
||||
Some(event) => event,
|
||||
None => {
|
||||
println!("no event");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
println!("event: {:?}", event);
|
||||
if event.is_err().unwrap_or(false) {
|
||||
println!("connect failed");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -27,22 +27,16 @@ mod example {
|
|||
|
||||
println!("Press Ctrl+C to exit...");
|
||||
|
||||
loop {
|
||||
// Wait for events.
|
||||
poller.wait(&mut events, None).unwrap();
|
||||
// Wait for events.
|
||||
poller.wait(&mut events, None).unwrap();
|
||||
|
||||
// Process events.
|
||||
for ev in events.iter() {
|
||||
match ev.key {
|
||||
1 => {
|
||||
println!("SIGINT received");
|
||||
return;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
// Process events.
|
||||
let ev = events.iter().next().unwrap();
|
||||
match ev.key {
|
||||
1 => {
|
||||
println!("SIGINT received");
|
||||
}
|
||||
|
||||
events.clear();
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
76
src/epoll.rs
76
src/epoll.rs
|
@ -4,16 +4,20 @@ use std::io;
|
|||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::time::Duration;
|
||||
|
||||
use rustix::event::{epoll, eventfd, EventfdFlags};
|
||||
use rustix::fd::OwnedFd;
|
||||
use rustix::fs::{fcntl_getfl, fcntl_setfl, OFlags};
|
||||
use rustix::io::{fcntl_getfd, fcntl_setfd, read, write, FdFlags};
|
||||
use rustix::pipe::{pipe, pipe_with, PipeFlags};
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
use rustix::event::{eventfd, EventfdFlags};
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
use rustix::time::{
|
||||
timerfd_create, timerfd_settime, Itimerspec, TimerfdClockId, TimerfdFlags, TimerfdTimerFlags,
|
||||
Timespec,
|
||||
};
|
||||
|
||||
use rustix::event::epoll;
|
||||
use rustix::fd::OwnedFd;
|
||||
use rustix::fs::{fcntl_getfl, fcntl_setfl, OFlags};
|
||||
use rustix::io::{fcntl_getfd, fcntl_setfd, read, write, FdFlags};
|
||||
use rustix::pipe::{pipe, pipe_with, PipeFlags};
|
||||
|
||||
use crate::{Event, PollMode};
|
||||
|
||||
/// Interface to epoll.
|
||||
|
@ -26,6 +30,9 @@ pub struct Poller {
|
|||
notifier: Notifier,
|
||||
|
||||
/// File descriptor for the timerfd that produces timeouts.
|
||||
///
|
||||
/// Redox does not support timerfd.
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
timer_fd: Option<OwnedFd>,
|
||||
}
|
||||
|
||||
|
@ -39,6 +46,7 @@ impl Poller {
|
|||
|
||||
// Set up notifier and timerfd.
|
||||
let notifier = Notifier::new()?;
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
let timer_fd = timerfd_create(
|
||||
TimerfdClockId::Monotonic,
|
||||
TimerfdFlags::CLOEXEC | TimerfdFlags::NONBLOCK,
|
||||
|
@ -48,10 +56,12 @@ impl Poller {
|
|||
let poller = Poller {
|
||||
epoll_fd,
|
||||
notifier,
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
timer_fd,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
if let Some(ref timer_fd) = poller.timer_fd {
|
||||
poller.add(
|
||||
timer_fd.as_raw_fd(),
|
||||
|
@ -70,7 +80,6 @@ impl Poller {
|
|||
tracing::trace!(
|
||||
epoll_fd = ?poller.epoll_fd.as_raw_fd(),
|
||||
notifier = ?poller.notifier,
|
||||
timer_fd = ?poller.timer_fd,
|
||||
"new",
|
||||
);
|
||||
Ok(poller)
|
||||
|
@ -155,6 +164,7 @@ impl Poller {
|
|||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
if let Some(ref timer_fd) = self.timer_fd {
|
||||
// Configure the timeout using timerfd.
|
||||
let new_val = Itimerspec {
|
||||
|
@ -181,8 +191,13 @@ impl Poller {
|
|||
)?;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
let timer_fd = &self.timer_fd;
|
||||
#[cfg(target_os = "redox")]
|
||||
let timer_fd: Option<core::convert::Infallible> = None;
|
||||
|
||||
// Timeout in milliseconds for epoll.
|
||||
let timeout_ms = match (&self.timer_fd, timeout) {
|
||||
let timeout_ms = match (timer_fd, timeout) {
|
||||
(_, Some(t)) if t == Duration::from_secs(0) => 0,
|
||||
(None, Some(t)) => {
|
||||
// Round up to a whole millisecond.
|
||||
|
@ -245,10 +260,10 @@ impl Drop for Poller {
|
|||
"drop",
|
||||
epoll_fd = ?self.epoll_fd.as_raw_fd(),
|
||||
notifier = ?self.notifier,
|
||||
timer_fd = ?self.timer_fd
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
if let Some(timer_fd) = self.timer_fd.take() {
|
||||
let _ = self.delete(timer_fd.as_fd());
|
||||
}
|
||||
|
@ -257,6 +272,7 @@ impl Drop for Poller {
|
|||
}
|
||||
|
||||
/// `timespec` value that equals zero.
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
const TS_ZERO: Timespec = unsafe { std::mem::transmute([0u8; std::mem::size_of::<Timespec>()]) };
|
||||
|
||||
/// Get the EPOLL flags for the interest.
|
||||
|
@ -365,6 +381,19 @@ impl EventExtra {
|
|||
pub fn is_pri(&self) -> bool {
|
||||
self.flags.contains(epoll::EventFlags::PRI)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_connect_failed(&self) -> Option<bool> {
|
||||
Some(
|
||||
self.flags.contains(epoll::EventFlags::ERR)
|
||||
&& self.flags.contains(epoll::EventFlags::HUP),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_err(&self) -> Option<bool> {
|
||||
Some(self.flags.contains(epoll::EventFlags::ERR))
|
||||
}
|
||||
}
|
||||
|
||||
/// The notifier for Linux.
|
||||
|
@ -377,6 +406,7 @@ impl EventExtra {
|
|||
#[derive(Debug)]
|
||||
enum Notifier {
|
||||
/// The primary notifier, using eventfd.
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
EventFd(OwnedFd),
|
||||
|
||||
/// The fallback notifier, using a pipe.
|
||||
|
@ -393,19 +423,22 @@ impl Notifier {
|
|||
/// Create a new notifier.
|
||||
fn new() -> io::Result<Self> {
|
||||
// Skip eventfd for testing if necessary.
|
||||
if !cfg!(polling_test_epoll_pipe) {
|
||||
// Try to create an eventfd.
|
||||
match eventfd(0, EventfdFlags::CLOEXEC | EventfdFlags::NONBLOCK) {
|
||||
Ok(fd) => {
|
||||
tracing::trace!("created eventfd for notifier");
|
||||
return Ok(Notifier::EventFd(fd));
|
||||
}
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
{
|
||||
if !cfg!(polling_test_epoll_pipe) {
|
||||
// Try to create an eventfd.
|
||||
match eventfd(0, EventfdFlags::CLOEXEC | EventfdFlags::NONBLOCK) {
|
||||
Ok(fd) => {
|
||||
tracing::trace!("created eventfd for notifier");
|
||||
return Ok(Notifier::EventFd(fd));
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"eventfd() failed with error ({}), falling back to pipe",
|
||||
err
|
||||
);
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"eventfd() failed with error ({}), falling back to pipe",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -427,6 +460,7 @@ impl Notifier {
|
|||
/// The file descriptor to register in the poller.
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
match self {
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
Notifier::EventFd(fd) => fd.as_fd(),
|
||||
Notifier::Pipe {
|
||||
read_pipe: read, ..
|
||||
|
@ -437,6 +471,7 @@ impl Notifier {
|
|||
/// Notify the poller.
|
||||
fn notify(&self) {
|
||||
match self {
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
Self::EventFd(fd) => {
|
||||
let buf: [u8; 8] = 1u64.to_ne_bytes();
|
||||
let _ = write(fd, &buf);
|
||||
|
@ -451,6 +486,7 @@ impl Notifier {
|
|||
/// Clear the notification.
|
||||
fn clear(&self) {
|
||||
match self {
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
Self::EventFd(fd) => {
|
||||
let mut buf = [0u8; 8];
|
||||
let _ = read(fd, &mut buf);
|
||||
|
|
|
@ -681,6 +681,18 @@ impl EventExtra {
|
|||
pub fn set_pri(&mut self, active: bool) {
|
||||
self.flags.set(AfdPollMask::RECEIVE_EXPEDITED, active);
|
||||
}
|
||||
|
||||
/// Check if TCP connect failed. Deprecated.
|
||||
#[inline]
|
||||
pub fn is_connect_failed(&self) -> Option<bool> {
|
||||
Some(self.flags.intersects(AfdPollMask::CONNECT_FAIL))
|
||||
}
|
||||
|
||||
/// Check if TCP connect failed.
|
||||
#[inline]
|
||||
pub fn is_err(&self) -> Option<bool> {
|
||||
Some(self.flags.intersects(AfdPollMask::CONNECT_FAIL))
|
||||
}
|
||||
}
|
||||
|
||||
/// A packet used to wake up the poller with an event.
|
||||
|
|
|
@ -296,11 +296,3 @@ impl<T: CompletionHandle> Drop for OverlappedEntry<T> {
|
|||
drop(unsafe { self.packet() });
|
||||
}
|
||||
}
|
||||
|
||||
struct CallOnDrop<F: FnMut()>(F);
|
||||
|
||||
impl<F: FnMut()> Drop for CallOnDrop<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.0)();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -371,6 +371,16 @@ impl EventExtra {
|
|||
pub fn is_pri(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_connect_failed(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_err(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mode_to_flags(mode: PollMode) -> kqueue::EventFlags {
|
||||
|
|
96
src/lib.rs
96
src/lib.rs
|
@ -1,11 +1,11 @@
|
|||
//! Portable interface to epoll, kqueue, event ports, and IOCP.
|
||||
//!
|
||||
//! Supported platforms:
|
||||
//! - [epoll](https://en.wikipedia.org/wiki/Epoll): Linux, Android
|
||||
//! - [epoll](https://en.wikipedia.org/wiki/Epoll): Linux, Android, RedoxOS
|
||||
//! - [kqueue](https://en.wikipedia.org/wiki/Kqueue): macOS, iOS, tvOS, watchOS, FreeBSD, NetBSD, OpenBSD,
|
||||
//! DragonFly BSD
|
||||
//! - [event ports](https://illumos.org/man/port_create): illumos, Solaris
|
||||
//! - [poll](https://en.wikipedia.org/wiki/Poll_(Unix)): VxWorks, Fuchsia, other Unix systems
|
||||
//! - [poll](https://en.wikipedia.org/wiki/Poll_(Unix)): VxWorks, Fuchsia, HermitOS, other Unix systems
|
||||
//! - [IOCP](https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports): Windows, Wine (version 7.13+)
|
||||
//!
|
||||
//! By default, polling is done in oneshot mode, which means interest in I/O events needs to
|
||||
|
@ -80,7 +80,11 @@ cfg_if! {
|
|||
if #[cfg(polling_test_poll_backend)] {
|
||||
mod poll;
|
||||
use poll as sys;
|
||||
} else if #[cfg(any(target_os = "linux", target_os = "android"))] {
|
||||
} else if #[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "redox"
|
||||
))] {
|
||||
mod epoll;
|
||||
use epoll as sys;
|
||||
} else if #[cfg(any(
|
||||
|
@ -103,6 +107,7 @@ cfg_if! {
|
|||
use kqueue as sys;
|
||||
} else if #[cfg(any(
|
||||
target_os = "vxworks",
|
||||
target_os = "hermit",
|
||||
target_os = "fuchsia",
|
||||
target_os = "horizon",
|
||||
unix,
|
||||
|
@ -332,6 +337,86 @@ impl Event {
|
|||
self.extra.is_pri()
|
||||
}
|
||||
|
||||
/// Tells if this event is the result of a connection failure.
|
||||
///
|
||||
/// This function checks if a TCP connection has failed. It corresponds to the `EPOLLERR` or `EPOLLHUP` event in Linux
|
||||
/// and `CONNECT_FAILED` event in Windows IOCP.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::{io, net};
|
||||
/// // Assuming polling and socket2 are included as dependencies in Cargo.toml
|
||||
/// use polling::Event;
|
||||
/// use socket2::Type;
|
||||
///
|
||||
/// fn main() -> io::Result<()> {
|
||||
/// let socket = socket2::Socket::new(socket2::Domain::IPV4, Type::STREAM, None)?;
|
||||
/// let poller = polling::Poller::new()?;
|
||||
/// unsafe {
|
||||
/// poller.add(&socket, Event::new(0, true, true))?;
|
||||
/// }
|
||||
/// let addr = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 8080);
|
||||
/// socket.set_nonblocking(true)?;
|
||||
/// let _ = socket.connect(&addr.into());
|
||||
///
|
||||
/// let mut events = polling::Events::new();
|
||||
///
|
||||
/// events.clear();
|
||||
/// poller.wait(&mut events, None)?;
|
||||
///
|
||||
/// let event = events.iter().next();
|
||||
///
|
||||
/// let event = match event {
|
||||
/// Some(event) => event,
|
||||
/// None => {
|
||||
/// println!("no event");
|
||||
/// return Ok(());
|
||||
/// },
|
||||
/// };
|
||||
///
|
||||
/// println!("event: {:?}", event);
|
||||
/// if event
|
||||
/// .is_connect_failed()
|
||||
/// .unwrap_or_default()
|
||||
/// {
|
||||
/// println!("connect failed");
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `Some(true)` if the connection has failed, `Some(false)` if the connection has not failed,
|
||||
/// or `None` if the platform does not support detecting this condition.
|
||||
#[inline]
|
||||
#[deprecated(
|
||||
since = "3.4.0",
|
||||
note = "use `is_err` in combination of is_hup instead, see documentation for `is_err`"
|
||||
)]
|
||||
pub fn is_connect_failed(&self) -> Option<bool> {
|
||||
self.extra.is_connect_failed()
|
||||
}
|
||||
|
||||
/// Tells if this event is the result of a connection failure.
|
||||
///
|
||||
/// This function checks if an error exist, particularly useful in detecting if TCP connection failed. It corresponds to the `EPOLLERR` event in Linux
|
||||
/// and `CONNECT_FAILED` event in Windows IOCP.
|
||||
///
|
||||
/// ## Caveats
|
||||
///
|
||||
/// In `epoll`, a TCP connection failure is indicated by `EPOLLERR` + `EPOLLHUP`, though just `EPOLLERR` is enough to indicate a connection failure.
|
||||
/// EPOLLHUP may happen when we haven't event called `connect` on the socket, but it is still a valid event to check for.
|
||||
///
|
||||
/// Returns `Some(true)` if the connection has failed, `Some(false)` if there is an error,
|
||||
/// or `None` if the platform does not support detecting this condition.
|
||||
#[inline]
|
||||
pub fn is_err(&self) -> Option<bool> {
|
||||
self.extra.is_err()
|
||||
}
|
||||
|
||||
/// Remove any extra information from this event.
|
||||
#[inline]
|
||||
pub fn clear_extra(&mut self) {
|
||||
|
@ -954,8 +1039,11 @@ impl fmt::Debug for Poller {
|
|||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(unix)] {
|
||||
if #[cfg(any(unix, target_os = "hermit"))] {
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::{AsRawFd, RawFd, AsFd, BorrowedFd};
|
||||
#[cfg(target_os = "hermit")]
|
||||
use std::os::hermit::io::{AsRawFd, RawFd, AsFd, BorrowedFd};
|
||||
|
||||
/// A resource with a raw file descriptor.
|
||||
pub trait AsRawSource {
|
||||
|
|
|
@ -20,6 +20,7 @@ pub mod iocp;
|
|||
|
||||
mod __private {
|
||||
#[doc(hidden)]
|
||||
#[allow(dead_code)]
|
||||
pub trait PollerSealed {}
|
||||
|
||||
impl PollerSealed for crate::Poller {}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Functionality that is only availale for IOCP-based platforms.
|
||||
//! Functionality that is only available for IOCP-based platforms.
|
||||
|
||||
pub use crate::sys::CompletionPacket;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::sys::{mode_to_flags, SourceId};
|
|||
use crate::{PollMode, Poller};
|
||||
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::process::Child;
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -171,11 +172,14 @@ impl Filter for Signal {}
|
|||
/// Monitor a child process.
|
||||
#[derive(Debug)]
|
||||
pub struct Process<'a> {
|
||||
/// The child process to monitor.
|
||||
child: &'a Child,
|
||||
/// The process ID to monitor.
|
||||
pid: rustix::process::Pid,
|
||||
|
||||
/// The operation to monitor.
|
||||
ops: ProcessOps,
|
||||
|
||||
/// Lifetime of the underlying process.
|
||||
_lt: PhantomData<&'a Child>,
|
||||
}
|
||||
|
||||
/// The operations that a monitored process can perform.
|
||||
|
@ -200,7 +204,24 @@ impl<'a> Process<'a> {
|
|||
/// Once registered into the `Poller`, the `Child` object must outlive this filter's
|
||||
/// registration into the poller.
|
||||
pub unsafe fn new(child: &'a Child, ops: ProcessOps) -> Self {
|
||||
Self { child, ops }
|
||||
Self {
|
||||
pid: rustix::process::Pid::from_child(child),
|
||||
ops,
|
||||
_lt: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `Process` from a PID.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The PID must be tied to an actual child process.
|
||||
pub unsafe fn from_pid(pid: std::num::NonZeroI32, ops: ProcessOps) -> Self {
|
||||
Self {
|
||||
pid: unsafe { rustix::process::Pid::from_raw_unchecked(pid.get()) },
|
||||
ops,
|
||||
_lt: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,7 +236,8 @@ unsafe impl FilterSealed for Process<'_> {
|
|||
|
||||
kqueue::Event::new(
|
||||
kqueue::EventFilter::Proc {
|
||||
pid: rustix::process::Pid::from_child(self.child),
|
||||
// SAFETY: We know that the PID is nonzero.
|
||||
pid: self.pid,
|
||||
flags: events,
|
||||
},
|
||||
flags | kqueue::EventFlags::RECEIPT,
|
||||
|
@ -225,7 +247,8 @@ unsafe impl FilterSealed for Process<'_> {
|
|||
|
||||
#[inline(always)]
|
||||
fn source_id(&self) -> SourceId {
|
||||
SourceId::Pid(rustix::process::Pid::from_child(self.child))
|
||||
// SAFETY: We know that the PID is nonzero
|
||||
SourceId::Pid(self.pid)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
250
src/poll.rs
250
src/poll.rs
|
@ -6,8 +6,12 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
|||
use std::sync::{Condvar, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
#[cfg(not(target_os = "hermit"))]
|
||||
use rustix::fd::{AsFd, AsRawFd, BorrowedFd};
|
||||
#[cfg(target_os = "hermit")]
|
||||
use std::os::hermit::io::{AsFd, AsRawFd, BorrowedFd};
|
||||
|
||||
use syscall::{poll, PollFd, PollFlags};
|
||||
|
||||
// std::os::unix doesn't exist on Fuchsia
|
||||
type RawFd = std::os::raw::c_int;
|
||||
|
@ -426,6 +430,16 @@ impl EventExtra {
|
|||
pub fn is_pri(&self) -> bool {
|
||||
self.flags.contains(PollFlags::PRI)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_connect_failed(&self) -> Option<bool> {
|
||||
Some(self.flags.contains(PollFlags::ERR) || self.flags.contains(PollFlags::HUP))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_err(&self) -> Option<bool> {
|
||||
Some(self.flags.contains(PollFlags::ERR))
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt_mode_as_remove(mode: PollMode) -> io::Result<bool> {
|
||||
|
@ -438,7 +452,182 @@ fn cvt_mode_as_remove(mode: PollMode) -> io::Result<bool> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "espidf"))]
|
||||
#[cfg(unix)]
|
||||
mod syscall {
|
||||
pub(super) use rustix::event::{poll, PollFd, PollFlags};
|
||||
|
||||
#[cfg(target_os = "espidf")]
|
||||
pub(super) use rustix::event::{eventfd, EventfdFlags};
|
||||
#[cfg(target_os = "espidf")]
|
||||
pub(super) use rustix::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
|
||||
#[cfg(target_os = "espidf")]
|
||||
pub(super) use rustix::io::{read, write};
|
||||
}
|
||||
|
||||
#[cfg(target_os = "hermit")]
|
||||
mod syscall {
|
||||
// TODO: Remove this shim once HermitOS is supported in Rustix.
|
||||
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::BitOr;
|
||||
|
||||
pub(super) use std::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
|
||||
|
||||
/// Create an eventfd.
|
||||
pub(super) fn eventfd(count: u64, _flags: EventfdFlags) -> io::Result<OwnedFd> {
|
||||
let fd = unsafe { hermit_abi::eventfd(count, 0) };
|
||||
|
||||
if fd < 0 {
|
||||
Err(io::Error::from_raw_os_error(unsafe {
|
||||
hermit_abi::get_errno()
|
||||
}))
|
||||
} else {
|
||||
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Read some bytes.
|
||||
pub(super) fn read(fd: BorrowedFd<'_>, bytes: &mut [u8]) -> io::Result<usize> {
|
||||
let count = unsafe { hermit_abi::read(fd.as_raw_fd(), bytes.as_mut_ptr(), bytes.len()) };
|
||||
|
||||
cvt(count)
|
||||
}
|
||||
|
||||
/// Write some bytes.
|
||||
pub(super) fn write(fd: BorrowedFd<'_>, bytes: &[u8]) -> io::Result<usize> {
|
||||
let count = unsafe { hermit_abi::write(fd.as_raw_fd(), bytes.as_ptr(), bytes.len()) };
|
||||
|
||||
cvt(count)
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `poll` system call.
|
||||
pub(super) fn poll(fds: &mut [PollFd<'_>], timeout: i32) -> io::Result<usize> {
|
||||
let call = unsafe {
|
||||
hermit_abi::poll(
|
||||
fds.as_mut_ptr() as *mut hermit_abi::pollfd,
|
||||
fds.len(),
|
||||
timeout,
|
||||
)
|
||||
};
|
||||
|
||||
cvt(call as isize)
|
||||
}
|
||||
|
||||
/// Safe wrapper around `pollfd`.
|
||||
#[repr(transparent)]
|
||||
pub(super) struct PollFd<'a> {
|
||||
inner: hermit_abi::pollfd,
|
||||
_lt: PhantomData<BorrowedFd<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> PollFd<'a> {
|
||||
pub(super) fn from_borrowed_fd(fd: BorrowedFd<'a>, inflags: PollFlags) -> Self {
|
||||
Self {
|
||||
inner: hermit_abi::pollfd {
|
||||
fd: fd.as_raw_fd(),
|
||||
events: inflags.0,
|
||||
revents: 0,
|
||||
},
|
||||
_lt: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn revents(&self) -> PollFlags {
|
||||
PollFlags(self.inner.revents)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for PollFd<'_> {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.inner.fd) }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PollFd<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("PollFd")
|
||||
.field("fd", &format_args!("0x{:x}", self.inner.fd))
|
||||
.field("events", &PollFlags(self.inner.events))
|
||||
.field("revents", &PollFlags(self.inner.revents))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around polling flags.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub(super) struct PollFlags(i16);
|
||||
|
||||
impl PollFlags {
|
||||
/// Empty set of flags.
|
||||
pub(super) const fn empty() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
pub(super) const IN: PollFlags = PollFlags(hermit_abi::POLLIN);
|
||||
pub(super) const OUT: PollFlags = PollFlags(hermit_abi::POLLOUT);
|
||||
pub(super) const WRBAND: PollFlags = PollFlags(hermit_abi::POLLWRBAND);
|
||||
pub(super) const ERR: PollFlags = PollFlags(hermit_abi::POLLERR);
|
||||
pub(super) const HUP: PollFlags = PollFlags(hermit_abi::POLLHUP);
|
||||
pub(super) const PRI: PollFlags = PollFlags(hermit_abi::POLLPRI);
|
||||
|
||||
/// Tell if this contains some flags.
|
||||
pub(super) fn contains(self, flags: PollFlags) -> bool {
|
||||
self.0 & flags.0 != 0
|
||||
}
|
||||
|
||||
/// Set a flag.
|
||||
pub(super) fn set(&mut self, flags: PollFlags, set: bool) {
|
||||
if set {
|
||||
self.0 |= flags.0;
|
||||
} else {
|
||||
self.0 &= !(flags.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tell if this is empty.
|
||||
pub(super) fn is_empty(self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
/// Tell if this intersects with some flags.
|
||||
pub(super) fn intersects(self, flags: PollFlags) -> bool {
|
||||
self.contains(flags)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for PollFlags {
|
||||
type Output = PollFlags;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(super) struct EventfdFlags;
|
||||
|
||||
impl EventfdFlags {
|
||||
pub(super) fn empty() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a number to an actual result.
|
||||
#[inline]
|
||||
fn cvt(len: isize) -> io::Result<usize> {
|
||||
if len < 0 {
|
||||
Err(io::Error::from_raw_os_error(unsafe {
|
||||
hermit_abi::get_errno()
|
||||
}))
|
||||
} else {
|
||||
Ok(len as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "espidf", target_os = "hermit")))]
|
||||
mod notify {
|
||||
use std::io;
|
||||
|
||||
|
@ -496,7 +685,7 @@ mod notify {
|
|||
self.read_pipe.as_fd()
|
||||
}
|
||||
|
||||
/// Provides the poll flags to be used when registering the read half of the botify pipe with the `Poller`.
|
||||
/// Provides the poll flags to be used when registering the read half of the notify pipe with the `Poller`.
|
||||
pub(super) fn poll_flags(&self) -> PollFlags {
|
||||
PollFlags::RDNORM
|
||||
}
|
||||
|
@ -510,7 +699,25 @@ mod notify {
|
|||
|
||||
/// Pops a notification (if any) from the pipe.
|
||||
pub(super) fn pop_notification(&self) -> Result<(), io::Error> {
|
||||
read(&self.read_pipe, &mut [0; 1])?;
|
||||
// Pipes on Vita do not guarantee that after `write` call succeeds, the
|
||||
// data becomes immediately available for reading on the other side of the pipe.
|
||||
// To ensure that the notification is not lost, the read side of the pipe is temporarily
|
||||
// switched to blocking for a single `read` call.
|
||||
#[cfg(target_os = "vita")]
|
||||
rustix::fs::fcntl_setfl(
|
||||
&self.read_pipe,
|
||||
rustix::fs::fcntl_getfl(&self.read_pipe)? & !rustix::fs::OFlags::NONBLOCK,
|
||||
)?;
|
||||
|
||||
let result = read(&self.read_pipe, &mut [0; 1]);
|
||||
|
||||
#[cfg(target_os = "vita")]
|
||||
rustix::fs::fcntl_setfl(
|
||||
&self.read_pipe,
|
||||
rustix::fs::fcntl_getfl(&self.read_pipe)? | rustix::fs::OFlags::NONBLOCK,
|
||||
)?;
|
||||
|
||||
result?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -529,20 +736,18 @@ mod notify {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "espidf")]
|
||||
#[cfg(any(target_os = "espidf", target_os = "hermit"))]
|
||||
mod notify {
|
||||
use std::io;
|
||||
use std::mem;
|
||||
|
||||
use rustix::event::PollFlags;
|
||||
use rustix::event::{eventfd, EventfdFlags};
|
||||
|
||||
use rustix::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
|
||||
use rustix::io::{read, write};
|
||||
use super::syscall::{
|
||||
eventfd, read, write, AsFd, AsRawFd, BorrowedFd, EventfdFlags, OwnedFd, PollFlags, RawFd,
|
||||
};
|
||||
|
||||
/// A notification pipe.
|
||||
///
|
||||
/// This implementation uses ther `eventfd` syscall to send notifications.
|
||||
/// This implementation uses the `eventfd` syscall to send notifications.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Notify {
|
||||
/// The file descriptor of the eventfd object. This is also stored as the first
|
||||
|
@ -566,13 +771,20 @@ mod notify {
|
|||
// (1) is not a problem for us, as we want the eventfd() file descriptor to be in a non-blocking mode anyway
|
||||
// (2) is also not a problem, as long as we don't try to read the counter value in an endless loop when we detect being notified
|
||||
|
||||
#[cfg(not(target_os = "espidf"))]
|
||||
let flags = EventfdFlags::NONBLOCK;
|
||||
|
||||
#[cfg(target_os = "espidf")]
|
||||
let flags = EventfdFlags::empty();
|
||||
|
||||
let event_fd = eventfd(0, flags)?;
|
||||
let event_fd = eventfd(0, flags).map_err(|err| {
|
||||
match io::Error::from(err) {
|
||||
err if err.kind() == io::ErrorKind::PermissionDenied => {
|
||||
// EPERM can happen if the eventfd isn't initialized yet.
|
||||
// Tell the user to call esp_vfs_eventfd_register.
|
||||
io::Error::new(
|
||||
io::ErrorKind::PermissionDenied,
|
||||
"failed to initialize eventfd for polling, try calling `esp_vfs_eventfd_register`"
|
||||
)
|
||||
},
|
||||
err => err,
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self { event_fd })
|
||||
}
|
||||
|
@ -589,14 +801,14 @@ mod notify {
|
|||
|
||||
/// Notifies the `Poller` instance via the eventfd file descriptor.
|
||||
pub(super) fn notify(&self) -> Result<(), io::Error> {
|
||||
write(&self.event_fd, &1u64.to_ne_bytes())?;
|
||||
write(self.event_fd.as_fd(), &1u64.to_ne_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pops a notification (if any) from the eventfd file descriptor.
|
||||
pub(super) fn pop_notification(&self) -> Result<(), io::Error> {
|
||||
read(&self.event_fd, &mut [0; mem::size_of::<u64>()])?;
|
||||
read(self.event_fd.as_fd(), &mut [0; mem::size_of::<u64>()])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
10
src/port.rs
10
src/port.rs
|
@ -250,4 +250,14 @@ impl EventExtra {
|
|||
pub fn is_pri(&self) -> bool {
|
||||
self.flags.contains(PollFlags::PRI)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_connect_failed(&self) -> Option<bool> {
|
||||
Some(self.flags.contains(PollFlags::ERR) && self.flags.contains(PollFlags::HUP))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_err(&self) -> Option<bool> {
|
||||
Some(self.flags.contains(PollFlags::ERR))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ fn concurrent_modify() -> io::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "vita")))]
|
||||
#[test]
|
||||
fn concurrent_interruption() -> io::Result<()> {
|
||||
struct MakeItSend<T>(T);
|
||||
|
|
Loading…
Reference in New Issue