1

I'm reading stdin using Boost.ASIO, but when I pipe into it I would expect that the pipe would close when the input has been fully consumed. I.e. I'm doing this at the commmand line:

cat somefile.txt | myprog

And I'd expect that myprog will see the file close. Instead it waits forever.

The code looks like this:

boost::asio::posix::stream_descriptor as_stdin(ios);
{
    boost::system::error_code error;
    as_stdin.assign(dup(STDIN_FILENO), error);
    if ( error ) {
        exit(2);
    }
}
auto proc = [&as_stdinr](auto yield) {
        boost::asio::streambuf buffer;
        while ( as_stdin.is_open() ) {
            auto bytes = boost::asio::async_read_until(as_stdin, buffer, '\n', yield);
            if ( bytes ) {
                buffer.commit(bytes);
                std::istream in(&buffer);
                std::string line;
                std::getline(in, line);
                std::cerr << line << std::endl;
            } else {
                std::cerr << "No bytes read" << std::endl;
            }
        }
        std::cerr << "Done" << std::endl;
    };
boost::asio::spawn(ios, proc);

All of the file content is properly echoed, so reading from the pipe works fine, but neither of the "No bytes read" or "Done" messages are ever printed. I've tried both with and without the dup system call.

Am I misunderstanding how the pipe works, or am I doing something wrong or missing something else?

I think this comes down to "How do I detect EOF when using coroutines?"

KayEss
  • 2,290
  • 15
  • 31

1 Answers1

1

You could catch the exception from async_read_until

size_t bytes = 0;
bool eof = false;
try {
    bytes = boost::asio::async_read_until(as_stdin, buffer, '\n', yield);
} catch(std::exception const& e) {
    std::cerr << "Exception: " << e.what() << "\n";
    bytes = 0;
    eof = true;
}
// ...
if (eof) break;

Or use the error_code:

boost::system::error_code ec;
auto bytes = boost::asio::async_read_until(as_stdin, buffer, '\n', yield[ec]);
// ...
if (ec) {
    std::cerr << "Error: " << ec.message() << "\n";
    break;
}

Output is very similar in both cases

Exception: End of file
No bytes read
Done

Or

No bytes read
Error: End of file
Done

Limitations

Regular files cannot be used with POSIX stream_descriptor, see https://stackoverflow.com/a/23631715/85371

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • No exception is thrown, it just hangs. The file thing is taken care of with the error handling around the assign. I think the bit I'm missing is the way you attach the error return to the yield. I'll try that tomorrow and see how it goes – KayEss Nov 13 '16 at 14:22
  • I don't think that matters. Are you sure `stdin` is a support fd type (e.g. pipe or socket)? – sehe Nov 13 '16 at 14:31
  • Yes. If I run it without the pipe then the assign fails (as expected). I.e. this works 'cat some.txt | myprog' but this errors: 'myprog < some.txt'. And I'm perfectly fine with that. I just need to be able to recognise the end of the input – KayEss Nov 13 '16 at 14:40
  • Yeah yeah. Not need to push. Which platform, shell, versions etc? Does `stat some.txt` reveal anything unexpected (is it a fifo node, perchance) – sehe Nov 13 '16 at 14:43
  • Just trying to be helpful :) I just tried the code with the yield[error] and now I get an error of asio.misc:2 which would appear to be all I need. Do you think not throwing is a bug? – KayEss Nov 13 '16 at 14:51
  • 1
    It's inconsistent yes, so I'd file a bug on that. (FWIW both worked on my GCC 5.4 + Boost 1.62, linux) – sehe Nov 13 '16 at 14:52
  • I've been using clang 3.8 with boost 1.62 on Ubuntu Xenial. No exception. I'll file a bug – KayEss Nov 13 '16 at 14:55
  • 1
    @KayEss be sure to mention whether you used libstdc++ or libc++ – sehe Nov 13 '16 at 14:57