diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd38a52c2..499de7591 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: diff --git a/tokio-stream/Cargo.toml b/tokio-stream/Cargo.toml index 911657c37..64db51628 100644 --- a/tokio-stream/Cargo.toml +++ b/tokio-stream/Cargo.toml @@ -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"] diff --git a/tokio-stream/src/wrappers.rs b/tokio-stream/src/wrappers.rs index f2dc21fb1..8e218f02a 100644 --- a/tokio-stream/src/wrappers.rs +++ b/tokio-stream/src/wrappers.rs @@ -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}; } diff --git a/tokio/src/signal/unix.rs b/tokio/src/signal/unix.rs index f96b2f4c2..86ea9a93e 100644 --- a/tokio/src/signal/unix.rs +++ b/tokio/src/signal/unix.rs @@ -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; diff --git a/tokio/src/signal/windows.rs b/tokio/src/signal/windows.rs index c231d6268..11ec6cb08 100644 --- a/tokio/src/signal/windows.rs +++ b/tokio/src/signal/windows.rs @@ -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 { - 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 { - 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 { #[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> { - 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> { - self.inner.inner.poll_recv(cx) + self.inner.poll_recv(cx) } } @@ -320,56 +217,7 @@ impl CtrlBreak { /// } /// ``` pub fn ctrl_break() -> io::Result { - 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()?, + }) } diff --git a/tokio/src/signal/windows/stub.rs b/tokio/src/signal/windows/stub.rs new file mode 100644 index 000000000..88630543d --- /dev/null +++ b/tokio/src/signal/windows/stub.rs @@ -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 { + panic!() +} + +pub(super) fn ctrl_break() -> io::Result { + panic!() +} diff --git a/tokio/src/signal/windows/sys.rs b/tokio/src/signal/windows/sys.rs new file mode 100644 index 000000000..8d29c357b --- /dev/null +++ b/tokio/src/signal/windows/sys.rs @@ -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 { + new(CTRL_C_EVENT) +} + +pub(super) fn ctrl_break() -> io::Result { + new(CTRL_BREAK_EVENT) +} + +fn new(signum: DWORD) -> io::Result { + 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() + } +}