signal: Replace ctrl_c with a CtrlC struct (#1273)

* Add a new `CtrlC` struct which will represent a stream of SIGINT
signals on Unix or the CTRL_C event on Windows
* `CtrlC` implements `Stream<Output = ()>` rather than `IoSteam` as
previously
This commit is contained in:
Ivan Petkov
2019-07-09 08:48:46 -07:00
committed by GitHub
parent 407d15cf93
commit 461eebe612
13 changed files with 129 additions and 116 deletions
+55
View File
@@ -0,0 +1,55 @@
#[cfg(unix)]
use crate::unix::Signal as Inner;
#[cfg(windows)]
use crate::windows::Event as Inner;
use crate::IoFuture;
use futures_core::stream::Stream;
use futures_util::future::FutureExt;
use futures_util::try_future::TryFutureExt;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio_reactor::Handle;
/// Represents a stream which receives "ctrl-c" notifications sent to the process.
///
/// In general signals are handled very differently across Unix and Windows, but
/// this is somewhat cross platform in terms of how it can be handled. A ctrl-c
/// event to a console process can be represented as a stream for both Windows
/// and Unix.
///
/// Note that there are a number of caveats listening for signals, and you may
/// wish to read up on the documentation in the `unix` or `windows` module to
/// take a peek.
#[must_use = "streams do nothing unless polled"]
#[derive(Debug)]
pub struct CtrlC {
inner: Inner,
}
impl CtrlC {
/// Creates a new stream which receives "ctrl-c" notifications sent to the
/// process.
///
/// This function binds to the default reactor.
pub fn new() -> IoFuture<Self> {
Self::with_handle(&Handle::default())
}
/// Creates a new stream which receives "ctrl-c" notifications sent to the
/// process.
///
/// This function binds to reactor specified by `handle`.
pub fn with_handle(handle: &Handle) -> IoFuture<CtrlC> {
Inner::ctrl_c(handle).map_ok(|inner| Self { inner }).boxed()
}
}
impl Stream for CtrlC {
type Item = ();
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.inner)
.poll_next(cx)
.map(|item| item.map(|_| ()))
}
}
+7 -57
View File
@@ -1,5 +1,5 @@
#![doc(html_root_url = "https://docs.rs/tokio-signal/0.2.8")]
#![deny(missing_docs, rust_2018_idioms)]
#![deny(missing_debug_implementations, missing_docs, rust_2018_idioms)]
#![cfg_attr(test, deny(warnings))]
#![cfg_attr(test, feature(async_await))]
#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))]
@@ -33,12 +33,10 @@
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Create an infinite stream of "Ctrl+C" notifications. Each item received
//! // on this stream may represent multiple ctrl-c signals.
//! let ctrl_c = tokio_signal::ctrl_c().await?;
//! let ctrl_c = tokio_signal::CtrlC::new().await?;
//!
//! // Process each ctrl-c as it comes in
//! let prog = ctrl_c.for_each(|event| {
//! event.expect("failed to get event");
//!
//! let prog = ctrl_c.for_each(|_| {
//! println!("ctrl-c received!");
//! future::ready(())
//! });
@@ -62,12 +60,10 @@
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Create an infinite stream of "Ctrl+C" notifications. Each item received
//! // on this stream may represent multiple ctrl-c signals.
//! let ctrl_c = tokio_signal::ctrl_c().await?;
//! let ctrl_c = tokio_signal::CtrlC::new().await?;
//!
//! // Process each ctrl-c as it comes in
//! let prog = ctrl_c.for_each(|event| {
//! event.expect("failed to get event");
//!
//! let prog = ctrl_c.for_each(|_| {
//! println!("ctrl-c received!");
//! future::ready(())
//! });
@@ -90,13 +86,10 @@ extern crate lazy_static;
use futures_core::future::Future;
use futures_core::stream::Stream;
use futures_util::future::FutureExt;
use futures_util::stream::StreamExt;
use futures_util::try_future::TryFutureExt;
use std::io;
use std::pin::Pin;
use tokio_reactor::Handle;
mod ctrl_c;
mod registry;
mod os {
@@ -114,47 +107,4 @@ pub type IoFuture<T> = Pin<Box<dyn Future<Output = io::Result<T>> + Send>>;
/// A stream whose item is `io::Result<T>`
pub type IoStream<T> = Pin<Box<dyn Stream<Item = io::Result<T>> + Send>>;
/// Creates a stream which receives "ctrl-c" notifications sent to a process.
///
/// In general signals are handled very differently across Unix and Windows, but
/// this is somewhat cross platform in terms of how it can be handled. A ctrl-c
/// event to a console process can be represented as a stream for both Windows
/// and Unix.
///
/// This function binds to the default event loop. Note that
/// there are a number of caveats listening for signals, and you may wish to
/// read up on the documentation in the `unix` or `windows` module to take a
/// peek.
pub fn ctrl_c() -> IoFuture<IoStream<()>> {
ctrl_c_handle(&Handle::default())
}
/// Creates a stream which receives "ctrl-c" notifications sent to a process.
///
/// In general signals are handled very differently across Unix and Windows, but
/// this is somewhat cross platform in terms of how it can be handled. A ctrl-c
/// event to a console process can be represented as a stream for both Windows
/// and Unix.
///
/// This function receives a `Handle` to an event loop and returns a future
/// which when resolves yields a stream receiving all signal events. Note that
/// there are a number of caveats listening for signals, and you may wish to
/// read up on the documentation in the `unix` or `windows` module to take a
/// peek.
pub fn ctrl_c_handle(handle: &Handle) -> IoFuture<IoStream<()>> {
return ctrl_c_imp(handle);
#[cfg(unix)]
fn ctrl_c_imp(handle: &Handle) -> IoFuture<IoStream<()>> {
unix::Signal::with_handle(unix::libc::SIGINT, &handle)
.map_ok(|signal| -> IoStream<()> { signal.map(|_| Ok(())).boxed() })
.boxed()
}
#[cfg(windows)]
fn ctrl_c_imp(handle: &Handle) -> IoFuture<IoStream<()>> {
windows::Event::ctrl_c_handle(&handle)
.map_ok(|event| -> IoStream<()> { event.map(|_| Ok(())).boxed() })
.boxed()
}
}
pub use ctrl_c::CtrlC;
-1
View File
@@ -252,7 +252,6 @@ mod tests {
drop(second_rx);
let (fire, wait) = oneshot::channel();
//let mut rt = Runtime::new().unwrap();
tokio::spawn(async {
wait.await.expect("wait failed");
+35
View File
@@ -159,6 +159,7 @@ fn signal_enable(signal: c_int) -> io::Result<()> {
}
}
#[derive(Debug)]
struct Driver {
wakeup: PollEvented<UnixStream>,
}
@@ -249,6 +250,8 @@ impl Driver {
/// If you've got any questions about this feel free to open an issue on the
/// repo, though, as I'd love to chat about this! In other words, I'd love to
/// alleviate some of these limitations if possible!
#[must_use = "streams do nothing unless polled"]
#[derive(Debug)]
pub struct Signal {
driver: Driver,
signal: c_int,
@@ -326,6 +329,10 @@ impl Signal {
})
.boxed()
}
pub(crate) fn ctrl_c(handle: &Handle) -> IoFuture<Signal> {
Self::with_handle(libc::SIGINT, handle)
}
}
impl Stream for Signal {
@@ -341,6 +348,11 @@ impl Stream for Signal {
#[cfg(test)]
mod tests {
use super::*;
use futures_util::future::FutureExt;
use futures_util::StreamExt;
use std::time::Duration;
use tokio_sync::oneshot;
use tokio_timer::Timeout;
#[test]
fn signal_enable_error_on_invalid_input() {
@@ -351,4 +363,27 @@ mod tests {
fn signal_enable_error_on_forbidden_input() {
signal_enable(signal_hook_registry::FORBIDDEN[0]).unwrap_err();
}
fn with_timeout<F: Future>(future: F) -> impl Future<Output = F::Output> {
Timeout::new(future, Duration::from_secs(1)).map(|result| result.expect("timed out"))
}
#[tokio::test]
async fn ctrl_c() {
let ctrl_c = with_timeout(crate::CtrlC::new())
.await
.expect("failed to init ctrl_c");
let (fire, wait) = oneshot::channel();
// NB: simulate a signal coming in by exercising our signal handler
// to avoid complications with sending SIGINT to the test process
tokio::spawn(async {
wait.await.expect("wait failed");
action(globals(), libc::SIGINT);
});
let _ = fire.send(());
let _ = with_timeout(ctrl_c.into_future()).await;
}
}
+6 -12
View File
@@ -89,6 +89,8 @@ static INIT: Once = ONCE_INIT;
/// received back-to-back, then the stream may only receive one item about the
/// two notifications.
// FIXME: refactor and combine with unix::Signal
#[must_use = "streams do nothing unless polled"]
#[derive(Debug)]
pub struct Event {
rx: Receiver<()>,
}
@@ -103,15 +105,7 @@ impl Event {
///
/// This function will register a handler via `SetConsoleCtrlHandler` and
/// deliver notifications to the returned stream.
pub fn ctrl_c() -> IoFuture<Event> {
Event::ctrl_c_handle(&Handle::default())
}
/// Creates a new stream listening for the `CTRL_C_EVENT` events.
///
/// This function will register a handler via `SetConsoleCtrlHandler` and
/// deliver notifications to the returned stream.
pub fn ctrl_c_handle(handle: &Handle) -> IoFuture<Event> {
pub(crate) fn ctrl_c(handle: &Handle) -> IoFuture<Event> {
Event::new(CTRL_C_EVENT, handle)
}
@@ -224,7 +218,7 @@ mod tests {
// first event loop cannot go away
let mut rt = current_thread::Runtime::new().unwrap();
let event_ctrl_c = rt
.block_on(with_timeout(Event::ctrl_c()))
.block_on(with_timeout(crate::CtrlC::new()))
.expect("failed to run future");
// Windows doesn't have a good programmatic way of sending events
@@ -234,7 +228,7 @@ mod tests {
super::handler(CTRL_C_EVENT);
}
rt.block_on(with_timeout(event_ctrl_c.into_future()));
let _ = rt.block_on(with_timeout(event_ctrl_c.into_future()));
let event_ctrl_break = rt
.block_on(with_timeout(Event::ctrl_break()))
@@ -244,6 +238,6 @@ mod tests {
super::handler(CTRL_BREAK_EVENT);
}
rt.block_on(with_timeout(event_ctrl_break.into_future()));
let _ = rt.block_on(with_timeout(event_ctrl_break.into_future()));
}
}