signal: migrate to std::futures (#1218)

Migrate to std::futures and the futures 0.3 preview and use async/await
where possible

**Breaking change:** the IoFuture and IoStream definitions used to refer
to Box<dyn Future> and Box<dyn Stream>, but now they are defined as
Pin<...> versions which are technically breaking.

No other breaking or functional changes have been made
This commit is contained in:
Ivan Petkov
2019-07-03 10:40:59 -07:00
committed by Carl Lerche
parent bd9760e124
commit cbad83f362
19 changed files with 304 additions and 338 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ members = [
"tokio-io", "tokio-io",
"tokio-macros", "tokio-macros",
"tokio-reactor", "tokio-reactor",
# "tokio-signal", "tokio-signal",
"tokio-sync", "tokio-sync",
"tokio-test", "tokio-test",
"tokio-threadpool", "tokio-threadpool",
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
crates: crates:
# - tokio-fs # - tokio-fs
tokio-reactor: [] tokio-reactor: []
# - tokio-signal tokio-signal: []
tokio-tcp: tokio-tcp:
- incoming - incoming
# - tokio-tls # - tokio-tls
+4 -1
View File
@@ -26,11 +26,13 @@ travis-ci = { repository = "tokio-rs/tokio" }
appveyor = { repository = "carllerche/tokio", id = "s83yxhy9qeb58va7" } appveyor = { repository = "carllerche/tokio", id = "s83yxhy9qeb58va7" }
[dependencies] [dependencies]
futures = "0.1.11" futures-core-preview = "0.3.0-alpha.16"
futures-util-preview = "0.3.0-alpha.16"
lazy_static = "1" lazy_static = "1"
tokio-reactor = { version = "0.2.0", path = "../tokio-reactor" } tokio-reactor = { version = "0.2.0", path = "../tokio-reactor" }
tokio-executor = { version = "0.2.0", path = "../tokio-executor" } tokio-executor = { version = "0.2.0", path = "../tokio-executor" }
tokio-io = { version = "0.2.0", path = "../tokio-io" } tokio-io = { version = "0.2.0", path = "../tokio-io" }
tokio-sync = { version = "0.2.0", path = "../tokio-sync" }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2" libc = "0.2"
@@ -41,6 +43,7 @@ signal-hook-registry = "~1"
[dev-dependencies] [dev-dependencies]
tokio = { version = "0.2.0", path = "../tokio" } tokio = { version = "0.2.0", path = "../tokio" }
tokio-timer = { version = "0.3.0", path = "../tokio-timer" } tokio-timer = { version = "0.3.0", path = "../tokio-timer" }
tokio-sync = { version = "0.2.0", path = "../tokio-sync", features = ["async-traits"]}
[target.'cfg(windows)'.dependencies.winapi] [target.'cfg(windows)'.dependencies.winapi]
version = "0.3" version = "0.3"
+24 -30
View File
@@ -1,22 +1,20 @@
#![deny(warnings, rust_2018_idioms)] #![deny(warnings, rust_2018_idioms)]
#![feature(async_await)]
use tokio; use futures_util::future;
use tokio_signal; use futures_util::stream::StreamExt;
use futures::{Future, Stream};
/// how many signals to handle before exiting /// how many signals to handle before exiting
const STOP_AFTER: u64 = 10; const STOP_AFTER: u64 = 10;
fn main() -> Result<(), Box<dyn std::error::Error>> { #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// tokio_signal provides a convenience builder for Ctrl+C // tokio_signal provides a convenience builder for Ctrl+C
// this even works cross-platform: linux and windows! // this even works cross-platform: linux and windows!
// //
// `fn ctrl_c()` produces a `Future` of the actual stream-initialisation // `fn ctrl_c()` produces a `Future` of the actual stream-initialisation
// the `flatten_stream()` convenience method lazily defers that // so first we await until the signal is ready.
// initialisation, allowing us to use it 'as if' it is already the let endless_stream = tokio_signal::ctrl_c().await?;
// stream we want, reducing boilerplate Future-handling.
let endless_stream = tokio_signal::ctrl_c().flatten_stream();
// don't keep going forever: convert the endless stream to a bounded one. // don't keep going forever: convert the endless stream to a bounded one.
let limited_stream = endless_stream.take(STOP_AFTER); let limited_stream = endless_stream.take(STOP_AFTER);
@@ -36,30 +34,26 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Stream::for_each is a powerful primitive provided by the Futures crate. // Stream::for_each is a powerful primitive provided by the Futures crate.
// It turns a Stream into a Future that completes after all stream-items // It turns a Stream into a Future that completes after all stream-items
// have been completed, or the first time the closure returns an error // have been completed, or the first time the closure returns an error
let future = limited_stream.for_each(|()| { let future = limited_stream
// Note how we manipulate the counter without any fancy synchronisation. .map(|result| result.expect("failed to get event"))
// The borrowchecker realises there can't be any conflicts, so the closure .for_each(|()| {
// can just capture it. // Note how we manipulate the counter without any fancy synchronisation.
counter += 1; // The borrowchecker realises there can't be any conflicts, so the closure
println!( // can just capture it.
"Ctrl+C received {} times! {} more before exit", counter += 1;
counter, println!(
STOP_AFTER - counter "Ctrl+C received {} times! {} more before exit",
); counter,
STOP_AFTER - counter
);
// return Ok-result to continue handling the stream // return a result to continue handling the stream
Ok(()) future::ready(())
}); });
// Up until now, we haven't really DONE anything, just prepared // Up until now, we haven't really DONE anything, just prepared
// now it's time to actually schedule, and thus execute, the stream // now it's time to actually the results!
// on our event loop future.await;
// FIXME(1000): windows uses a global driver task which doesn't terminate
// on its own, so if we use block_on_all our application will never exit
//tokio::runtime::current_thread::block_on_all(future)?;
tokio::runtime::current_thread::Runtime::new()
.expect("failed to start runtime on current thread")
.block_on(future)?;
println!("Stream ended, quiting the program."); println!("Stream ended, quiting the program.");
Ok(()) Ok(())
+16 -10
View File
@@ -1,21 +1,25 @@
#![deny(warnings, rust_2018_idioms)] #![deny(warnings, rust_2018_idioms)]
#![feature(async_await)]
//! A small example of how to listen for two signals at the same time //! A small example of how to listen for two signals at the same time
use std::error::Error;
// A trick to not fail build on non-unix platforms when using unix-specific features. // A trick to not fail build on non-unix platforms when using unix-specific features.
#[cfg(unix)] #[cfg(unix)]
mod platform { mod platform {
use futures::{Future, Stream}; use futures_util::stream::{self, StreamExt};
use std::error::Error;
use tokio_signal::unix::{Signal, SIGINT, SIGTERM}; use tokio_signal::unix::{Signal, SIGINT, SIGTERM};
pub fn main() -> Result<(), Box<dyn (::std::error::Error)>> { pub async fn main() -> Result<(), Box<dyn Error>> {
// Create a stream for each of the signals we'd like to handle. // Create a stream for each of the signals we'd like to handle.
let sigint = Signal::new(SIGINT).flatten_stream(); let sigint = Signal::new(SIGINT).await?;
let sigterm = Signal::new(SIGTERM).flatten_stream(); let sigterm = Signal::new(SIGTERM).await?;
// Use the `select` combinator to merge these two streams into one // Use the `select` combinator to merge these two streams into one
let stream = sigint.select(sigterm); let stream = stream::select(sigint, sigterm);
// Wait for a signal to arrive // Wait for a signal to arrive
println!("Waiting for SIGINT or SIGTERM"); println!("Waiting for SIGINT or SIGTERM");
@@ -24,8 +28,7 @@ mod platform {
to send a SIGINT to all processes named 'multiple' \ to send a SIGINT to all processes named 'multiple' \
(i.e. this binary)" (i.e. this binary)"
); );
let (item, _rest) = ::tokio::runtime::current_thread::block_on_all(stream.into_future()) let (item, _rest) = stream.into_future().await;
.map_err(|_| "failed to wait for signals")?;
// Figure out which signal we received // Figure out which signal we received
let item = item.ok_or("received no signal")?; let item = item.ok_or("received no signal")?;
@@ -35,6 +38,7 @@ mod platform {
assert_eq!(item, SIGTERM); assert_eq!(item, SIGTERM);
println!("received SIGTERM"); println!("received SIGTERM");
} }
Ok(()) Ok(())
} }
@@ -42,11 +46,13 @@ mod platform {
#[cfg(not(unix))] #[cfg(not(unix))]
mod platform { mod platform {
pub fn main() -> Result<(), Box<dyn std::error::Error>> { use std::error::Error;
pub async fn main() -> Result<(), Box<dyn Error>> {
Ok(()) Ok(())
} }
} }
fn main() -> Result<(), Box<dyn std::error::Error>> { #[tokio::main]
platform::main() async fn main() -> Result<(), Box<dyn Error>> {
platform::main().await
} }
+18 -11
View File
@@ -1,15 +1,20 @@
#![deny(warnings, rust_2018_idioms)] #![deny(warnings, rust_2018_idioms)]
#![feature(async_await)]
use std::error::Error;
// A trick to not fail build on non-unix platforms when using unix-specific features. // A trick to not fail build on non-unix platforms when using unix-specific features.
#[cfg(unix)] #[cfg(unix)]
mod platform { mod platform {
use futures::{Future, Stream}; use futures_util::future;
use futures_util::stream::StreamExt;
use std::error::Error;
use tokio_signal::unix::{Signal, SIGHUP}; use tokio_signal::unix::{Signal, SIGHUP};
pub fn main() -> Result<(), Box<dyn (::std::error::Error)>> { pub async fn main() -> Result<(), Box<dyn Error>> {
// on Unix, we can listen to whatever signal we want, in this case: SIGHUP // on Unix, we can listen to whatever signal we want, in this case: SIGHUP
let stream = Signal::new(SIGHUP).flatten_stream(); let stream = Signal::new(SIGHUP).await?;
println!("Waiting for SIGHUPS (Ctrl+C to quit)"); println!("Waiting for SIGHUPS (Ctrl+C to quit)");
println!( println!(
@@ -27,25 +32,27 @@ mod platform {
or something", or something",
the_signal the_signal
); );
Ok(())
future::ready(())
}); });
// Up until now, we haven't really DONE anything, just prepared // Up until now, we haven't really DONE anything, just prepared
// now it's time to actually schedule, and thus execute, the stream // now it's time to actually the results!
// on our event loop, and loop forever future.await;
::tokio::runtime::current_thread::block_on_all(future)?;
Ok(()) Ok(())
} }
} }
#[cfg(not(unix))] #[cfg(not(unix))]
mod platform { mod platform {
pub fn main() -> Result<(), Box<dyn ::std::error::Error>> { use std::error::Error;
pub async fn main() -> Result<(), Box<dyn Error>> {
Ok(()) Ok(())
} }
} }
fn main() -> Result<(), Box<dyn std::error::Error>> { #[tokio::main]
platform::main() async fn main() -> Result<(), Box<dyn Error>> {
platform::main().await
} }
+65 -35
View File
@@ -1,6 +1,7 @@
#![doc(html_root_url = "https://docs.rs/tokio-signal/0.2.8")] #![doc(html_root_url = "https://docs.rs/tokio-signal/0.2.8")]
#![deny(missing_docs, rust_2018_idioms)] #![deny(missing_docs, rust_2018_idioms)]
#![cfg_attr(test, deny(warnings))] #![cfg_attr(test, deny(warnings))]
#![cfg_attr(test, feature(async_await))]
#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))] #![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))]
//! Asynchronous signal handling for Tokio //! Asynchronous signal handling for Tokio
@@ -23,43 +24,77 @@
//! Print out all ctrl-C notifications received //! Print out all ctrl-C notifications received
//! //!
//! ```rust,no_run //! ```rust,no_run
//! use futures::{Future, Stream}; //! #![feature(async_await)]
//! //!
//! // Create an infinite stream of "Ctrl+C" notifications. Each item received //! use futures_util::future;
//! // on this stream may represent multiple ctrl-c signals. //! use futures_util::stream::StreamExt;
//! let ctrl_c = tokio_signal::ctrl_c().flatten_stream(); //!
//! #[tokio::main]
//! 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?;
//!
//! // Process each ctrl-c as it comes in
//! let prog = ctrl_c.for_each(|event| {
//! event.expect("failed to get event");
//!
//! println!("ctrl-c received!");
//! future::ready(())
//! });
//!
//! prog.await;
//! //!
//! // Process each ctrl-c as it comes in
//! let prog = ctrl_c.for_each(|()| {
//! println!("ctrl-c received!");
//! Ok(()) //! Ok(())
//! }); //! }
//!
//! tokio::runtime::current_thread::block_on_all(prog).unwrap();
//! ``` //! ```
//! //!
//! Wait for SIGHUP on Unix //! Wait for SIGHUP on Unix
//! //!
//! ```rust,no_run //! ```rust,no_run
//! # #[cfg(unix)] fn dox() { //! #![feature(async_await)]
//! use futures::{Future, Stream}; //!
//! use futures_util::future;
//! use futures_util::stream::StreamExt;
//! use tokio_signal::unix::{Signal, SIGHUP}; //! use tokio_signal::unix::{Signal, SIGHUP};
//! //!
//! // Like the previous example, this is an infinite stream of signals //! #[tokio::main]
//! // being received, and signals may be coalesced while pending. //! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let stream = Signal::new(SIGHUP).flatten_stream(); //! // 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?;
//! //!
//! // Convert out stream into a future and block the program //! // Process each ctrl-c as it comes in
//! tokio::runtime::current_thread::block_on_all(stream.into_future()).ok().unwrap(); //! let prog = ctrl_c.for_each(|event| {
//! # } //! event.expect("failed to get event");
//!
//! println!("ctrl-c received!");
//! future::ready(())
//! });
//!
//! prog.await;
//!
//! // Like the previous example, this is an infinite stream of signals
//! // being received, and signals may be coalesced while pending.
//! let stream = Signal::new(SIGHUP).await?;
//!
//! // Convert out stream into a future and block the program
//! let (signal, _signal) = stream.into_future().await;
//! println!("got signal {:?}", signal);
//! Ok(())
//! }
//! ``` //! ```
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
use futures::stream::Stream; use futures_core::future::Future;
use futures::{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::io;
use std::pin::Pin;
use tokio_reactor::Handle; use tokio_reactor::Handle;
mod registry; mod registry;
@@ -74,10 +109,10 @@ mod os {
pub mod unix; pub mod unix;
pub mod windows; pub mod windows;
/// A future whose error is `io::Error` /// A future whose output is `io::Result<T>`
pub type IoFuture<T> = Box<dyn Future<Item = T, Error = io::Error> + Send>; pub type IoFuture<T> = Pin<Box<dyn Future<Output = io::Result<T>> + Send>>;
/// A stream whose error is `io::Error` /// A stream whose item is `io::Result<T>`
pub type IoStream<T> = Box<dyn Stream<Item = T, Error = io::Error> + Send>; 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. /// Creates a stream which receives "ctrl-c" notifications sent to a process.
/// ///
@@ -111,20 +146,15 @@ pub fn ctrl_c_handle(handle: &Handle) -> IoFuture<IoStream<()>> {
#[cfg(unix)] #[cfg(unix)]
fn ctrl_c_imp(handle: &Handle) -> IoFuture<IoStream<()>> { fn ctrl_c_imp(handle: &Handle) -> IoFuture<IoStream<()>> {
let handle = handle.clone(); unix::Signal::with_handle(unix::libc::SIGINT, &handle)
Box::new(future::lazy(move || { .map_ok(|signal| -> IoStream<()> { signal.map(|_| Ok(())).boxed() })
unix::Signal::with_handle(unix::libc::SIGINT, &handle) .boxed()
.map(|x| Box::new(x.map(|_| ())) as Box<dyn Stream<Item = _, Error = _> + Send>)
}))
} }
#[cfg(windows)] #[cfg(windows)]
fn ctrl_c_imp(handle: &Handle) -> IoFuture<IoStream<()>> { fn ctrl_c_imp(handle: &Handle) -> IoFuture<IoStream<()>> {
let handle = handle.clone(); windows::Event::ctrl_c_handle(&handle)
// Use lazy to ensure that `ctrl_c` gets called while on an event loop .map_ok(|event| -> IoStream<()> { event.map(|_| Ok(())).boxed() })
Box::new(future::lazy(move || { .boxed()
windows::Event::ctrl_c_handle(&handle)
.map(|x| Box::new(x) as Box<dyn Stream<Item = _, Error = _> + Send>)
}))
} }
} }
+47 -67
View File
@@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex; use std::sync::Mutex;
use crate::os::{OsExtraData, OsStorage}; use crate::os::{OsExtraData, OsStorage};
use futures::sync::mpsc::Sender; use tokio_sync::mpsc::Sender;
pub(crate) type EventId = usize; pub(crate) type EventId = usize;
@@ -98,7 +98,7 @@ impl<S: Storage> Registry<S> {
for i in (0..recipients.len()).rev() { for i in (0..recipients.len()).rev() {
match recipients[i].try_send(()) { match recipients[i].try_send(()) {
Ok(()) => {} Ok(()) => {}
Err(ref e) if e.is_disconnected() => { Err(ref e) if e.is_closed() => {
recipients.swap_remove(i); recipients.swap_remove(i);
} }
@@ -169,65 +169,53 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use futures::sync::mpsc::channel; use futures_util::{future, StreamExt};
use futures::sync::oneshot; use tokio_sync::mpsc::channel;
use futures::{Future, Stream}; use tokio_sync::oneshot;
use std::time::Duration; #[tokio::test]
use tokio::runtime::Runtime; async fn smoke() {
use tokio_timer::sleep;
#[test]
fn smoke() {
let registry = Registry::new(vec![ let registry = Registry::new(vec![
EventInfo::default(), EventInfo::default(),
EventInfo::default(), EventInfo::default(),
EventInfo::default(), EventInfo::default(),
]); ]);
let (first_tx, first_rx) = channel(0); let (first_tx, first_rx) = channel(3);
let (second_tx, second_rx) = channel(0); let (second_tx, second_rx) = channel(3);
let (third_tx, third_rx) = channel(0); let (third_tx, third_rx) = channel(3);
registry.register_listener(0, first_tx); registry.register_listener(0, first_tx);
registry.register_listener(1, second_tx); registry.register_listener(1, second_tx);
registry.register_listener(2, third_tx); registry.register_listener(2, third_tx);
let (fire, wait) = oneshot::channel(); let (fire, wait) = oneshot::channel();
let rt = Runtime::new().unwrap();
rt.spawn( tokio::spawn(async {
wait.and_then(move |_| { wait.await.expect("wait failed");
// Record some events which should get coalesced
registry.record_event(0);
registry.record_event(0);
registry.record_event(1);
registry.record_event(1);
registry.broadcast();
sleep(Duration::from_millis(100)) // Record some events which should get coalesced
.map_err(|e| panic!("{:#?}", e)) registry.record_event(0);
.and_then(move |_| { registry.record_event(0);
registry.record_event(0); registry.record_event(1);
registry.broadcast(); registry.record_event(1);
registry.broadcast();
drop(registry); // Send subsequent signal
Ok(()) registry.record_event(0);
}) registry.broadcast();
})
.map_err(|e| panic!("{}", e)), drop(registry);
});
let _ = fire.send(());
let all = future::join3(
first_rx.collect::<Vec<_>>(),
second_rx.collect::<Vec<_>>(),
third_rx.collect::<Vec<_>>(),
); );
let (first_results, second_results, third_results) = rt let (first_results, second_results, third_results) = all.await;
.block_on(futures::lazy(move || {
let _ = fire.send(());
first_rx
.collect()
.join3(second_rx.collect(), third_rx.collect())
}))
.expect("failed to extract events");
assert_eq!(2, first_results.len()); assert_eq!(2, first_results.len());
assert_eq!(1, second_results.len()); assert_eq!(1, second_results.len());
assert_eq!(0, third_results.len()); assert_eq!(0, third_results.len());
@@ -238,7 +226,7 @@ mod tests {
fn register_panics_on_invalid_input() { fn register_panics_on_invalid_input() {
let registry = Registry::new(vec![EventInfo::default()]); let registry = Registry::new(vec![EventInfo::default()]);
let (tx, _) = channel(0); let (tx, _) = channel(1);
registry.register_listener(1, tx); registry.register_listener(1, tx);
} }
@@ -248,13 +236,13 @@ mod tests {
registry.record_event(42); registry.record_event(42);
} }
#[test] #[tokio::test]
fn broadcast_cleans_up_disconnected_listeners() { async fn broadcast_cleans_up_disconnected_listeners() {
let registry = Registry::new(vec![EventInfo::default()]); let registry = Registry::new(vec![EventInfo::default()]);
let (first_tx, first_rx) = channel(0); let (first_tx, first_rx) = channel(1);
let (second_tx, second_rx) = channel(0); let (second_tx, second_rx) = channel(1);
let (third_tx, third_rx) = channel(0); let (third_tx, third_rx) = channel(1);
registry.register_listener(0, first_tx); registry.register_listener(0, first_tx);
registry.register_listener(0, second_tx); registry.register_listener(0, second_tx);
@@ -264,28 +252,20 @@ mod tests {
drop(second_rx); drop(second_rx);
let (fire, wait) = oneshot::channel(); let (fire, wait) = oneshot::channel();
let rt = Runtime::new().unwrap(); //let mut rt = Runtime::new().unwrap();
rt.spawn( tokio::spawn(async {
wait.and_then(move |_| { wait.await.expect("wait failed");
// Record some events which should get coalesced
registry.record_event(0);
registry.broadcast();
assert_eq!(1, registry.storage[0].recipients.lock().unwrap().len()); registry.record_event(0);
drop(registry); registry.broadcast();
Ok(()) assert_eq!(1, registry.storage[0].recipients.lock().unwrap().len());
}) drop(registry);
.map_err(|e| panic!("{}", e)), });
);
let results = rt let _ = fire.send(());
.block_on(futures::lazy(move || { let results: Vec<()> = third_rx.collect().await;
let _ = fire.send(());
third_rx.collect()
}))
.expect("failed to extract events");
assert_eq!(1, results.len()); assert_eq!(1, results.len());
} }
+38 -47
View File
@@ -7,20 +7,21 @@
pub use libc; pub use libc;
use std::io::prelude::*; use std::io::{self, Error, ErrorKind, Write};
use std::io::{self, Error, ErrorKind};
use std::pin::Pin; use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Once, ONCE_INIT}; use std::sync::{Once, ONCE_INIT};
use futures::future; use crate::IoFuture;
use futures::sync::mpsc::{channel, Receiver}; use futures_core::stream::Stream;
use futures::{Async, Future}; use futures_util::future::{self, FutureExt};
use futures::{Poll, Stream};
use libc::c_int; use libc::c_int;
use mio_uds::UnixStream; use mio_uds::UnixStream;
use tokio_io::IoFuture; use std::future::Future;
use std::task::{Context, Poll};
use tokio_io::AsyncRead;
use tokio_reactor::{Handle, PollEvented}; use tokio_reactor::{Handle, PollEvented};
use tokio_sync::mpsc::{channel, Receiver};
use crate::registry::{globals, EventId, EventInfo, Globals, Init, Storage}; use crate::registry::{globals, EventId, EventInfo, Globals, Init, Storage};
@@ -163,17 +164,15 @@ struct Driver {
} }
impl Future for Driver { impl Future for Driver {
type Item = (); type Output = ();
type Error = ();
fn poll(&mut self) -> Poll<(), ()> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Drain the data from the pipe and maintain interest in getting more // Drain the data from the pipe and maintain interest in getting more
self.drain(); self.drain(cx);
// Broadcast any signals which were received // Broadcast any signals which were received
globals().broadcast(); globals().broadcast();
// This task just lives until the end of the event loop Poll::Pending
Ok(Async::NotReady)
} }
} }
@@ -204,13 +203,13 @@ impl Driver {
/// We do *NOT* use the existence of any read bytes as evidence a sigal was /// We do *NOT* use the existence of any read bytes as evidence a sigal was
/// received since the `pending` flags would have already been set if that /// received since the `pending` flags would have already been set if that
/// was the case. See #38 for more info. /// was the case. See #38 for more info.
fn drain(&mut self) { fn drain(mut self: Pin<&mut Self>, cx: &mut Context<'_>) {
loop { loop {
match self.wakeup.read(&mut [0; 128]) { match Pin::new(&mut self.wakeup).poll_read(cx, &mut [0; 128]) {
Ok(0) => panic!("EOF on self-pipe"), Poll::Ready(Ok(0)) => panic!("EOF on self-pipe"),
Ok(_) => {} Poll::Ready(Ok(_)) => {}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break, Poll::Ready(Err(e)) => panic!("Bad read on self-pipe: {}", e),
Err(e) => panic!("Bad read on self-pipe: {}", e), Poll::Pending => break,
} }
} }
} }
@@ -306,44 +305,36 @@ impl Signal {
/// channels will receive the signal notification. /// channels will receive the signal notification.
pub fn with_handle(signal: c_int, handle: &Handle) -> IoFuture<Signal> { pub fn with_handle(signal: c_int, handle: &Handle) -> IoFuture<Signal> {
let handle = handle.clone(); let handle = handle.clone();
Box::new(future::lazy(move || { future::lazy(move |_| {
let result = (|| { // Turn the signal delivery on once we are ready for it
// Turn the signal delivery on once we are ready for it signal_enable(signal)?;
signal_enable(signal)?;
// Ensure there's a driver for our associated event loop processing // Ensure there's a driver for our associated event loop processing
// signals. // signals.
let driver = Driver::new(&handle)?; let driver = Driver::new(&handle)?;
// One wakeup in a queue is enough, no need for us to buffer up any // One wakeup in a queue is enough, no need for us to buffer up any
// more. NB: channels always guarantee at least one slot per sender, // more.
// so we don't need additional slots let (tx, rx) = channel(1);
let (tx, rx) = channel(0); globals().register_listener(signal as EventId, tx);
globals().register_listener(signal as EventId, tx);
Ok(Signal { Ok(Signal {
driver: driver, driver: driver,
rx: rx, rx: rx,
signal: signal, signal: signal,
}) })
})(); })
future::result(result) .boxed()
}))
} }
} }
impl Stream for Signal { impl Stream for Signal {
type Item = c_int; type Item = c_int;
type Error = io::Error;
fn poll(&mut self) -> Poll<Option<c_int>, io::Error> { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.driver.poll().unwrap(); let _ = Pin::new(&mut self.driver).poll(cx);
self.rx self.rx.poll_recv(cx).map(|item| item.map(|()| self.signal))
.poll()
.map(|ready| ready.map(|item| item.map(|()| self.signal)))
// receivers don't generate errors
.map_err(|_| unreachable!())
} }
} }
+30 -44
View File
@@ -8,13 +8,16 @@
#![cfg(windows)] #![cfg(windows)]
use std::convert::TryFrom; use std::convert::TryFrom;
use std::future::Future;
use std::io; use std::io;
use std::pin::Pin;
use std::sync::{Once, ONCE_INIT}; use std::sync::{Once, ONCE_INIT};
use std::task::{Context, Poll};
use futures::future; use futures_core::stream::Stream;
use futures::sync::mpsc::{channel, Receiver, Sender}; use futures_util::future::{self, FutureExt};
use futures::{Async, Future, Poll, Stream};
use tokio_reactor::Handle; use tokio_reactor::Handle;
use tokio_sync::mpsc::{channel, Receiver, Sender};
use winapi::shared::minwindef::*; use winapi::shared::minwindef::*;
use winapi::um::consoleapi::SetConsoleCtrlHandler; use winapi::um::consoleapi::SetConsoleCtrlHandler;
use winapi::um::wincon::*; use winapi::um::wincon::*;
@@ -62,9 +65,9 @@ pub(crate) struct OsExtraData {
impl Init for OsExtraData { impl Init for OsExtraData {
fn init() -> Self { fn init() -> Self {
let (driver_waker, driver_rx) = channel(0); let (driver_waker, driver_rx) = channel(1);
::tokio_executor::spawn(DriverTask { rx: driver_rx }); tokio_executor::spawn(DriverTask { rx: driver_rx });
Self { driver_waker } Self { driver_waker }
} }
@@ -129,7 +132,7 @@ impl Event {
} }
fn new(signum: DWORD, _handle: &Handle) -> IoFuture<Event> { fn new(signum: DWORD, _handle: &Handle) -> IoFuture<Event> {
let new_signal = future::poll_fn(move || { future::lazy(move |_| {
let mut init = None; let mut init = None;
INIT.call_once(|| { INIT.call_once(|| {
init = Some(global_init()); init = Some(global_init());
@@ -139,25 +142,20 @@ impl Event {
return Err(e); return Err(e);
} }
let (tx, rx) = channel(0); let (tx, rx) = channel(1);
globals().register_listener(signum as EventId, tx); globals().register_listener(signum as EventId, tx);
Ok(Async::Ready(Event { rx })) Ok(Event { rx })
}); })
.boxed()
Box::new(new_signal)
} }
} }
impl Stream for Event { impl Stream for Event {
type Item = (); type Item = ();
type Error = io::Error;
fn poll(&mut self) -> Poll<Option<()>, io::Error> { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.rx self.rx.poll_recv(cx)
.poll()
// receivers don't generate errors
.map_err(|_| unreachable!())
} }
} }
@@ -173,26 +171,23 @@ fn global_init() -> io::Result<()> {
} }
impl Future for DriverTask { impl Future for DriverTask {
type Item = (); type Output = ();
type Error = ();
fn poll(&mut self) -> Poll<(), ()> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
loop { loop {
// Ensure we keep polling our waker until we know there are no more // Ensure we keep polling our waker until we know there are no more
// events (and therefore we've registered interest to be woken again). // events (and therefore we've registered interest to be woken again).
match self.rx.poll() { match self.rx.poll_recv(cx) {
Ok(Async::Ready(Some(()))) => continue, Poll::Ready(Some(())) => continue,
Ok(Async::Ready(None)) => panic!("driver got disconnected?"), Poll::Ready(None) => panic!("driver got disconnected?"),
Ok(Async::NotReady) => break, Poll::Pending => break,
// receivers don't generate errors
Err(()) => unreachable!(),
} }
} }
globals().broadcast(); globals().broadcast();
// TODO(1000): when to finish this task? // TODO(1000): when to finish this task?
Ok(Async::NotReady) Poll::Pending
} }
} }
@@ -213,20 +208,14 @@ unsafe extern "system" fn handler(ty: DWORD) -> BOOL {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use futures_util::future::FutureExt;
use futures_util::stream::StreamExt;
use std::time::Duration; use std::time::Duration;
use tokio::runtime::current_thread; use tokio::runtime::current_thread;
use tokio::timer::Timeout; use tokio_timer::Timeout;
fn with_timeout<F: Future>(future: F) -> impl Future<Item = F::Item, Error = F::Error> { fn with_timeout<F: Future>(future: F) -> impl Future<Output = F::Output> {
Timeout::new(future, Duration::from_secs(1)).map_err(|e| { Timeout::new(future, Duration::from_secs(1)).map(|result| result.expect("timed out"))
if e.is_timer() {
panic!("failed to register timer");
} else if e.is_elapsed() {
panic!("timed out")
} else {
e.into_inner().expect("missing inner error")
}
})
} }
#[test] #[test]
@@ -245,19 +234,16 @@ mod tests {
super::handler(CTRL_C_EVENT); super::handler(CTRL_C_EVENT);
} }
rt.block_on(with_timeout(event_ctrl_c.into_future())) rt.block_on(with_timeout(event_ctrl_c.into_future()));
.ok()
.expect("failed to run event");
let event_ctrl_break = rt let event_ctrl_break = rt
.block_on(with_timeout(Event::ctrl_break())) .block_on(with_timeout(Event::ctrl_break()))
.expect("failed to run future"); .expect("failed to run future");
unsafe { unsafe {
super::handler(CTRL_BREAK_EVENT); super::handler(CTRL_BREAK_EVENT);
} }
rt.block_on(with_timeout(event_ctrl_break.into_future())) rt.block_on(with_timeout(event_ctrl_break.into_future()));
.ok()
.expect("failed to run event");
} }
} }
+1 -3
View File
@@ -29,7 +29,5 @@ fn dropping_loops_does_not_cause_starvation() {
send_signal(TEST_SIGNAL); send_signal(TEST_SIGNAL);
let signal_future = signal.into_future().map_err(|(e, _)| e); run_with_timeout(&mut rt, signal.into_future());
run_with_timeout(&mut rt, signal_future).expect("failed to get signal");
} }
+9 -10
View File
@@ -1,24 +1,23 @@
#![cfg(unix)] #![cfg(unix)]
#![deny(warnings, rust_2018_idioms)] #![deny(warnings, rust_2018_idioms)]
#![feature(async_await)]
use libc; use libc;
pub mod support; pub mod support;
use crate::support::*; use crate::support::*;
#[test] #[tokio::test]
fn drop_then_get_a_signal() { async fn drop_then_get_a_signal() {
let mut rt = CurrentThreadRuntime::new().unwrap(); let signal = with_timeout(Signal::new(libc::SIGUSR1))
let signal = run_with_timeout(&mut rt, Signal::new(libc::SIGUSR1)) .await
.expect("failed to create first signal"); .expect("failed to create first signal");
drop(signal); drop(signal);
send_signal(libc::SIGUSR1); send_signal(libc::SIGUSR1);
let signal = run_with_timeout(&mut rt, Signal::new(libc::SIGUSR1)) let signal = with_timeout(Signal::new(libc::SIGUSR1))
.expect("failed to create signal") .await
.into_future() .expect("failed to create second signal");
.map(|_| ())
.map_err(|(e, _)| panic!("{}", e));
run_with_timeout(&mut rt, signal).expect("failed to get signal"); with_timeout(signal.into_future()).await;
} }
@@ -1,5 +1,6 @@
#![cfg(unix)] #![cfg(unix)]
#![deny(warnings, rust_2018_idioms)] #![deny(warnings, rust_2018_idioms)]
#![feature(async_await)]
use libc; use libc;
@@ -8,25 +9,22 @@ use crate::support::*;
const TEST_SIGNAL: libc::c_int = libc::SIGUSR1; const TEST_SIGNAL: libc::c_int = libc::SIGUSR1;
#[test] #[tokio::test]
fn dropping_signal_does_not_deregister_any_other_instances() { async fn dropping_signal_does_not_deregister_any_other_instances() {
// NB: Deadline requires a timer registration which is provided by
// tokio's `current_thread::Runtime`, but isn't available by just using
// tokio's default CurrentThread executor which powers `current_thread::block_on_all`.
let mut rt = CurrentThreadRuntime::new().expect("failed to init runtime");
// NB: Testing for issue #38: signals should not starve based on ordering // NB: Testing for issue #38: signals should not starve based on ordering
let first_duplicate_signal = run_with_timeout(&mut rt, Signal::new(TEST_SIGNAL)) let first_duplicate_signal = with_timeout(Signal::new(TEST_SIGNAL))
.await
.expect("failed to register first duplicate signal"); .expect("failed to register first duplicate signal");
let signal = let signal = with_timeout(Signal::new(TEST_SIGNAL))
run_with_timeout(&mut rt, Signal::new(TEST_SIGNAL)).expect("failed to register signal"); .await
let second_duplicate_signal = run_with_timeout(&mut rt, Signal::new(TEST_SIGNAL)) .expect("failed to register signal");
let second_duplicate_signal = with_timeout(Signal::new(TEST_SIGNAL))
.await
.expect("failed to register second duplicate signal"); .expect("failed to register second duplicate signal");
drop(first_duplicate_signal); drop(first_duplicate_signal);
drop(second_duplicate_signal); drop(second_duplicate_signal);
send_signal(TEST_SIGNAL); send_signal(TEST_SIGNAL);
run_with_timeout(&mut rt, signal.into_future().map_err(|(e, _)| e)) with_timeout(signal.into_future()).await;
.expect("failed to get signal");
} }
+1 -3
View File
@@ -22,9 +22,7 @@ fn multi_loop() {
let mut rt = CurrentThreadRuntime::new().unwrap(); let mut rt = CurrentThreadRuntime::new().unwrap();
let signal = run_with_timeout(&mut rt, Signal::new(libc::SIGHUP)).unwrap(); let signal = run_with_timeout(&mut rt, Signal::new(libc::SIGHUP)).unwrap();
sender.send(()).unwrap(); sender.send(()).unwrap();
run_with_timeout(&mut rt, signal.into_future()) run_with_timeout(&mut rt, signal.into_future());
.ok()
.unwrap();
}) })
}) })
.collect(); .collect();
+10 -10
View File
@@ -1,22 +1,22 @@
#![cfg(unix)] #![cfg(unix)]
#![deny(warnings, rust_2018_idioms)] #![deny(warnings, rust_2018_idioms)]
#![feature(async_await)]
pub mod support; pub mod support;
use crate::support::*; use crate::support::*;
use libc; use libc;
#[test] #[tokio::test]
fn notify_both() { async fn notify_both() {
let mut rt = CurrentThreadRuntime::new().unwrap(); let signal1 = with_timeout(Signal::new(libc::SIGUSR2))
let signal1 = .await
run_with_timeout(&mut rt, Signal::new(libc::SIGUSR2)).expect("failed to create signal1"); .expect("failed to create signal1");
let signal2 = let signal2 = with_timeout(Signal::new(libc::SIGUSR2))
run_with_timeout(&mut rt, Signal::new(libc::SIGUSR2)).expect("failed to create signal2"); .await
.expect("failed to create signal2");
send_signal(libc::SIGUSR2); send_signal(libc::SIGUSR2);
run_with_timeout(&mut rt, signal1.into_future().join(signal2.into_future())) with_timeout(future::join(signal1.into_future(), signal2.into_future())).await;
.ok()
.expect("failed to receive");
} }
-18
View File
@@ -1,18 +0,0 @@
#![cfg(unix)]
#![deny(warnings, rust_2018_idioms)]
pub mod support;
use crate::support::*;
use libc;
#[test]
fn tokio_simple() {
let signal_future = Signal::new(libc::SIGUSR1).and_then(|signal| {
send_signal(libc::SIGUSR1);
signal.into_future().map(|_| ()).map_err(|(err, _)| err)
});
let mut rt = CurrentThreadRuntime::new().expect("failed to init runtime");
run_with_timeout(&mut rt, signal_future).expect("failed");
}
+7 -8
View File
@@ -1,20 +1,19 @@
#![cfg(unix)] #![cfg(unix)]
#![deny(warnings, rust_2018_idioms)] #![deny(warnings, rust_2018_idioms)]
#![feature(async_await)]
pub mod support; pub mod support;
use crate::support::*; use crate::support::*;
use libc; use libc;
#[test] #[tokio::test]
fn simple() { async fn simple() {
let mut rt = CurrentThreadRuntime::new().unwrap(); let signal = with_timeout(Signal::new(libc::SIGUSR1))
let signal = .await
run_with_timeout(&mut rt, Signal::new(libc::SIGUSR1)).expect("failed to create signal"); .expect("failed to create signal");
send_signal(libc::SIGUSR1); send_signal(libc::SIGUSR1);
run_with_timeout(&mut rt, signal.into_future()) with_timeout(signal.into_future()).await;
.ok()
.expect("failed to get signal");
} }
+8 -13
View File
@@ -1,27 +1,22 @@
#![cfg(unix)] #![cfg(unix)]
#![deny(warnings, rust_2018_idioms)] #![deny(warnings, rust_2018_idioms)]
use futures_util::future::FutureExt;
use libc::{c_int, getpid, kill}; use libc::{c_int, getpid, kill};
use std::future::Future;
use std::time::Duration; use std::time::Duration;
use tokio::timer::Timeout; use tokio_timer::Timeout;
pub use futures::{Future, Stream}; pub use futures_util::future;
pub use futures_util::stream::StreamExt;
pub use tokio::runtime::current_thread::{self, Runtime as CurrentThreadRuntime}; pub use tokio::runtime::current_thread::{self, Runtime as CurrentThreadRuntime};
pub use tokio_signal::unix::Signal; pub use tokio_signal::unix::Signal;
pub fn with_timeout<F: Future>(future: F) -> impl Future<Item = F::Item, Error = F::Error> { pub fn with_timeout<F: Future>(future: F) -> impl Future<Output = F::Output> {
Timeout::new(future, Duration::from_secs(1)).map_err(|e| { Timeout::new(future, Duration::from_secs(1)).map(Result::unwrap)
if e.is_timer() {
panic!("failed to register timer");
} else if e.is_elapsed() {
panic!("timed out")
} else {
e.into_inner().expect("missing inner error")
}
})
} }
pub fn run_with_timeout<F>(rt: &mut CurrentThreadRuntime, future: F) -> Result<F::Item, F::Error> pub fn run_with_timeout<F>(rt: &mut CurrentThreadRuntime, future: F) -> F::Output
where where
F: Future, F: Future,
{ {
+13 -13
View File
@@ -1,24 +1,24 @@
#![cfg(unix)] #![cfg(unix)]
#![deny(warnings, rust_2018_idioms)] #![deny(warnings, rust_2018_idioms)]
#![feature(async_await)]
pub mod support; pub mod support;
use crate::support::*; use crate::support::*;
use libc; use libc;
#[test] #[tokio::test]
fn twice() { async fn twice() {
let mut rt = CurrentThreadRuntime::new().unwrap(); let mut signal = with_timeout(Signal::new(libc::SIGUSR1))
let signal = run_with_timeout(&mut rt, Signal::new(libc::SIGUSR1)).unwrap(); .await
.expect("failed to get signal");
send_signal(libc::SIGUSR1); for _ in 0..2 {
let (num, signal) = run_with_timeout(&mut rt, signal.into_future()) send_signal(libc::SIGUSR1);
.ok()
.unwrap();
assert_eq!(num, Some(libc::SIGUSR1));
send_signal(libc::SIGUSR1); let (num, sig) = with_timeout(signal.into_future()).await;
run_with_timeout(&mut rt, signal.into_future()) assert_eq!(num, Some(libc::SIGUSR1));
.ok()
.unwrap(); signal = sig;
}
} }