Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Expose kqueue's signal handling functionality #72

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions examples/signal.rs
Original file line number Diff line number Diff line change
@@ -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.")
}
75 changes: 59 additions & 16 deletions src/kqueue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
}
64 changes: 64 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down