On Mon, 2018-03-19 at 14:21 +0000, Thomas Quarendon via Boost-users wrote:
On 19 March 2018 at 10:33 tom@quarendon.net wrote:


Attempting to understand the implementation it feels like this could be made to work. 

Interestingly I notice that the basic_socket_streambuf class (in 1.67 at least), in accordance with the N4656 draft specification, DOES have support for timeouts. It implements the overflow and underflow calls using lower level asio calls, somewhat shadowing the implementation of socket_ops::sync_recv, but, crucially, not attempting an initial blocking read, then passing a timeout to the "poll_read" call:

      // Wait for socket to become ready.
      if (detail::socket_ops::poll_read(
            socket().native_handle(), 0, timeout(), ec_) < 0)



This would seem to suggest that fundamentally, reading with a timeout can be made to work, as it works fine here.

If this doesn't work out for you, here's another way - it relies on the fact that you can create an asio socket object from a socket handle, and that socket object can be owned by a different io_context.

This allows us to reserve a thread for a separate utility context:

std::string read_line_with_timeout(boost::asio::ip::tcp::socket &sock, boost::asio::streambuf &buf)
{
namespace asio = boost::asio;

// these statics could of course be encapsulated into a service object
static asio::io_context executor;
static asio::io_context::work work(executor);
static std::thread mythread{[&] { executor.run(); }};

auto temp_socket = asio::generic::stream_protocol::socket(executor,
sock.local_endpoint().protocol(),
dup(sock.native_handle()));
auto timer = asio::deadline_timer(executor, boost::posix_time::milliseconds(3000));

std::condition_variable cv;
std::mutex m;

int done_count = 0;
boost::system::error_code err;

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

auto aborted = [](boost::system::error_code const &ec) { return ec == boost::asio::error::operation_aborted; };

auto common_handler = [&](auto ec)
{
if (not aborted(ec))
{
auto lock = get_lock();
if (done_count++ == 0) {
err = ec;
boost::system::error_code sink;
temp_socket.cancel(sink);
timer.cancel(sink);
}
lock.unlock();
cv.notify_one();
}
};

async_read_until(temp_socket, buf, '\n', [&](auto ec, auto&&...) { common_handler(ec); });
timer.async_wait([&](auto ec)
{
common_handler(ec ? ec : asio::error::timed_out);
});

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

if (err) throw boost::system::system_error(err);
std::istream is(&buf);
std::string result;
std::getline(is, result);
return result;
}

So, for non SSL usage, replicating the same kind of logic that basic_socket_streambuf does would seem like it would work. However, that won't work for SSL. You'd basically have to create your own socket class that did this, and then wrap that in the ssl_stream. 

IMHO at least, having an equivalent of the "expiry" functionality provided by basic_socket_streambuf but at the tcp::socket class level would seem desirable. It seems odd that this functionality is considered useful by the N4656 draft specification at the basic_socket_streambuf level, but not at the tcp::socket level. And looking at what basic_socket_streambuf does, it wouldn't seem like it would be that complex. 

Thanks.
_______________________________________________
Boost-users mailing list
Boost-users@lists.boost.org
https://lists.boost.org/mailman/listinfo.cgi/boost-users