tokio_ctrlc_error/
lib.rs

1//! Easy ctrl+c handling with `failure` and `futures`.
2//!
3//! In many cases, a ctrl+c event from the user is hardly different from
4//! a fatal application error. This crate, inspired by Python's `InterruptedException`
5//! makes it easy to treat ctrl+c in precisely such a way.
6//!
7//!
8//! # Examples
9//! ```
10//!     use futures::prelude::*;
11//!     use tokio_ctrlc_error::AsyncCtrlc;
12//!
13//!     fn lengthy_task() -> impl Future<Item = (), Error = failure::Error> {
14//!         futures::future::ok(())
15//!     }
16//!
17//!     let task = lengthy_task().ctrlc_as_error();
18//!     let mut rt = tokio::runtime::Runtime::new().unwrap();
19//!     let res = rt.block_on(task);
20//!     println!("{:?}", res);
21//! ```
22//!
23//! # Usage notes
24//! `ctrlc_as_error` has the same semantics as `select` and will return either
25//! the result of the future or an `KeyboardInterrupt` error, whichever occurs
26//! first. In particular, the interrupt is intercepted **only for those futures
27//! in the chain that precede the call**. For example:
28//!
29//! ```
30//!     use std::time::Duration;
31//!     use futures::prelude::*;
32//!     use tokio_ctrlc_error::AsyncCtrlc;
33//!
34//!     fn sleep() -> impl Future<Item = (), Error = failure::Error> {
35//!         // The sleep is very short, so that the tests don't take too much time
36//!         tokio_timer::sleep(Duration::from_millis(1)).from_err()
37//!     }
38//!
39//!     let task = sleep()
40//!         .ctrlc_as_error()
41//!         .and_then(|_| sleep());
42//!     let mut rt = tokio::runtime::Runtime::new().unwrap();
43//!     let res = rt.block_on(task);
44//! ```
45//!
46//! Here, the interrupt will be handled only during the first sleep.
47//! During the second sleep, the default handling of the signal will take place.
48
49use failure::Fail;
50use futures::{prelude::*, FlattenStream};
51use tokio_signal::{IoFuture, IoStream};
52
53#[derive(Debug, Fail)]
54#[fail(display = "keyboard interrupt")]
55pub struct KeyboardInterrupt;
56
57#[derive(Debug, Fail)]
58#[fail(display = "I/O error handling ctrl+c: {}", _0)]
59pub struct IoError(std::io::Error);
60
61pub struct CtrlcAsError<F> {
62    // we will switch to `struct CtrlC` in tokio 0.3
63    ctrlc: FlattenStream<IoFuture<IoStream<()>>>,
64    future: F,
65}
66
67impl<F: Future> Future for CtrlcAsError<F>
68where
69    F::Error: From<KeyboardInterrupt> + From<IoError>,
70{
71    type Error = F::Error;
72    type Item = F::Item;
73
74    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
75        let ctrlc_fut = self.ctrlc.poll().map_err(IoError)?;
76        if ctrlc_fut.is_ready() {
77            Err(KeyboardInterrupt.into())
78        } else {
79            self.future.poll().map_err(Into::into)
80        }
81    }
82}
83
84pub trait AsyncCtrlc<F: Future> {
85    /// Intercept ctrl+c during execution and return an error in such case.
86    fn ctrlc_as_error(self) -> CtrlcAsError<F>;
87}
88
89impl<F: Future> AsyncCtrlc<F> for F
90where
91    F::Error: From<KeyboardInterrupt> + From<IoError>,
92{
93    fn ctrlc_as_error(self) -> CtrlcAsError<F> {
94        CtrlcAsError {
95            ctrlc: tokio_signal::ctrl_c().flatten_stream(),
96            future: self,
97        }
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::AsyncCtrlc;
104    use futures::prelude::*;
105
106    // Test if it compiles when used with the multi-threaded runtime
107    #[test]
108    fn test_send_future() {
109        use tokio::runtime::Runtime;
110        fn get_fut() -> Box<dyn Future<Item = (), Error = failure::Error> + Send> {
111            let f = futures::future::ok(());
112            Box::new(f)
113        }
114
115        let future = get_fut().ctrlc_as_error();
116        let mut rt = Runtime::new().unwrap();
117        rt.block_on(future).unwrap();
118    }
119
120    // Test if it compiles when used with the single-threaded runtime
121    #[test]
122    fn test_non_send_future() {
123        use tokio::runtime::current_thread::Runtime;
124        fn get_fut() -> Box<dyn Future<Item = (), Error = failure::Error>> {
125            let f = futures::future::ok(());
126            Box::new(f)
127        }
128
129        let future = get_fut().ctrlc_as_error();
130        let mut rt = Runtime::new().unwrap();
131        rt.block_on(future).unwrap();
132    }
133
134}