2

This program reads from stdin (via iostream) and writes to stdout (via boost/asio):

#include <boost/asio.hpp>
#include <cassert>
#include <iostream>
#include <string>

boost::asio::io_service io_service;
boost::asio::posix::stream_descriptor out(io_service, ::dup(STDOUT_FILENO));
std::string line = "";

static void handler(
    boost::system::error_code const &,
    std::size_t
) {
    assert(std::getline(std::cin, line));
    line += "\n";
    async_write(out, boost::asio::buffer(line), handler);
}

int main()
{
    async_write(out, boost::asio::buffer(line), handler);
    io_service.run();
}

build: g++ -std=gnu++1y -O0 -g3 -o out in.cxx -lboost_system -lboost_thread

run: cat | ./out

output:

foo [TERMINAL INPUT]
foo
bar [TERMINAL INPUT]
cat: -: Resource temporarily unavailable
bar
out: in.cxx:14: void handler(const boost::system::error_code&, std::size_t): Assertion `std::getline(std::cin, line)' failed.
Aborted (core dumped)

cat gets an EAGAIN from a write() to its standard output, treats it as an error, and closes the pipe. In turn getline() fails and the program aborts.

This looks like asio is setting the program's standard input (which is the standard output of cat, see Strange exception throw - assign: Operation not permitted) to non-blocking. It has no obvious reason to do this, because it is operating on standard output only, though.

If I get this right, is this a bug in asio? Is there a workaround?

libboost1.58-dev/xenial-updates,now 1.58.0+dfsg-5ubuntu3.1 amd64 [installed]
g++/xenial,now 4:5.3.1-1ubuntu1 amd64 [installed]
Community
  • 1
  • 1
not-a-user
  • 4,088
  • 3
  • 21
  • 37
  • I don't know how you infer that "asio is setting the program's standard input to non-blocking". I don't see that logic. And yes, if you redirect the output of your program to a file then you WILL get `assign: operation not permitted` because async-IO is not supported to files on linux. – sehe Feb 28 '17 at 13:12
  • @sehe: `cat` says `-: Resource temporarily unavailable` - so someone must have set the file descriptor to non-blocking. If it was not `cat` or the shell, it must be the program, i.e. asio. – not-a-user Mar 01 '17 at 07:02
  • @sehe: I'm not seeing the `assign: operation not permitted` error here. I only refer to where I learned that both ends of a shell pipe share the same file descriptor. – not-a-user Mar 01 '17 at 07:05
  • Oh. You should have said _that_. Also, I don't see anywhere where that claim is made ("both ends of a shell pipe share the same file descriptor"). More importantly I think it's false and irrelevant. [(fds are local to a process anyways, there is no useful comparison between fds from different processes)](http://lackingrhoticity.blogspot.nl/2015/05/passing-fds-handles-between-processes.html) – sehe Mar 01 '17 at 22:33
  • @sehe: Well it is not the same file descriptor, but their file descriptors refer to the same open file. So the flags that one sets (such as `O_NONBLOCK`) will be seen by the other. See my example at http://ideone.com/r4TVZd. – not-a-user Mar 02 '17 at 07:32

1 Answers1

2

You invoke Undefined Behaviour here:

async_write(out, boost::asio::buffer(string + "\n"), handler);

Because the buffer is a local variable it will be destroyed when handler exits, before the async write operation may get a chance to run.

EDIT Actually, even if the variable wasn't local, the + "\n" makes it a temporary!

Here's a suggested fix for this simple program:

Live On Coliru

#include <boost/asio.hpp>
#include <cassert>
#include <iostream>
#include <string>

boost::asio::io_service io_service;
boost::asio::posix::stream_descriptor out(io_service, ::dup(STDOUT_FILENO));
std::string input_buffer;

static void handler(boost::system::error_code /*error*/, std::size_t /*bytes_transferred*/) {
    if (std::getline(std::cin, input_buffer)) {
        input_buffer += "\n";
        async_write(out, boost::asio::buffer(input_buffer), handler);
    }
}

int main() {
    async_write(out, boost::asio::buffer(""), handler);
    io_service.run();
}

I'm not sure this solves all your problems, but at the very least you need to fix it in order to be able to reason about your program at all

sehe
  • 374,641
  • 47
  • 450
  • 633