mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-06 00:00:10 +02:00
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:
@@ -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
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user