Search code examples
c++stdinfuturenonblocking

How to read stdin without blocking using C++ future?


A test program StdoutWriter writes some text ({"id":0,"cmd":1}) to stdout in 1 second and then again after 5 seconds and then waits 10 seconds and exits. I've ran this program by itself and verified the timing and output is correct.

Another test program StdinReader (code given below), using a future reads stdin and prints each line. StdinReader is not working as expected.

I launch the two programs from terminal with:

./StdoutWriter | ./StdinReader

The problem I'm seeing is that the reading of each line seems to be blocking until StdoutWriter exits.

The wrong output being observed looks like:

waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
you wrote {"id":0,"cmd":1}
waiting...
you wrote {"id":0,"cmd":1}
waiting...
you wrote 
waiting...
you wrote 
waiting...
you wrote 

Correct output should look like:

waiting...
waiting...
you wrote {"id":0,"cmd":1}
waiting...
waiting...
waiting...
waiting...
waiting...
you wrote {"id":0,"cmd":1}
waiting...
waiting...
waiting...
waiting...
you wrote 
waiting...
you wrote 
waiting...
you wrote

Here's the code for StdinReader which I got from: https://gist.github.com/vmrob/ff20420a20c59b5a98a1

#include <iostream>
#include <chrono>
#include <future>
#include <string>

std::string GetLineFromCin() {
    std::string line;
    std::getline(std::cin, line);
    return line;
}

int main() {

    auto future = std::async(std::launch::async, GetLineFromCin);

    while (true) {
        if (future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
            auto line = future.get();

            // Set a new line. Subtle race condition between the previous line
            // and this. Some lines could be missed. To aleviate, you need an
            // io-only thread. I'll give an example of that as well.
            future = std::async(std::launch::async, GetLineFromCin);

            std::cout << "you wrote " << line << std::endl;
        }

        std::cout << "waiting..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

Solution

  • Your StdoutWriter program is likely not actually writing to stdout like you think it is. If you explicitly flush your output stream after writing then your StdinReader will likely work as you expect.

    To improve performance, most I/O libraries do some level of buffering when you write to an output stream. That means that when you write to an output stream the library will generally just keep the data internally rather than writing it directly to its underlying OS file descriptor. This avoids paying the price of doing a syscall for every write.

    The level of buffering will often vary depending on the type of file/device you're writing to:

    • When writing to a terminal, output is very often line-buffered. That means the library will flush its internal buffers and write the output to the underlying file descriptor every time you write a newline character.

    • When writing to pipes or normal files output is generally fully-buffered though. That means that the library will only flush the data from its internal buffer to the underlying file descriptor when the buffer fills or you explicitly tell it to.