Something like this should do it (I have used streambuf mechanics, but any would suffice)

You need to ensure that some other thread is servicing the io_context

std::string timed_read_line(boost::asio::streambuf& buffer, boost::asio::ip::tcp::socket& sock) {
namespace asio = boost::asio;
using boost::system::error_code;

std::condition_variable cv;
std::mutex mut;
error_code op_error = error_code();
bool done = false;

auto get_lock = [&] { return std::unique_lock<std::mutex>(mut); };

auto read_handler = [&](error_code ec, std::size_t sz)
{
auto lock = get_lock();
if (not done)
{
done = true;
op_error = ec;
}
lock.unlock();
cv.notify_one();
};

auto timer_handler = [&](error_code ec)
{
auto lock = get_lock();
if (not done)
{
done = true;
op_error = asio::error::timed_out;
}
};


asio::async_read_until(sock, buffer, '\n', read_handler);
asio::deadline_timer timer(sock.get_io_context(), boost::posix_time::seconds(3));
timer.async_wait(timer_handler);

auto lock = get_lock();
cv.wait(lock, [&]{return done; });

if (op_error)
{
throw boost::system::system_error(op_error);
} else {
std::istream is(std::addressof(buffer));
std::string result;
std::getline(is, result);
return result;
}
};


On 16 March 2018 at 07:46, Jeremi Piotrowski via Boost-users <boost-users@lists.boost.org> wrote:
On Thu, Mar 15, 2018 at 07:04:19PM +0000, Thomas Quarendon via Boost-users wrote:
> The examples all revolve around the technique of starting an *async* read, then performing a nested loop of io_service::run_one. However, so far I've been unable to find a form of code that works reliably in a multithreaded environment.
>

I played around with this, and I don't really see how this can work
reliably when called from _within_ the io_service. I don't belive the
io_service was intended to be used in this re-entrant manner.

> The basis for my experimentation is here: https://gist.github.com/anonymous/1160c11f8ed9c29b9184325191a3a63b
> It starts a server thread, then starts a client that makes a connection and then writes nothing, to simulate a "bad" client and to provoke a "timeout" condition.
>
> [snip]
>
> With multiple threads, the handleReadTimeout/handleReadComplete callbacks are run on other threads. So the while loop here just blocks, as there is never anything to run. That's my surmise of what's going on anyway. I've experimented with strands to try and force it all onto the same thread, but so far failed (if the above code is called in the context of the same strand, it just seems to block the handleReadTimeout and handleReadComplete callbacks from being called).
>

Strands don't force it to the same thread, they just force the handlers to
not be run concurrently. Anyway, I found I can make your example work if
you add a separate io_service to execute the handlers for the blocking
connection. I believe all the example solutions that you linked to also
made the assumption that you wouldn't try to call the blocking reads from
within the io_service as well.

--- boost_asio_read_timeout     2018-03-16 08:15:18.877050171 +0100
+++ asio2.cpp   2018-03-16 08:41:02.677071294 +0100
@@ -31,12 +31,13 @@ public:
 };

 class BlockingConnection : public boost::enable_shared_from_this<BlockingConnection> {
+       boost::asio::io_service svc_;
        boost::asio::strand strand_;
        tcp::socket socket_;
 public:

        BlockingConnection(io_service& ioservice)
-               : strand_(ioservice), socket_(ioservice)
+               : strand_(svc_), socket_(svc_)
        {}

        tcp::socket& socket() {
@@ -62,7 +63,8 @@ public:
                async_read(socket_, buffer(b),
                        strand_.wrap(boost::bind(handleReadComplete, &ec, &bytesRead, &readComplete, &timer, _1, _2)));

-               while (socket_.get_io_service().run_one()) {
+               boost::asio::io_service::work work(svc_);
+               while (svc_.run_one()) {
                        if (timedOut && readComplete) {
                                break;
                        }
---

One thing to note: I thought I could get away with keeping
BlockingConnection.socket_ in the initial io_service, but found that this
will deadlock if all the threads of the initial io_service happen to be
executing this code at the same time. In that case there may be no thread
to service the actual 'async_read/timer' handlers (that in turn call
strand_.dispatch). Moving the BlockingConnection.socket_ to
BlockingConnection.svc_ fixes that.

> The alternative formulation, the one I started with actually, is to do an async wait on the timer, and a normal synchronous read on the socket. The timer callback performs a cancel (or close -- I tried both) on the socket hoping to cause the socket read to error. This is the kind of thing you'd do if you were programming raw sockets. That works fine on Windows, but won't work on Linux. The documentation for cancel does say it cancels any outstanding *async* calls, but I'm surprised calling close doesn't work and cause the read to wake.
>

The documentation also states that socket objects are _not_ thread safe.
Thats the real reason this doesn't work.
_______________________________________________
Boost-users mailing list
Boost-users@lists.boost.org
https://lists.boost.org/mailman/listinfo.cgi/boost-users