From aef434c089754051e4a10c0e618f46003f31d2dc Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Thu, 28 Nov 2019 15:03:04 -0800 Subject: [PATCH] signal: update documentation with caveats (#1854) --- tokio/src/signal/ctrl_c.rs | 18 ++++++ tokio/src/signal/mod.rs | 9 +-- tokio/src/signal/unix.rs | 107 +++++++++++++++++++++++++++++++----- tokio/src/signal/windows.rs | 87 ++++++++++++++++++++++++++--- 4 files changed, 190 insertions(+), 31 deletions(-) diff --git a/tokio/src/signal/ctrl_c.rs b/tokio/src/signal/ctrl_c.rs index 35ef2393568..2240052ce9f 100644 --- a/tokio/src/signal/ctrl_c.rs +++ b/tokio/src/signal/ctrl_c.rs @@ -15,6 +15,24 @@ use std::io; /// future will complete on the first received `ctrl-c` **after** the initial /// call to either `Future::poll` or `.await`. /// +/// # Caveats +/// +/// On Unix platforms, the first time that a `Signal` instance is registered for a +/// particular signal kind, an OS signal-handler is installed which replaces the +/// default platform behavior when that signal is received, **for the duration of +/// the entire process**. +/// +/// For example, Unix systems will terminate a process by default when it +/// receives a signal generated by "CTRL+C" on the terminal. But, when a +/// `ctrl_c` stream is created to listen for this signal, the time it arrives, +/// it will be translated to a stream event, and the process will continue to +/// execute. **Even if this `Signal` instance is dropped, subsequent SIGINT +/// deliveries will end up captured by Tokio, and the default platform behavior +/// will NOT be reset**. +/// +/// Thus, applications should take care to ensure the expected signal behavior +/// occurs as expected after listening for specific signals. +/// /// # Examples /// /// ```rust,no_run diff --git a/tokio/src/signal/mod.rs b/tokio/src/signal/mod.rs index 6b36bc4e669..6e5e350df57 100644 --- a/tokio/src/signal/mod.rs +++ b/tokio/src/signal/mod.rs @@ -1,16 +1,12 @@ //! Asynchronous signal handling for Tokio //! -//! The primary type exported from this crate, `unix::Signal`, allows -//! listening for arbitrary signals on Unix platforms, receiving them -//! in an asynchronous fashion. -//! //! Note that signal handling is in general a very tricky topic and should be //! used with great care. This crate attempts to implement 'best practice' for //! signal handling, but it should be evaluated for your own applications' needs //! to see if it's suitable. //! -//! The are some fundamental limitations of this crate documented on the -//! `Signal` structure as well. +//! The are some fundamental limitations of this crate documented on the OS +//! specific structures, as well. //! //! # Examples //! @@ -31,7 +27,6 @@ //! //! ```rust,no_run //! # #[cfg(unix)] { -//! //! use tokio::signal::unix::{signal, SignalKind}; //! //! #[tokio::main] diff --git a/tokio/src/signal/unix.rs b/tokio/src/signal/unix.rs index b6ff9926eb2..cd326424bdb 100644 --- a/tokio/src/signal/unix.rs +++ b/tokio/src/signal/unix.rs @@ -309,12 +309,7 @@ impl Driver { } } -/// An implementation of `Stream` for receiving a particular type of signal. -/// -/// This structure implements the `Stream` trait and represents notifications -/// of the current process receiving a particular signal. The signal being -/// listened for is passed to `Signal::new`, and the same signal number is then -/// yielded as each element for the stream. +/// A stream of events for receiving a particular type of OS signal. /// /// In general signal handling on Unix is a pretty tricky topic, and this /// structure is no exception! There are some important limitations to keep in @@ -336,13 +331,46 @@ impl Driver { /// improvements are possible in this crate, it's recommended to not plan on /// having millions of signal channels open. /// -/// * Currently the "driver task" to process incoming signals never exits. This -/// driver task runs in the background of the event loop provided, and -/// in general you shouldn't need to worry about it. -/// /// 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! +/// repo! New approaches to alleviate some of these limitations are always +/// appreciated! +/// +/// # Caveats +/// +/// The first time that a `Signal` instance is registered for a particular +/// signal kind, an OS signal-handler is installed which replaces the default +/// platform behavior when that signal is received, **for the duration of the +/// entire process**. +/// +/// For example, Unix systems will terminate a process by default when it +/// receives SIGINT. But, when a `Signal` instance is created to listen for +/// this signal, the next SIGINT that arrives will be translated to a stream +/// event, and the process will continue to execute. **Even if this `Signal` +/// instance is dropped, subsequent SIGINT deliveries will end up captured by +/// Tokio, and the default platform behavior will NOT be reset**. +/// +/// Thus, applications should take care to ensure the expected signal behavior +/// occurs as expected after listening for specific signals. +/// +/// # Examples +/// +/// Wait for SIGHUP +/// +/// ```rust,no_run +/// use tokio::signal::unix::{signal, SignalKind}; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), Box> { +/// // An infinite stream of hangup signals. +/// let mut stream = signal(SignalKind::hangup())?; +/// +/// // Print whenever a HUP signal is received +/// loop { +/// stream.recv().await; +/// println!("got signal HUP"); +/// } +/// } +/// ``` #[must_use = "streams do nothing unless polled"] #[derive(Debug)] pub struct Signal { @@ -351,7 +379,7 @@ pub struct Signal { } /// Creates a new stream which will receive notifications when the current -/// process receives the signal `signal`. +/// process receives the specified signal `kind`. /// /// This function will create a new stream which binds to the default reactor. /// The `Signal` stream is an infinite stream which will receive @@ -391,13 +419,62 @@ pub fn signal(kind: SignalKind) -> io::Result { } impl Signal { - #[doc(hidden)] // TODO: Dox + /// Receive the next signal notification event. + /// + /// `None` is returned if no more events can be received by this stream. + /// + /// # Examples + /// + /// Wait for SIGHUP + /// + /// ```rust,no_run + /// use tokio::signal::unix::{signal, SignalKind}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// // An infinite stream of hangup signals. + /// let mut stream = signal(SignalKind::hangup())?; + /// + /// // Print whenever a HUP signal is received + /// loop { + /// stream.recv().await; + /// println!("got signal HUP"); + /// } + /// } + /// ``` pub async fn recv(&mut self) -> Option<()> { use crate::future::poll_fn; poll_fn(|cx| self.poll_recv(cx)).await } - #[doc(hidden)] // TODO: document + /// Poll to receive the next signal notification event, outside of an + /// `async` context. + /// + /// `None` is returned if no more events can be received by this stream. + /// + /// # Examples + /// + /// Polling from a manually implemented future + /// + /// ```rust,no_run + /// use std::pin::Pin; + /// use std::future::Future; + /// use std::task::{Context, Poll}; + /// use tokio::signal::unix::Signal; + /// + /// struct MyFuture { + /// signal: Signal, + /// } + /// + /// impl Future for MyFuture { + /// type Output = Option<()>; + /// + /// fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + /// println!("polling MyFuture"); + /// self.signal.poll_recv(cx) + /// } + /// } + /// ``` pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll> { let _ = self.driver.poll(cx); self.rx.poll_recv(cx) diff --git a/tokio/src/signal/windows.rs b/tokio/src/signal/windows.rs index 725518d3ea7..de35643e5b6 100644 --- a/tokio/src/signal/windows.rs +++ b/tokio/src/signal/windows.rs @@ -73,7 +73,6 @@ impl Init for OsExtraData { /// processed quickly enough. This means that if two notifications are /// 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(crate) struct Event { @@ -139,7 +138,7 @@ unsafe extern "system" fn handler(ty: DWORD) -> BOOL { /// Represents a stream which receives "ctrl-break" notifications sent to the process /// via `SetConsoleCtrlHandler`. /// -/// A notification to this process notifies *all* streams listening to +/// A notification to this process notifies *all* streams listening for /// this event. Moreover, the notifications **are coalesced** if they aren't processed /// quickly enough. This means that if two notifications are received back-to-back, /// then the stream may only receive one item about the two notifications. @@ -150,25 +149,95 @@ pub struct CtrlBreak { } impl CtrlBreak { - #[doc(hidden)] // TODO: document + /// Receive the next signal notification event. + /// + /// `None` is returned if no more events can be received by this stream. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tokio::signal::windows::ctrl_break; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// // An infinite stream of CTRL-BREAK events. + /// let mut stream = ctrl_break()?; + /// + /// // Print whenever a CTRL-BREAK event is received + /// loop { + /// stream.recv().await; + /// println!("got signal CTRL-BREAK"); + /// } + /// } + /// ``` + pub async fn recv(&mut self) -> Option<()> { + use crate::future::poll_fn; + poll_fn(|cx| self.poll_recv(cx)).await + } + + /// Poll to receive the next signal notification event, outside of an + /// `async` context. + /// + /// `None` is returned if no more events can be received by this stream. + /// + /// # Examples + /// + /// Polling from a manually implemented future + /// + /// ```rust,no_run + /// use std::pin::Pin; + /// use std::future::Future; + /// use std::task::{Context, Poll}; + /// use tokio::signal::windows::CtrlBreak; + /// + /// struct MyFuture { + /// ctrl_break: CtrlBreak, + /// } + /// + /// impl Future for MyFuture { + /// type Output = Option<()>; + /// + /// fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + /// println!("polling MyFuture"); + /// self.ctrl_break.poll_recv(cx) + /// } + /// } + /// ``` pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll> { self.inner.rx.poll_recv(cx) } } -#[cfg(feature = "stream")] -impl futures_core::Stream for CtrlBreak { - type Item = (); +cfg_stream! { + impl futures_core::Stream for CtrlBreak { + type Item = (); - fn poll_next(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.poll_recv(cx) + fn poll_next(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_recv(cx) + } } } /// Creates a new stream which receives "ctrl-break" notifications sent to the /// process. /// -/// This function binds to the default reactor. +/// # Examples +/// +/// ```rust,no_run +/// use tokio::signal::windows::ctrl_break; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), Box> { +/// // An infinite stream of CTRL-BREAK events. +/// let mut stream = ctrl_break()?; +/// +/// // Print whenever a CTRL-BREAK event is received +/// loop { +/// stream.recv().await; +/// println!("got signal CTRL-BREAK"); +/// } +/// } +/// ``` pub fn ctrl_break() -> io::Result { Event::new(CTRL_BREAK_EVENT).map(|inner| CtrlBreak { inner }) }