signal: Add SignalKind for registering signals more easily (#1430)

This avoids having consumers import libc for common signals, and it
improves discoverability since users need not be aware that libc
contains all supported constants.
This commit is contained in:
Ivan Petkov 2019-08-13 21:07:22 -07:00 committed by Carl Lerche
parent 513326e01d
commit 338b37884a
13 changed files with 169 additions and 69 deletions

View File

@ -40,7 +40,7 @@ use std::process::{self, ExitStatus};
use std::task::Context;
use std::task::Poll;
use tokio_reactor::{Handle, PollEvented};
use tokio_signal::unix::Signal;
use tokio_signal::unix::{Signal, SignalKind};
impl Wait for process::Child {
fn id(&self) -> u32 {
@ -99,7 +99,7 @@ pub(crate) fn spawn_child(cmd: &mut process::Command, handle: &Handle) -> io::Re
let stdout = stdio(child.stdout.take(), handle)?;
let stderr = stdio(child.stderr.take(), handle)?;
let signal = Signal::with_handle(libc::SIGCHLD, handle)?;
let signal = Signal::with_handle(SignalKind::sigchld(), handle)?;
Ok(SpawnedChild {
child: Child {

View File

@ -7,12 +7,14 @@
#[cfg(unix)]
mod platform {
use futures_util::stream::{self, StreamExt};
use tokio_signal::unix::{Signal, SIGINT, SIGTERM};
use tokio_signal::unix::{Signal, SignalKind};
pub async fn main() {
// Create a stream for each of the signals we'd like to handle.
let sigint = Signal::new(SIGINT).unwrap().map(|_| SIGINT);
let sigterm = Signal::new(SIGTERM).unwrap().map(|_| SIGTERM);
let sigint = Signal::new(SignalKind::sigint()).unwrap().map(|_| "SIGINT");
let sigterm = Signal::new(SignalKind::sigterm())
.unwrap()
.map(|_| "SIGTERM");
// Use the `select` combinator to merge these two streams into one
let stream = stream::select(sigint, sigterm);
@ -27,13 +29,8 @@ mod platform {
let (item, _rest) = stream.into_future().await;
// Figure out which signal we received
let item = item.ok_or("received no signal").unwrap();
if item == SIGINT {
println!("received SIGINT");
} else {
assert_eq!(item, SIGTERM);
println!("received SIGTERM");
}
let msg = item.ok_or("received no signal").unwrap();
println!("received {}", msg);
}
}

View File

@ -5,11 +5,11 @@
#[cfg(unix)]
mod platform {
use futures_util::stream::StreamExt;
use tokio_signal::unix::{Signal, SIGHUP};
use tokio_signal::unix::{Signal, SignalKind};
pub async fn main() {
// on Unix, we can listen to whatever signal we want, in this case: SIGHUP
let mut stream = Signal::new(SIGHUP).unwrap();
let mut stream = Signal::new(SignalKind::sighup()).unwrap();
println!("Waiting for SIGHUPS (Ctrl+C to quit)");
println!(

View File

@ -59,7 +59,7 @@
//!
//! use futures_util::future;
//! use futures_util::stream::StreamExt;
//! use tokio_signal::unix::{Signal, SIGHUP};
//! use tokio_signal::unix::{Signal, SignalKind};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -77,7 +77,7 @@
//!
//! // 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)?;
//! let stream = Signal::new(SignalKind::sighup())?;
//!
//! // Convert out stream into a future and block the program
//! let (signal, _signal) = stream.into_future().await;

View File

@ -23,28 +23,6 @@ use tokio_sync::mpsc::{channel, Receiver};
use crate::registry::{globals, EventId, EventInfo, Globals, Init, Storage};
pub use libc::{SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTRAP};
pub use libc::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2};
/// BSD-specific definitions
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
))]
pub mod bsd {
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"
))]
pub use super::libc::SIGINFO;
}
pub(crate) type OsStorage = Vec<SignalInfo>;
// Number of different unix signals
@ -84,6 +62,131 @@ impl Init for OsExtraData {
}
}
/// Represents the specific kind of signal to listen for.
#[derive(Debug, Clone, Copy)]
pub struct SignalKind(c_int);
impl SignalKind {
/// Allows for listening to any valid OS signal.
///
/// For example, this can be used for listening for platform-specific
/// signals.
/// ```rust,no_run
/// # use tokio_signal::unix::SignalKind;
/// # let signum = -1;
/// // let signum = libc::OS_SPECIFIC_SIGNAL;
/// let kind = SignalKind::from_raw(signum);
/// ```
pub fn from_raw(signum: c_int) -> Self {
Self(signum)
}
/// Represents the SIGALRM signal.
///
/// On Unix systems this signal is sent when a real-time timer has expired.
/// By default, the process is terminated by this signal.
pub fn sigalrm() -> Self {
Self(libc::SIGALRM)
}
/// Represents the SIGCHLD signal.
///
/// On Unix systems this signal is sent when the status of a child process
/// has changed. By default, this signal is ignored.
pub fn sigchld() -> Self {
Self(libc::SIGCHLD)
}
/// Represents the SIGHUP signal.
///
/// On Unix systems this signal is sent when the terminal is disconnected.
/// By default, the process is terminated by this signal.
pub fn sighup() -> Self {
Self(libc::SIGHUP)
}
/// Represents the SIGINFO signal.
///
/// On Unix systems this signal is sent to request a status update from the
/// process. By default, this signal is ignored.
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"
))]
pub fn siginfo() -> Self {
Self(libc::SIGINFO)
}
/// Represents the SIGINT signal.
///
/// On Unix systems this signal is sent to interrupt a program.
/// By default, the process is terminated by this signal.
pub fn sigint() -> Self {
Self(libc::SIGINT)
}
/// Represents the SIGIO signal.
///
/// On Unix systems this signal is sent when I/O operations are possible
/// on some file descriptor. By default, this signal is ignored.
pub fn sigio() -> Self {
Self(libc::SIGIO)
}
/// Represents the SIGPIPE signal.
///
/// On Unix systems this signal is sent when the process attempts to write
/// to a pipe which has no reader. By default, the process is terminated by
/// this signal.
pub fn sigpipe() -> Self {
Self(libc::SIGPIPE)
}
/// Represents the SIGQUIT signal.
///
/// On Unix systems this signal is sent to issue a shutdown of the
/// process, after which the OS will dump the process core.
/// By default, the process is terminated by this signal.
pub fn sigquit() -> Self {
Self(libc::SIGQUIT)
}
/// Represents the SIGTERM signal.
///
/// On Unix systems this signal is sent to issue a shutdown of the
/// process. By default, the process is terminated by this signal.
pub fn sigterm() -> Self {
Self(libc::SIGTERM)
}
/// Represents the SIGUSR1 signal.
///
/// On Unix systems this is a user defined signal.
/// By default, the process is terminated by this signal.
pub fn sigusr1() -> Self {
Self(libc::SIGUSR1)
}
/// Represents the SIGUSR2 signal.
///
/// On Unix systems this is a user defined signal.
/// By default, the process is terminated by this signal.
pub fn sigusr2() -> Self {
Self(libc::SIGUSR2)
}
/// Represents the SIGWINCH signal.
///
/// On Unix systems this signal is sent when the terminal window is resized.
/// By default, this signal is ignored.
pub fn sigwinch() -> Self {
Self(libc::SIGWINCH)
}
}
pub(crate) struct SignalInfo {
event_info: EventInfo,
init: Once,
@ -259,10 +362,7 @@ impl Signal {
/// Creates a new stream which will receive notifications when the current
/// process receives the signal `signal`.
///
/// This function will create a new stream which binds to the default event
/// loop. This function returns a future which will
/// then resolve to the signal stream, if successful.
///
/// This function will create a new stream which binds to the default reactor.
/// The `Signal` stream is an infinite stream which will receive
/// notifications whenever a signal is received. More documentation can be
/// found on `Signal` itself, but to reiterate:
@ -281,17 +381,15 @@ impl Signal {
/// * If the previous initialization of this specific signal failed.
/// * If the signal is one of
/// [`signal_hook::FORBIDDEN`](https://docs.rs/signal-hook/*/signal_hook/fn.register.html#panics)
pub fn new(signal: c_int) -> io::Result<Self> {
Signal::with_handle(signal, &Handle::default())
pub fn new(kind: SignalKind) -> io::Result<Self> {
Signal::with_handle(kind, &Handle::default())
}
/// Creates a new stream which will receive notifications when the current
/// process receives the signal `signal`.
///
/// This function will create a new stream which may be based on the
/// event loop handle provided. This function returns a future which will
/// then resolve to the signal stream, if successful.
///
/// provided reactor handle.
/// The `Signal` stream is an infinite stream which will receive
/// notifications whenever a signal is received. More documentation can be
/// found on `Signal` itself, but to reiterate:
@ -303,7 +401,9 @@ impl Signal {
/// A `Signal` stream can be created for a particular signal number
/// multiple times. When a signal is received then all the associated
/// channels will receive the signal notification.
pub fn with_handle(signal: c_int, handle: &Handle) -> io::Result<Self> {
pub fn with_handle(kind: SignalKind, handle: &Handle) -> io::Result<Self> {
let signal = kind.0;
// Turn the signal delivery on once we are ready for it
signal_enable(signal)?;
@ -320,7 +420,7 @@ impl Signal {
}
pub(crate) fn ctrl_c(handle: &Handle) -> io::Result<Self> {
Self::with_handle(libc::SIGINT, handle)
Self::with_handle(SignalKind::sigint(), handle)
}
}

View File

@ -6,18 +6,18 @@ use libc;
pub mod support;
use crate::support::*;
const TEST_SIGNAL: libc::c_int = libc::SIGUSR1;
#[test]
fn dropping_loops_does_not_cause_starvation() {
let (mut rt, signal) = {
let kind = SignalKind::sigusr1();
let mut first_rt = CurrentThreadRuntime::new().expect("failed to init first runtime");
let mut first_signal = Signal::new(TEST_SIGNAL).expect("failed to register first signal");
let mut first_signal = Signal::new(kind).expect("failed to register first signal");
let mut second_rt = CurrentThreadRuntime::new().expect("failed to init second runtime");
let mut second_signal = Signal::new(TEST_SIGNAL).expect("failed to register second signal");
let mut second_signal = Signal::new(kind).expect("failed to register second signal");
send_signal(TEST_SIGNAL);
send_signal(libc::SIGUSR1);
let _ = run_with_timeout(&mut first_rt, first_signal.next())
.expect("failed to await first signal");
@ -31,7 +31,7 @@ fn dropping_loops_does_not_cause_starvation() {
(second_rt, second_signal)
};
send_signal(TEST_SIGNAL);
send_signal(libc::SIGUSR1);
let _ = run_with_timeout(&mut rt, signal.into_future());
}

View File

@ -9,11 +9,12 @@ use crate::support::*;
#[tokio::test]
async fn drop_then_get_a_signal() {
let signal = Signal::new(libc::SIGUSR1).expect("failed to create first signal");
let kind = SignalKind::sigusr1();
let signal = Signal::new(kind).expect("failed to create first signal");
drop(signal);
send_signal(libc::SIGUSR1);
let signal = Signal::new(libc::SIGUSR1).expect("failed to create second signal");
let signal = Signal::new(kind).expect("failed to create second signal");
let _ = with_timeout(signal.into_future()).await;
}

View File

@ -7,21 +7,21 @@ use libc;
pub mod support;
use crate::support::*;
const TEST_SIGNAL: libc::c_int = libc::SIGUSR1;
#[tokio::test]
async fn dropping_signal_does_not_deregister_any_other_instances() {
let kind = SignalKind::sigusr1();
// NB: Testing for issue alexcrichton/tokio-signal#38:
// signals should not starve based on ordering
let first_duplicate_signal =
Signal::new(TEST_SIGNAL).expect("failed to register first duplicate signal");
let signal = Signal::new(TEST_SIGNAL).expect("failed to register signal");
Signal::new(kind).expect("failed to register first duplicate signal");
let signal = Signal::new(kind).expect("failed to register signal");
let second_duplicate_signal =
Signal::new(TEST_SIGNAL).expect("failed to register second duplicate signal");
Signal::new(kind).expect("failed to register second duplicate signal");
drop(first_duplicate_signal);
drop(second_duplicate_signal);
send_signal(TEST_SIGNAL);
send_signal(libc::SIGUSR1);
let _ = with_timeout(signal.into_future()).await;
}

View File

@ -20,7 +20,7 @@ fn multi_loop() {
let sender = sender.clone();
thread::spawn(move || {
let mut rt = CurrentThreadRuntime::new().unwrap();
let signal = Signal::new(libc::SIGHUP).unwrap();
let signal = Signal::new(SignalKind::sighup()).unwrap();
sender.send(()).unwrap();
let _ = run_with_timeout(&mut rt, signal.into_future());
})

View File

@ -9,9 +9,10 @@ use libc;
#[tokio::test]
async fn notify_both() {
let signal1 = Signal::new(libc::SIGUSR2).expect("failed to create signal1");
let kind = SignalKind::sigusr2();
let signal1 = Signal::new(kind).expect("failed to create signal1");
let signal2 = Signal::new(libc::SIGUSR2).expect("failed to create signal2");
let signal2 = Signal::new(kind).expect("failed to create signal2");
send_signal(libc::SIGUSR2);
let _ = with_timeout(future::join(signal1.into_future(), signal2.into_future())).await;

View File

@ -9,7 +9,7 @@ use libc;
#[tokio::test]
async fn simple() {
let signal = Signal::new(libc::SIGUSR1).expect("failed to create signal");
let signal = Signal::new(SignalKind::sigusr1()).expect("failed to create signal");
send_signal(libc::SIGUSR1);

View File

@ -10,7 +10,7 @@ use tokio_timer::Timeout;
pub use futures_util::future;
pub use futures_util::stream::StreamExt;
pub use tokio::runtime::current_thread::{self, Runtime as CurrentThreadRuntime};
pub use tokio_signal::unix::Signal;
pub use tokio_signal::unix::{Signal, SignalKind};
pub fn with_timeout<F: Future>(future: F) -> impl Future<Output = F::Output> {
Timeout::new(future, Duration::from_secs(1)).map(Result::unwrap)

View File

@ -9,7 +9,8 @@ use libc;
#[tokio::test]
async fn twice() {
let mut signal = Signal::new(libc::SIGUSR1).expect("failed to get signal");
let kind = SignalKind::sigusr1();
let mut signal = Signal::new(kind).expect("failed to get signal");
for _ in 0..2 {
send_signal(libc::SIGUSR1);