rt: signal driver now uses I/O driver directly (#5125)

The signal driver uses a `UnixStream` to receive signal events.
Previously, the signal driver used `PollEvented` internally to receive
events on the `UnixStream`. However, using `PollEvented` from
within a runtime driver created a circular link between the runtime and
the `PollEvented` instance.

This patch replaces `PollEvented` usage in favor of accessing the I/O
driver directly. The I/O driver now reserves a token for signal-related
events and tracks signal readiness internally. The signal driver queries
the I/O driver to check for signal-related readiness.
This commit is contained in:
Carl Lerche
2022-10-26 10:17:59 -07:00
committed by GitHub
parent 1ca17bef67
commit ec66a92b01
5 changed files with 58 additions and 55 deletions
+6
View File
@@ -178,6 +178,12 @@
//! [`Sink`]: https://docs.rs/futures/0.3/futures/sink/trait.Sink.html //! [`Sink`]: https://docs.rs/futures/0.3/futures/sink/trait.Sink.html
//! [`Stream`]: https://docs.rs/futures/0.3/futures/stream/trait.Stream.html //! [`Stream`]: https://docs.rs/futures/0.3/futures/stream/trait.Stream.html
//! [`Write`]: std::io::Write //! [`Write`]: std::io::Write
#![cfg_attr(
not(all(feature = "rt", feature = "net")),
allow(dead_code, unused_imports)
)]
cfg_io_blocking! { cfg_io_blocking! {
pub(crate) mod blocking; pub(crate) mod blocking;
} }
+1 -5
View File
@@ -119,11 +119,7 @@ impl<E: Source> PollEvented<E> {
} }
/// Returns a reference to the registration. /// Returns a reference to the registration.
#[cfg(any( #[cfg(any(feature = "net"))]
feature = "net",
all(unix, feature = "process"),
all(unix, feature = "signal"),
))]
pub(crate) fn registration(&self) -> &Registration { pub(crate) fn registration(&self) -> &Registration {
&self.registration &self.registration
} }
+27 -2
View File
@@ -1,4 +1,4 @@
#![cfg_attr(not(feature = "rt"), allow(dead_code))] #![cfg_attr(not(all(feature = "rt", feature = "net")), allow(dead_code))]
mod registration; mod registration;
pub(crate) use registration::Registration; pub(crate) use registration::Registration;
@@ -26,6 +26,9 @@ pub(crate) struct Driver {
/// as it is mostly used to determine when to call `compact()`. /// as it is mostly used to determine when to call `compact()`.
tick: u8, tick: u8,
/// True when an event with the signal token is received
signal_ready: bool,
/// Reuse the `mio::Events` value across calls to poll. /// Reuse the `mio::Events` value across calls to poll.
events: mio::Events, events: mio::Events,
@@ -86,6 +89,7 @@ enum Tick {
// TODO: Don't use a fake token. Instead, reserve a slot entry for the wakeup // TODO: Don't use a fake token. Instead, reserve a slot entry for the wakeup
// token. // token.
const TOKEN_WAKEUP: mio::Token = mio::Token(1 << 31); const TOKEN_WAKEUP: mio::Token = mio::Token(1 << 31);
const TOKEN_SIGNAL: mio::Token = mio::Token(1 + (1 << 31));
const ADDRESS: bit::Pack = bit::Pack::least_significant(24); const ADDRESS: bit::Pack = bit::Pack::least_significant(24);
@@ -119,6 +123,7 @@ impl Driver {
Ok(Driver { Ok(Driver {
tick: 0, tick: 0,
signal_ready: false,
events: mio::Events::with_capacity(1024), events: mio::Events::with_capacity(1024),
poll, poll,
resources: slab, resources: slab,
@@ -193,7 +198,11 @@ impl Driver {
for event in events.iter() { for event in events.iter() {
let token = event.token(); let token = event.token();
if token != TOKEN_WAKEUP { if token == TOKEN_WAKEUP {
// Nothing to do, the event is used to unblock the I/O driver
} else if token == TOKEN_SIGNAL {
self.signal_ready = true;
} else {
Self::dispatch( Self::dispatch(
&mut self.resources, &mut self.resources,
self.tick, self.tick,
@@ -378,3 +387,19 @@ impl Direction {
} }
} }
} }
// Signal handling
cfg_signal_internal_and_unix! {
impl Driver {
pub(crate) fn register_signal_receiver(&mut self, receiver: &mut mio::net::UnixStream) -> io::Result<()> {
self.inner.registry.register(receiver, TOKEN_SIGNAL, mio::Interest::READABLE)?;
Ok(())
}
pub(crate) fn consume_signal_ready(&mut self) -> bool {
let ret = self.signal_ready;
self.signal_ready = false;
ret
}
}
}
+21 -43
View File
@@ -2,16 +2,12 @@
//! Signal driver //! Signal driver
use crate::io::interest::Interest;
use crate::io::PollEvented;
use crate::runtime::io; use crate::runtime::io;
use crate::signal::registry::globals; use crate::signal::registry::globals;
use mio::net::UnixStream; use mio::net::UnixStream;
use std::io::{self as std_io, Read}; use std::io::{self as std_io, Read};
use std::ptr;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use std::time::Duration; use std::time::Duration;
/// Responsible for registering wakeups when an OS signal is received, and /// Responsible for registering wakeups when an OS signal is received, and
@@ -22,10 +18,10 @@ use std::time::Duration;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Driver { pub(crate) struct Driver {
/// Thread parker. The `Driver` park implementation delegates to this. /// Thread parker. The `Driver` park implementation delegates to this.
park: io::Driver, io: io::Driver,
/// A pipe for receiving wake events from the signal handler /// A pipe for receiving wake events from the signal handler
receiver: PollEvented<UnixStream>, receiver: UnixStream,
/// Shared state /// Shared state
inner: Arc<Inner>, inner: Arc<Inner>,
@@ -43,7 +39,7 @@ pub(super) struct Inner(());
impl Driver { impl Driver {
/// Creates a new signal `Driver` instance that delegates wakeups to `park`. /// Creates a new signal `Driver` instance that delegates wakeups to `park`.
pub(crate) fn new(park: io::Driver) -> std_io::Result<Self> { pub(crate) fn new(mut io: io::Driver) -> std_io::Result<Self> {
use std::mem::ManuallyDrop; use std::mem::ManuallyDrop;
use std::os::unix::io::{AsRawFd, FromRawFd}; use std::os::unix::io::{AsRawFd, FromRawFd};
@@ -55,12 +51,11 @@ impl Driver {
// I'm not sure if the second (failed) registration simply doesn't end // I'm not sure if the second (failed) registration simply doesn't end
// up receiving wake up notifications, or there could be some race // up receiving wake up notifications, or there could be some race
// condition when consuming readiness events, but having distinct // condition when consuming readiness events, but having distinct
// descriptors for distinct PollEvented instances appears to mitigate // descriptors appears to mitigate this.
// this.
// //
// Unfortunately we cannot just use a single global PollEvented instance // Unfortunately we cannot just use a single global UnixStream instance
// either, since we can't compare Handles or assume they will always // either, since we can't assume they will always be registered with the
// point to the exact same reactor. // exact same reactor.
// //
// Mio 0.7 removed `try_clone()` as an API due to unexpected behavior // Mio 0.7 removed `try_clone()` as an API due to unexpected behavior
// with registering dups with the same reactor. In this case, duping is // with registering dups with the same reactor. In this case, duping is
@@ -73,12 +68,12 @@ impl Driver {
// safety: there is nothing unsafe about this, but the `from_raw_fd` fn is marked as unsafe. // safety: there is nothing unsafe about this, but the `from_raw_fd` fn is marked as unsafe.
let original = let original =
ManuallyDrop::new(unsafe { std::os::unix::net::UnixStream::from_raw_fd(receiver_fd) }); ManuallyDrop::new(unsafe { std::os::unix::net::UnixStream::from_raw_fd(receiver_fd) });
let receiver = UnixStream::from_std(original.try_clone()?); let mut receiver = UnixStream::from_std(original.try_clone()?);
let receiver =
PollEvented::new_with_interest_and_handle(receiver, Interest::READABLE, park.handle())?; io.register_signal_receiver(&mut receiver)?;
Ok(Self { Ok(Self {
park, io,
receiver, receiver,
inner: Arc::new(Inner(())), inner: Arc::new(Inner(())),
}) })
@@ -93,38 +88,31 @@ impl Driver {
} }
pub(crate) fn park(&mut self) { pub(crate) fn park(&mut self) {
self.park.park(); self.io.park();
self.process(); self.process();
} }
pub(crate) fn park_timeout(&mut self, duration: Duration) { pub(crate) fn park_timeout(&mut self, duration: Duration) {
self.park.park_timeout(duration); self.io.park_timeout(duration);
self.process(); self.process();
} }
pub(crate) fn shutdown(&mut self) { pub(crate) fn shutdown(&mut self) {
self.park.shutdown() self.io.shutdown()
} }
fn process(&self) { fn process(&mut self) {
// Check if the pipe is ready to read and therefore has "woken" us up // If the signal pipe has not recieved a readiness event, then there is
// // nothing else to do.
// To do so, we will `poll_read_ready` with a noop waker, since we don't if !self.io.consume_signal_ready() {
// need to actually be notified when read ready... return;
let waker = unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE)) }; }
let mut cx = Context::from_waker(&waker);
let ev = match self.receiver.registration().poll_read_ready(&mut cx) {
Poll::Ready(Ok(ev)) => ev,
Poll::Ready(Err(e)) => panic!("reactor gone: {}", e),
Poll::Pending => return, // No wake has arrived, bail
};
// Drain the pipe completely so we can receive a new readiness event // Drain the pipe completely so we can receive a new readiness event
// if another signal has come in. // if another signal has come in.
let mut buf = [0; 128]; let mut buf = [0; 128];
loop { loop {
match (&*self.receiver).read(&mut buf) { match self.receiver.read(&mut buf) {
Ok(0) => panic!("EOF on self-pipe"), Ok(0) => panic!("EOF on self-pipe"),
Ok(_) => continue, // Keep reading Ok(_) => continue, // Keep reading
Err(e) if e.kind() == std_io::ErrorKind::WouldBlock => break, Err(e) if e.kind() == std_io::ErrorKind::WouldBlock => break,
@@ -132,21 +120,11 @@ impl Driver {
} }
} }
self.receiver.registration().clear_readiness(ev);
// Broadcast any signals which were received // Broadcast any signals which were received
globals().broadcast(); globals().broadcast();
} }
} }
const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(noop_clone, noop, noop, noop);
unsafe fn noop_clone(_data: *const ()) -> RawWaker {
RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE)
}
unsafe fn noop(_data: *const ()) {}
// ===== impl Handle ===== // ===== impl Handle =====
impl Handle { impl Handle {
+3 -5
View File
@@ -375,20 +375,18 @@ fn io_driver_fd_count() {
let rt = current_thread(); let rt = current_thread();
let metrics = rt.metrics(); let metrics = rt.metrics();
// Since this is enabled w/ the process driver we always assert_eq!(metrics.io_driver_fd_registered_count(), 0);
// have 1 fd registered.
assert_eq!(metrics.io_driver_fd_registered_count(), 1);
let stream = tokio::net::TcpStream::connect("google.com:80"); let stream = tokio::net::TcpStream::connect("google.com:80");
let stream = rt.block_on(async move { stream.await.unwrap() }); let stream = rt.block_on(async move { stream.await.unwrap() });
assert_eq!(metrics.io_driver_fd_registered_count(), 2); assert_eq!(metrics.io_driver_fd_registered_count(), 1);
assert_eq!(metrics.io_driver_fd_deregistered_count(), 0); assert_eq!(metrics.io_driver_fd_deregistered_count(), 0);
drop(stream); drop(stream);
assert_eq!(metrics.io_driver_fd_deregistered_count(), 1); assert_eq!(metrics.io_driver_fd_deregistered_count(), 1);
assert_eq!(metrics.io_driver_fd_registered_count(), 2); assert_eq!(metrics.io_driver_fd_registered_count(), 1);
} }
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]