Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stdin can block shutdown #2466

Open
Darksonn opened this issue Apr 29, 2020 · 3 comments
Open

Stdin can block shutdown #2466

Darksonn opened this issue Apr 29, 2020 · 3 comments
Labels
A-tokio Area: The main tokio crate C-bug Category: This is a bug. E-help-wanted Call for participation: Help is requested to fix this issue. I-hang Program never terminates, resulting from infinite loops, deadlock, livelock, etc. M-io Module: tokio/io

Comments

@Darksonn
Copy link
Contributor

Types such as Stdin and Stdout internally use blocking IO to read from the streams. This means that shutdown of the runtime can be blocked on threads with pending operations of this kind.

See #2318 as an example of when this happens.

@Darksonn Darksonn added A-tokio Area: The main tokio crate C-bug Category: This is a bug. E-help-wanted Call for participation: Help is requested to fix this issue. I-hang Program never terminates, resulting from infinite loops, deadlock, livelock, etc. M-io Module: tokio/io labels Apr 29, 2020
@jakobrs
Copy link

jakobrs commented Sep 26, 2021

Why doesn't tokio just epoll stdin directly in this case? stdin is a pipe and can therefore be used with epoll(7). Mio already supports this with mio::SourceFd, and tokio with tokio::io::unix::AsyncFd.

Here's the example in #2318, using tokio_fd::AsyncFd instead (which wraps tokio::io::unix::AsyncFd and implements AsyncRead and AsyncWrite):

use std::convert::TryFrom;

use tokio::io::AsyncReadExt;

#[tokio::main]
async fn main() {
    //let mut stdin = tokio::io::stdin(); // replaced with:
    let mut stdin = tokio_fd::AsyncFd::try_from(libc::STDIN_FILENO).unwrap();
    let mut buffer = [0; 8];

    let duration = std::time::Duration::from_secs(3);
    let timeout = tokio::time::sleep(duration);

    tokio::select! {
        _ = stdin.read(&mut buffer) => println!("reading done"),
        _ = timeout => println!("timeout!"),
    }

    println!("the end")
}

With this change the program no longer blocks when shutting down.

@sfackler
Copy link
Contributor

  1. stdin is sometimes a pipe.
  2. Setting a pipe to nonblocking affects both ends of the pipe, so it's not really a thing you can do in the general case without breaking upstream and/or downstream processes in a pipeline.

@bnoordhuis
Copy link
Contributor

It's not just pipes but e.g. ptys too.

I'd generalize it to the following observation: stdio file descriptors often exist in multiple non-cooperating processes. Changing the properties of a file descriptor in one process affects all other processes.

One linux-only workaround, at least for pipes, is to reopen the file descriptor through /proc/self/fd/<fd>.

For ptys, a workaround is to reopen /dev/pts/<fd> with the O_NOCTTY flag set if it's the slave/secondary. For master/primary ptys, blocking I/O is the only solution I'm aware of.

chamons added a commit to chamons/rusty-socket-echo that referenced this issue Aug 25, 2022
Overall tokio makes this little echo example a lot simpler/clean. 

I ran into trouble with stdin reading blocking exit of the client due to tokio-rs/tokio#2466, but shoving it on to a thread worked wonders.
Slixe added a commit to xelis-project/xelis-blockchain that referenced this issue Jan 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-tokio Area: The main tokio crate C-bug Category: This is a bug. E-help-wanted Call for participation: Help is requested to fix this issue. I-hang Program never terminates, resulting from infinite loops, deadlock, livelock, etc. M-io Module: tokio/io
Projects
None yet
Development

No branches or pull requests

4 participants