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}