From 246ae0894d4074c38ea2d2eb520e386b0d36d82d Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Thu, 24 Feb 2022 02:42:25 -0500 Subject: move to tokio --- src/blocking/input.rs | 3 ++- src/input.rs | 56 +++++++++++++++++++++++++++++++++++++-------------- src/lib.rs | 5 +---- src/output.rs | 31 ++++++++++++---------------- 4 files changed, 57 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/blocking/input.rs b/src/blocking/input.rs index 48d9ee8..63c9ac5 100644 --- a/src/blocking/input.rs +++ b/src/blocking/input.rs @@ -36,7 +36,8 @@ impl RawGuard { /// Switch back from raw mode early. /// /// # Errors - /// * `Error::SetTerminalMode`: failed to return the terminal from raw mode + /// * `Error::SetTerminalMode`: failed to return the terminal from raw + /// mode pub fn cleanup(&mut self) -> crate::error::Result<()> { self.termios.take().map_or(Ok(()), |termios| { let stdin = std::io::stdin().as_raw_fd(); diff --git a/src/input.rs b/src/input.rs index 90213ec..7f434f5 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,5 +1,5 @@ -use futures_lite::io::AsyncReadExt as _; use std::os::unix::io::AsRawFd as _; +use tokio::io::AsyncReadExt as _; use crate::private::Input as _; @@ -16,16 +16,20 @@ impl RawGuard { /// /// # Errors /// * `Error::SetTerminalMode`: failed to put the terminal into raw mode + // spawn_blocking is uncancellable, and the tcgetattr/tcsetattr calls + // can't panic, so unwrap is safe here + #[allow(clippy::missing_panics_doc)] pub async fn new() -> crate::error::Result { let stdin = std::io::stdin().as_raw_fd(); - let termios = blocking::unblock(move || { + let termios = tokio::task::spawn_blocking(move || { nix::sys::termios::tcgetattr(stdin) .map_err(crate::error::Error::SetTerminalMode) }) - .await?; + .await + .unwrap()?; let mut termios_raw = termios.clone(); nix::sys::termios::cfmakeraw(&mut termios_raw); - blocking::unblock(move || { + tokio::task::spawn_blocking(move || { nix::sys::termios::tcsetattr( stdin, nix::sys::termios::SetArg::TCSANOW, @@ -33,7 +37,8 @@ impl RawGuard { ) .map_err(crate::error::Error::SetTerminalMode) }) - .await?; + .await + .unwrap()?; Ok(Self { termios: Some(termios), }) @@ -42,11 +47,15 @@ impl RawGuard { /// Switch back from raw mode early. /// /// # Errors - /// * `Error::SetTerminalMode`: failed to return the terminal from raw mode + /// * `Error::SetTerminalMode`: failed to return the terminal from raw + /// mode + // spawn_blocking is uncancellable, and the tcsetattr call can't panic, so + // unwrap is safe here + #[allow(clippy::missing_panics_doc)] pub async fn cleanup(&mut self) -> crate::error::Result<()> { if let Some(termios) = self.termios.take() { let stdin = std::io::stdin().as_raw_fd(); - blocking::unblock(move || { + tokio::task::spawn_blocking(move || { nix::sys::termios::tcsetattr( stdin, nix::sys::termios::SetArg::TCSANOW, @@ -55,6 +64,7 @@ impl RawGuard { .map_err(crate::error::Error::SetTerminalMode) }) .await + .unwrap() } else { Ok(()) } @@ -66,11 +76,19 @@ impl Drop for RawGuard { /// of an async drop mechanism. If this could be a problem, you should /// call `cleanup` manually instead. fn drop(&mut self) { - futures_lite::future::block_on(async { - // https://github.com/rust-lang/rust-clippy/issues/8003 - #[allow(clippy::let_underscore_drop)] - let _ = self.cleanup().await; - }); + // doesn't literally call `cleanup`, because calling spawn_blocking + // while the tokio runtime is in the process of shutting down doesn't + // work (spawn_blocking tasks are cancelled if the runtime starts + // shutting down before the task body starts running), but should be + // kept in sync with the actual things that `cleanup` does. + if let Some(termios) = self.termios.take() { + let stdin = std::io::stdin().as_raw_fd(); + let _ = nix::sys::termios::tcsetattr( + stdin, + nix::sys::termios::SetArg::TCSANOW, + &termios, + ); + } } } @@ -80,8 +98,16 @@ impl Drop for RawGuard { /// additionally configure the types of keypresses you are interested in /// through the `parse_*` methods. This configuration can be changed between /// any two calls to [`read_key`](Input::read_key). +/// +/// # Note +/// +/// This is built on [`tokio::io::Stdin`], and inherits its caveats. In +/// particular, it will likely cause a hang until one more newline is received +/// when the tokio runtime shuts down. Because of this, it is generally +/// recommended to spawn a thread and use +/// [`textmode::blocking::Input`](crate::blocking::Input) instead. pub struct Input { - stdin: blocking::Unblock, + stdin: tokio::io::Stdin, raw: Option, buf: Vec, @@ -161,7 +187,7 @@ impl Input { #[must_use] pub fn new_without_raw() -> Self { Self { - stdin: blocking::Unblock::new(std::io::stdin()), + stdin: tokio::io::stdin(), raw: None, buf: Vec::with_capacity(4096), pos: 0, @@ -290,7 +316,7 @@ impl Input { } async fn read_stdin( - stdin: &mut blocking::Unblock, + stdin: &mut tokio::io::Stdin, buf: &mut [u8], ) -> crate::error::Result { stdin diff --git a/src/lib.rs b/src/lib.rs index 7e89eed..3bfb9b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,11 +8,8 @@ //! ```no_run //! use textmode::Textmode; //! # #[cfg(feature = "async")] +//! # #[tokio::main] //! # fn main() -> textmode::Result<()> { -//! # futures_lite::future::block_on(async { run().await }) -//! # } -//! # #[cfg(feature = "async")] -//! # async fn run() -> textmode::Result<()> { //! let mut tm = textmode::Output::new().await?; //! tm.clear(); //! tm.move_to(5, 5); diff --git a/src/output.rs b/src/output.rs index 9b72438..378b7d4 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,4 +1,4 @@ -use futures_lite::io::AsyncWriteExt as _; +use tokio::io::AsyncWriteExt as _; use crate::private::Output as _; @@ -16,11 +16,7 @@ impl ScreenGuard { /// # Errors /// * `Error::WriteStdout`: failed to write initialization to stdout pub async fn new() -> crate::error::Result { - write_stdout( - &mut blocking::Unblock::new(std::io::stdout()), - crate::INIT, - ) - .await?; + write_stdout(&mut tokio::io::stdout(), crate::INIT).await?; Ok(Self { cleaned_up: false }) } @@ -33,11 +29,7 @@ impl ScreenGuard { return Ok(()); } self.cleaned_up = true; - write_stdout( - &mut blocking::Unblock::new(std::io::stdout()), - crate::DEINIT, - ) - .await + write_stdout(&mut tokio::io::stdout(), crate::DEINIT).await } } @@ -46,10 +38,12 @@ impl Drop for ScreenGuard { /// of an async drop mechanism. If this could be a problem, you should /// call `cleanup` manually instead. fn drop(&mut self) { - futures_lite::future::block_on(async { - // https://github.com/rust-lang/rust-clippy/issues/8003 - #[allow(clippy::let_underscore_drop)] - let _ = self.cleanup().await; + tokio::task::block_in_place(move || { + tokio::runtime::Handle::current().block_on(async { + // https://github.com/rust-lang/rust-clippy/issues/8003 + #[allow(clippy::let_underscore_drop)] + let _ = self.cleanup().await; + }); }); } } @@ -61,7 +55,7 @@ impl Drop for ScreenGuard { /// then call [`refresh`](Output::refresh) when you want to update the /// terminal on `stdout`. pub struct Output { - stdout: blocking::Unblock, + stdout: tokio::io::Stdout, screen: Option, cur: vt100::Parser, @@ -112,8 +106,9 @@ impl Output { }; let cur = vt100::Parser::new(rows, cols, 0); let next = vt100::Parser::new(rows, cols, 0); + Self { - stdout: blocking::Unblock::new(std::io::stdout()), + stdout: tokio::io::stdout(), screen: None, cur, next, @@ -158,7 +153,7 @@ impl Output { } async fn write_stdout( - stdout: &mut blocking::Unblock, + stdout: &mut tokio::io::Stdout, buf: &[u8], ) -> crate::error::Result<()> { stdout -- cgit v1.2.3-54-g00ecf