diff --git a/examples/signal.rs b/examples/signal.rs new file mode 100644 index 0000000..1b2cbbd --- /dev/null +++ b/examples/signal.rs @@ -0,0 +1,65 @@ +#[cfg(all( + any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "dragonfly", + ), + not(polling_test_poll_backend), +))] +fn main() { + use polling::os::kqueue::PollerExt; + use polling::{PollMode, Poller}; + use std::process::Command; + + // Create a new poller. + let poller = Poller::new().unwrap(); + + // Add a signal handler for SIGCHLD. + poller + .add_signal(libc::SIGCHLD, 1, PollMode::Oneshot) + .unwrap(); + + // Spawn a new child process. + let mut child = Command::new("sleep").arg("3").spawn().unwrap(); + + // Wait for the SIGCHLD signal. + let mut events = Vec::new(); + loop { + poller.wait(&mut events, None).unwrap(); + + for ev in events.drain(..) { + // See if we got the SIGCHLD signal. + if ev.readable && ev.key == 1 { + println!("Got SIGCHLD signal!"); + + // Check if the child process has exited. + if let Ok(Some(status)) = child.try_wait() { + println!("Child exited with status: {}", status); + return; + } + } + } + } +} + +#[cfg(not(all( + any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "dragonfly", + ), + not(polling_test_poll_backend), +)))] +fn main() { + eprintln!("This example is only supported on kqueue-compatible OS.") +} diff --git a/src/kqueue.rs b/src/kqueue.rs index 1b7a3b3..848b93f 100644 --- a/src/kqueue.rs +++ b/src/kqueue.rs @@ -80,12 +80,7 @@ impl Poller { log::trace!("add: kqueue_fd={}, fd={}, ev={:?}", self.kqueue_fd, fd, ev); } - let mode_flags = match mode { - PollMode::Oneshot => libc::EV_ONESHOT, - PollMode::Level => 0, - PollMode::Edge => libc::EV_CLEAR, - }; - + let mode_flags = poll_mode_to_flag(mode); let read_flags = if ev.readable { libc::EV_ADD | mode_flags } else { @@ -115,19 +110,58 @@ impl Poller { }, ]; + self.change(changelist) + } + + /// Add a signal to the poller. + pub(super) fn add_signal(&self, signal: i32, token: usize, mode: PollMode) -> io::Result<()> { + log::trace!( + "add_signal: kqueue_fd={}, signal={}, token={}", + self.kqueue_fd, + signal, + token + ); + + let mode_flags = poll_mode_to_flag(mode); + let flags = libc::EV_ADD | libc::EV_RECEIPT | mode_flags; + + // A list of changes for kqueue. + let changelist = [libc::kevent { + ident: signal as _, + filter: libc::EVFILT_SIGNAL, + flags, + udata: token as _, + ..unsafe { mem::zeroed() } + }]; + + self.change(changelist) + } + + /// Submit the following changelist to the kernel. + #[inline] + fn change( + &self, + changelist: impl Copy + AsRef<[libc::kevent]> + AsMut<[libc::kevent]>, + ) -> io::Result<()> { // Apply changes. let mut eventlist = changelist; - syscall!(kevent( - self.kqueue_fd, - changelist.as_ptr() as *const libc::kevent, - changelist.len() as _, - eventlist.as_mut_ptr() as *mut libc::kevent, - eventlist.len() as _, - ptr::null(), - ))?; + + { + let changelist = changelist.as_ref(); + let eventlist = eventlist.as_mut(); + + syscall!(kevent( + self.kqueue_fd, + changelist.as_ptr() as *const libc::kevent, + changelist.len() as _, + eventlist.as_mut_ptr() as *mut libc::kevent, + eventlist.len() as _, + ptr::null(), + ))?; + } // Check for errors. - for ev in &eventlist { + for ev in eventlist.as_mut() { // Explanation for ignoring EPIPE: https://github.com/tokio-rs/mio/issues/582 if (ev.flags & libc::EV_ERROR) != 0 && ev.data != 0 @@ -244,9 +278,18 @@ impl Events { // https://github.com/golang/go/commit/23aad448b1e3f7c3b4ba2af90120bde91ac865b4 self.list[..self.len].iter().map(|ev| Event { key: ev.udata as usize, - readable: ev.filter == libc::EVFILT_READ, + readable: ev.filter == libc::EVFILT_READ || ev.filter == libc::EVFILT_SIGNAL, writable: ev.filter == libc::EVFILT_WRITE || (ev.filter == libc::EVFILT_READ && (ev.flags & libc::EV_EOF) != 0), }) } } + +/// Convert a poll mode to a `kqueue` flag. +fn poll_mode_to_flag(mode: PollMode) -> libc::c_ushort { + match mode { + PollMode::Oneshot => libc::EV_ONESHOT, + PollMode::Level => 0, + PollMode::Edge => libc::EV_DISPATCH, + } +} diff --git a/src/lib.rs b/src/lib.rs index d388283..8256e15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -538,6 +538,70 @@ impl Poller { } } +/// OS-specific functionality for the types in this crate. +pub mod os { + /// OS-specific functionality for platforms that use `kqueue`. + /// + /// These platforms include: + /// + /// - macOS/iOS/tvOS/watchOS + /// - FreeBSD/OpenBSD/NetBSD/DragonFlyBSD + #[cfg(all( + any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "dragonfly", + ), + not(polling_test_poll_backend), + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "dragonfly", + ))) + )] + pub mod kqueue { + use crate::{PollMode, Poller}; + use std::io; + + /// An extension trait for [`Poller`] that uses `kqueue` to implement the [`Poller`]. + /// + /// [`Poller`]: crate::Poller + pub trait PollerExt: __sealed::Sealed { + /// Register a signal in this poller. + /// + /// Once the application receives the signal, it will be returned in the next `wait` call + /// with the provided `key` as a `readable` event. + fn add_signal(&self, signal: i32, key: usize, mode: PollMode) -> io::Result<()>; + } + + impl __sealed::Sealed for Poller {} + + impl PollerExt for Poller { + fn add_signal(&self, signal: i32, key: usize, mode: PollMode) -> io::Result<()> { + self.poller.add_signal(signal, key, mode) + } + } + + mod __sealed { + #[doc(hidden)] + pub trait Sealed {} + } + } +} + #[cfg(all( any( target_os = "linux",