signal: make windows docs for signal module show up on unix builds (#3770)

This commit is contained in:
Alice Ryhl 2021-08-02 20:55:17 +02:00 committed by GitHub
parent cf02b3f32d
commit 69a6585429
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 197 additions and 176 deletions

View File

@ -286,6 +286,7 @@ jobs:
- name: "doc --lib --all-features"
run: cargo doc --lib --no-deps --all-features --document-private-items
env:
RUSTFLAGS: --cfg docsrs
RUSTDOCFLAGS: --cfg docsrs -Dwarnings
loom:

View File

@ -44,3 +44,8 @@ proptest = "1"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
# Issue #3770
#
# This should allow `docsrs` to be read across projects, so that `tokio-stream`
# can pick up stubbed types exported by `tokio`.
rustc-args = ["--cfg", "docsrs"]

View File

@ -36,9 +36,9 @@ cfg_signal! {
#[cfg(unix)]
pub use signal_unix::SignalStream;
#[cfg(windows)]
#[cfg(any(windows, docsrs))]
mod signal_windows;
#[cfg(windows)]
#[cfg(any(windows, docsrs))]
pub use signal_windows::{CtrlCStream, CtrlBreakStream};
}

View File

@ -4,6 +4,7 @@
//! `Signal` type for receiving notifications of signals.
#![cfg(unix)]
#![cfg_attr(docsrs, doc(cfg(all(unix, feature = "signal"))))]
use crate::signal::registry::{globals, EventId, EventInfo, Globals, Init, Storage};
use crate::signal::RxFuture;

View File

@ -5,127 +5,22 @@
//! `SetConsoleCtrlHandler` function which receives events of the type
//! `CTRL_C_EVENT` and `CTRL_BREAK_EVENT`.
#![cfg(windows)]
#![cfg(any(windows, docsrs))]
#![cfg_attr(docsrs, doc(cfg(all(windows, feature = "signal"))))]
use crate::signal::registry::{globals, EventId, EventInfo, Init, Storage};
use crate::signal::RxFuture;
use std::convert::TryFrom;
use std::io;
use std::sync::Once;
use std::task::{Context, Poll};
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE};
use winapi::um::consoleapi::SetConsoleCtrlHandler;
use winapi::um::wincon::{CTRL_BREAK_EVENT, CTRL_C_EVENT};
#[derive(Debug)]
pub(crate) struct OsStorage {
ctrl_c: EventInfo,
ctrl_break: EventInfo,
}
#[cfg(not(docsrs))]
#[path = "windows/sys.rs"]
mod imp;
#[cfg(not(docsrs))]
pub(crate) use self::imp::{OsExtraData, OsStorage};
impl Init for OsStorage {
fn init() -> Self {
Self {
ctrl_c: EventInfo::default(),
ctrl_break: EventInfo::default(),
}
}
}
impl Storage for OsStorage {
fn event_info(&self, id: EventId) -> Option<&EventInfo> {
match DWORD::try_from(id) {
Ok(CTRL_C_EVENT) => Some(&self.ctrl_c),
Ok(CTRL_BREAK_EVENT) => Some(&self.ctrl_break),
_ => None,
}
}
fn for_each<'a, F>(&'a self, mut f: F)
where
F: FnMut(&'a EventInfo),
{
f(&self.ctrl_c);
f(&self.ctrl_break);
}
}
#[derive(Debug)]
pub(crate) struct OsExtraData {}
impl Init for OsExtraData {
fn init() -> Self {
Self {}
}
}
/// Stream of events discovered via `SetConsoleCtrlHandler`.
///
/// This structure can be used to listen for events of the type `CTRL_C_EVENT`
/// and `CTRL_BREAK_EVENT`. The `Stream` trait is implemented for this struct
/// and will resolve for each notification received by the process. Note that
/// there are few limitations with this as well:
///
/// * A notification to this process notifies *all* `Event` streams for that
/// event type.
/// * Notifications to an `Event` stream **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.
#[must_use = "streams do nothing unless polled"]
#[derive(Debug)]
pub(crate) struct Event {
inner: RxFuture,
}
impl Event {
fn new(signum: DWORD) -> io::Result<Self> {
global_init()?;
let rx = globals().register_listener(signum as EventId);
Ok(Self {
inner: RxFuture::new(rx),
})
}
}
fn global_init() -> io::Result<()> {
static INIT: Once = Once::new();
let mut init = None;
INIT.call_once(|| unsafe {
let rc = SetConsoleCtrlHandler(Some(handler), TRUE);
let ret = if rc == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
};
init = Some(ret);
});
init.unwrap_or_else(|| Ok(()))
}
unsafe extern "system" fn handler(ty: DWORD) -> BOOL {
let globals = globals();
globals.record_event(ty as EventId);
// According to https://docs.microsoft.com/en-us/windows/console/handlerroutine
// the handler routine is always invoked in a new thread, thus we don't
// have the same restrictions as in Unix signal handlers, meaning we can
// go ahead and perform the broadcast here.
if globals.broadcast() {
TRUE
} else {
// No one is listening for this notification any more
// let the OS fire the next (possibly the default) handler.
FALSE
}
}
#[cfg(docsrs)]
#[path = "windows/stub.rs"]
mod imp;
/// Creates a new stream which receives "ctrl-c" notifications sent to the
/// process.
@ -150,7 +45,9 @@ unsafe extern "system" fn handler(ty: DWORD) -> BOOL {
/// }
/// ```
pub fn ctrl_c() -> io::Result<CtrlC> {
Event::new(CTRL_C_EVENT).map(|inner| CtrlC { inner })
Ok(CtrlC {
inner: self::imp::ctrl_c()?,
})
}
/// Represents a stream which receives "ctrl-c" notifications sent to the process
@ -163,7 +60,7 @@ pub fn ctrl_c() -> io::Result<CtrlC> {
#[must_use = "streams do nothing unless polled"]
#[derive(Debug)]
pub struct CtrlC {
inner: Event,
inner: RxFuture,
}
impl CtrlC {
@ -191,7 +88,7 @@ impl CtrlC {
/// }
/// ```
pub async fn recv(&mut self) -> Option<()> {
self.inner.inner.recv().await
self.inner.recv().await
}
/// Polls to receive the next signal notification event, outside of an
@ -223,7 +120,7 @@ impl CtrlC {
/// }
/// ```
pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<()>> {
self.inner.inner.poll_recv(cx)
self.inner.poll_recv(cx)
}
}
@ -237,7 +134,7 @@ impl CtrlC {
#[must_use = "streams do nothing unless polled"]
#[derive(Debug)]
pub struct CtrlBreak {
inner: Event,
inner: RxFuture,
}
impl CtrlBreak {
@ -263,7 +160,7 @@ impl CtrlBreak {
/// }
/// ```
pub async fn recv(&mut self) -> Option<()> {
self.inner.inner.recv().await
self.inner.recv().await
}
/// Polls to receive the next signal notification event, outside of an
@ -295,7 +192,7 @@ impl CtrlBreak {
/// }
/// ```
pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<()>> {
self.inner.inner.poll_recv(cx)
self.inner.poll_recv(cx)
}
}
@ -320,56 +217,7 @@ impl CtrlBreak {
/// }
/// ```
pub fn ctrl_break() -> io::Result<CtrlBreak> {
Event::new(CTRL_BREAK_EVENT).map(|inner| CtrlBreak { inner })
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use crate::runtime::Runtime;
use tokio_test::{assert_ok, assert_pending, assert_ready_ok, task};
#[test]
fn ctrl_c() {
let rt = rt();
let _enter = rt.enter();
let mut ctrl_c = task::spawn(crate::signal::ctrl_c());
assert_pending!(ctrl_c.poll());
// Windows doesn't have a good programmatic way of sending events
// like sending signals on Unix, so we'll stub out the actual OS
// integration and test that our handling works.
unsafe {
super::handler(CTRL_C_EVENT);
}
assert_ready_ok!(ctrl_c.poll());
}
#[test]
fn ctrl_break() {
let rt = rt();
rt.block_on(async {
let mut ctrl_break = assert_ok!(super::ctrl_break());
// Windows doesn't have a good programmatic way of sending events
// like sending signals on Unix, so we'll stub out the actual OS
// integration and test that our handling works.
unsafe {
super::handler(CTRL_BREAK_EVENT);
}
ctrl_break.recv().await.unwrap();
});
}
fn rt() -> Runtime {
crate::runtime::Builder::new_current_thread()
.build()
.unwrap()
}
Ok(CtrlBreak {
inner: self::imp::ctrl_break()?,
})
}

View File

@ -0,0 +1,13 @@
//! Stub implementations for the platform API so that rustdoc can build linkable
//! documentation on non-windows platforms.
use crate::signal::RxFuture;
use std::io;
pub(super) fn ctrl_c() -> io::Result<RxFuture> {
panic!()
}
pub(super) fn ctrl_break() -> io::Result<RxFuture> {
panic!()
}

View File

@ -0,0 +1,153 @@
use std::convert::TryFrom;
use std::io;
use std::sync::Once;
use crate::signal::registry::{globals, EventId, EventInfo, Init, Storage};
use crate::signal::RxFuture;
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE};
use winapi::um::consoleapi::SetConsoleCtrlHandler;
use winapi::um::wincon::{CTRL_BREAK_EVENT, CTRL_C_EVENT};
pub(super) fn ctrl_c() -> io::Result<RxFuture> {
new(CTRL_C_EVENT)
}
pub(super) fn ctrl_break() -> io::Result<RxFuture> {
new(CTRL_BREAK_EVENT)
}
fn new(signum: DWORD) -> io::Result<RxFuture> {
global_init()?;
let rx = globals().register_listener(signum as EventId);
Ok(RxFuture::new(rx))
}
#[derive(Debug)]
pub(crate) struct OsStorage {
ctrl_c: EventInfo,
ctrl_break: EventInfo,
}
impl Init for OsStorage {
fn init() -> Self {
Self {
ctrl_c: EventInfo::default(),
ctrl_break: EventInfo::default(),
}
}
}
impl Storage for OsStorage {
fn event_info(&self, id: EventId) -> Option<&EventInfo> {
match DWORD::try_from(id) {
Ok(CTRL_C_EVENT) => Some(&self.ctrl_c),
Ok(CTRL_BREAK_EVENT) => Some(&self.ctrl_break),
_ => None,
}
}
fn for_each<'a, F>(&'a self, mut f: F)
where
F: FnMut(&'a EventInfo),
{
f(&self.ctrl_c);
f(&self.ctrl_break);
}
}
#[derive(Debug)]
pub(crate) struct OsExtraData {}
impl Init for OsExtraData {
fn init() -> Self {
Self {}
}
}
fn global_init() -> io::Result<()> {
static INIT: Once = Once::new();
let mut init = None;
INIT.call_once(|| unsafe {
let rc = SetConsoleCtrlHandler(Some(handler), TRUE);
let ret = if rc == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
};
init = Some(ret);
});
init.unwrap_or_else(|| Ok(()))
}
unsafe extern "system" fn handler(ty: DWORD) -> BOOL {
let globals = globals();
globals.record_event(ty as EventId);
// According to https://docs.microsoft.com/en-us/windows/console/handlerroutine
// the handler routine is always invoked in a new thread, thus we don't
// have the same restrictions as in Unix signal handlers, meaning we can
// go ahead and perform the broadcast here.
if globals.broadcast() {
TRUE
} else {
// No one is listening for this notification any more
// let the OS fire the next (possibly the default) handler.
FALSE
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use crate::runtime::Runtime;
use tokio_test::{assert_ok, assert_pending, assert_ready_ok, task};
#[test]
fn ctrl_c() {
let rt = rt();
let _enter = rt.enter();
let mut ctrl_c = task::spawn(crate::signal::ctrl_c());
assert_pending!(ctrl_c.poll());
// Windows doesn't have a good programmatic way of sending events
// like sending signals on Unix, so we'll stub out the actual OS
// integration and test that our handling works.
unsafe {
super::handler(CTRL_C_EVENT);
}
assert_ready_ok!(ctrl_c.poll());
}
#[test]
fn ctrl_break() {
let rt = rt();
rt.block_on(async {
let mut ctrl_break = assert_ok!(crate::signal::windows::ctrl_break());
// Windows doesn't have a good programmatic way of sending events
// like sending signals on Unix, so we'll stub out the actual OS
// integration and test that our handling works.
unsafe {
super::handler(CTRL_BREAK_EVENT);
}
ctrl_break.recv().await.unwrap();
});
}
fn rt() -> Runtime {
crate::runtime::Builder::new_current_thread()
.build()
.unwrap()
}
}