1

I have a fairly simple use case. I would like to read from a boost socket using boost::asio::read, but with a timeout for the read call. I.e. If nothing nothing is read from the socket in 5 seconds, the call should terminate/throw-an-error/whatever. The code without a timeout is shown below:

Json::Value Client::MakeRequest(const std::string &ip_addr, unsigned short port,
                                const Json::Value &request)
{
    boost::asio::io_context io_context;
    Json::StreamWriterBuilder writer_;
    std::string serialized_req = Json::writeString(writer_, request);
    tcp::socket s(io_context);

    tcp::resolver resolver(io_context);

    try {
        s.connect({boost::asio::ip::address::from_string(ip_addr), port});
    } catch(const std::exception &err) {
        throw std::runtime_error(err.what());
    }
    boost::asio::write(s, boost::asio::buffer(serialized_req));
    s.shutdown(tcp::socket::shutdown_send);

    error_code ec;
    char reply[2048];
    
    // I would like to replace this with a call which times out.
    size_t reply_length = boost::asio::read(s, boost::asio::buffer(reply),
                                            ec);
    ...
}

There are examples on stackoverflow stating how to accomplish this using the deprecated boost::asio::io_service. However, my code uses io_context instead, so this is not viable. This code, from an answer by Tom Trebicky in 2017, accomplishes my task using io_service:

template <typename SyncReadStream, typename MutableBufferSequence>
void readWithTimeout(SyncReadStream& s, const MutableBufferSequence& buffers, const boost::asio::deadline_timer::duration_type& expiry_time)
{
    boost::optional<boost::system::error_code> timer_result;
    boost::asio::deadline_timer timer(s.get_io_service());
    timer.expires_from_now(expiry_time);
    timer.async_wait([&timer_result] (const boost::system::error_code& error) { timer_result.reset(error); });

    boost::optional<boost::system::error_code> read_result;
    boost::asio::async_read(s, buffers, [&read_result] (const boost::system::error_code& error, size_t) { read_result.reset(error); });

    s.get_io_service().reset();
    while (s.get_io_service().run_one())
    { 
        if (read_result)
            timer.cancel();
        else if (timer_result)
            s.cancel();
    }

    if (*read_result)
        throw boost::system::system_error(*read_result);
}

I have tried to convert this to using io_context, but to no avail. Can someone provide a version which reads from a socket with timeouts using io_context?

At present, my attempt to transfer this over to io_context is this:

    template <typename SyncReadStream, typename MutableBufferSequence>
    static void ReadWithTimeout(SyncReadStream &s,
                                const MutableBufferSequence &buffers,
                                const boost::asio::deadline_timer::duration_type
                                &expiry_time)
    {
        boost::optional<boost::system::error_code> timer_result;
        boost::asio::deadline_timer timer(s.get_executor().context());
        timer.expires_from_now(expiry_time);
        timer.async_wait([&timer_result] (const error_code& error)
                                          {
                                            timer_result.reset(error);
                                          });

        boost::optional<boost::system::error_code> read_result;
        boost::asio::async_read(s, buffers,
                                [&read_result] (const error_code& error, size_t)
                                {
                                    read_result.reset(error);
                                });

        s.get_executor().template target<boost::asio::io_context>()->reset();
        while (s.get_executor().template target<boost::asio::io_context>()->run_one())
        {
            if (read_result)
                timer.cancel();
            else if (timer_result)
                s.cancel();
        }

        if (*read_result)
            throw boost::system::system_error(*read_result);
    }

This gives the following error:

====================[ Build | chord_and_dhash | Debug ]=========================
/opt/clion-2021.1.3/bin/cmake/linux/bin/cmake --build /home/patrick/CLionProjects/chord_and_dhash/cmake-build-debug --target chord_and_dhash -- -j 6
[  8%] Built target gtest
[ 26%] Built target jsoncpp_lib
[ 34%] Built target gtest_main
Scanning dependencies of target chord_and_dhash
[ 39%] Building CXX object CMakeFiles/chord_and_dhash.dir/src/chord/abstract_chord_peer.cpp.o
[ 43%] Building CXX object CMakeFiles/chord_and_dhash.dir/src/chord/chord_peer.cpp.o
[ 47%] Building CXX object CMakeFiles/chord_and_dhash.dir/test/server_test.cpp.o
[ 56%] Building CXX object CMakeFiles/chord_and_dhash.dir/src/chord/remote_peer.cpp.o
[ 56%] Building CXX object CMakeFiles/chord_and_dhash.dir/src/networking/client.cpp.o
[ 60%] Building CXX object CMakeFiles/chord_and_dhash.dir/test/chord_test.cpp.o
In file included from /usr/include/boost/asio/basic_socket.hpp:22,
                 from /usr/include/boost/asio/basic_datagram_socket.hpp:20,
                 from /usr/include/boost/asio.hpp:24,
                 from /home/patrick/CLionProjects/chord_and_dhash/src/networking/client.h:15,
                 from /home/patrick/CLionProjects/chord_and_dhash/src/networking/client.cpp:1:
/usr/include/boost/asio/detail/io_object_impl.hpp: In instantiation of ‘boost::asio::detail::io_object_impl<IoObjectService, Executor>::io_object_impl(ExecutionContext&, typename std::enable_if<std::is_convertible<ExecutionContext&, boost::asio::execution_context&>::value>::type*) [with ExecutionContext = boost::asio::execution_context; IoObjectService = boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >; Executor = boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > >; typename std::enable_if<std::is_convertible<ExecutionContext&, boost::asio::execution_context&>::value>::type = void]’:
/usr/include/boost/asio/basic_deadline_timer.hpp:182:20:   required from ‘boost::asio::basic_deadline_timer<Time, TimeTraits, Executor>::basic_deadline_timer(ExecutionContext&, typename std::enable_if<std::is_convertible<ExecutionContext&, boost::asio::execution_context&>::value>::type*) [with ExecutionContext = boost::asio::execution_context; Time = boost::posix_time::ptime; TimeTraits = boost::asio::time_traits<boost::posix_time::ptime>; Executor = boost::asio::execution::any_executor<boost::asio::execution::context_as_t<boost::asio::execution_context&>, boost::asio::execution::detail::blocking::never_t<0>, boost::asio::execution::prefer_only<boost::asio::execution::detail::blocking::possibly_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::tracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::outstanding_work::untracked_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::fork_t<0> >, boost::asio::execution::prefer_only<boost::asio::execution::detail::relationship::continuation_t<0> > >; typename std::enable_if<std::is_convertible<ExecutionContext&, boost::asio::execution_context&>::value>::type = void]’
/home/patrick/CLionProjects/chord_and_dhash/src/networking/client.h:45:37:   required from ‘static void Client::ReadWithTimeout(SyncReadStream&, const MutableBufferSequence&, const duration_type&) [with SyncReadStream = boost::asio::basic_stream_socket<boost::asio::ip::tcp>; MutableBufferSequence = boost::asio::mutable_buffers_1; boost::asio::basic_deadline_timer<boost::posix_time::ptime>::duration_type = boost::posix_time::time_duration]’
/home/patrick/CLionProjects/chord_and_dhash/src/networking/client.cpp:70:81:   required from here
/usr/include/boost/asio/detail/io_object_impl.hpp:61:25: error: ‘class boost::asio::execution_context’ has no member named ‘get_executor’
   61 |       executor_(context.get_executor())
      |                 ~~~~~~~~^~~~~~~~~~~~
gmake[3]: *** [CMakeFiles/chord_and_dhash.dir/build.make:199: CMakeFiles/chord_and_dhash.dir/src/networking/client.cpp.o] Error 1
gmake[3]: *** Waiting for unfinished jobs....
In file included from /home/patrick/CLionProjects/chord_and_dhash/src/chord/../data_structures/database.h:17,
                 from /home/patrick/CLionProjects/chord_and_dhash/src/chord/abstract_chord_peer.h:42,
                 from /home/patrick/CLionProjects/chord_and_dhash/src/chord/abstract_chord_peer.cpp:1:
/home/patrick/CLionProjects/chord_and_dhash/src/chord/../data_structures/merkle_node.h: In member function ‘CSMerkleNode<DataType>::operator Json::Value() const’:
/home/patrick/CLionProjects/chord_and_dhash/src/chord/../data_structures/merkle_node.h:294:5: warning: no return statement in function returning non-void [-Wreturn-type]
  294 |     }
      |     ^
In file included from /home/patrick/CLionProjects/chord_and_dhash/test/../src/chord/../data_structures/database.h:17,
                 from /home/patrick/CLionProjects/chord_and_dhash/test/../src/chord/abstract_chord_peer.h:42,
                 from /home/patrick/CLionProjects/chord_and_dhash/test/../src/chord/chord_peer.h:21,
                 from /home/patrick/CLionProjects/chord_and_dhash/test/chord_test.cpp:2:
/home/patrick/CLionProjects/chord_and_dhash/test/../src/chord/../data_structures/merkle_node.h: In member function ‘CSMerkleNode<DataType>::operator Json::Value() const’:
/home/patrick/CLionProjects/chord_and_dhash/test/../src/chord/../data_structures/merkle_node.h:294:5: warning: no return statement in function returning non-void [-Wreturn-type]
  294 |     }
      |     ^
In file included from /home/patrick/CLionProjects/chord_and_dhash/src/chord/../data_structures/database.h:17,
                 from /home/patrick/CLionProjects/chord_and_dhash/src/chord/abstract_chord_peer.h:42,
                 from /home/patrick/CLionProjects/chord_and_dhash/src/chord/chord_peer.h:21,
                 from /home/patrick/CLionProjects/chord_and_dhash/src/chord/chord_peer.cpp:1:
/home/patrick/CLionProjects/chord_and_dhash/src/chord/../data_structures/merkle_node.h: In member function ‘CSMerkleNode<DataType>::operator Json::Value() const’:
/home/patrick/CLionProjects/chord_and_dhash/src/chord/../data_structures/merkle_node.h:294:5: warning: no return statement in function returning non-void [-Wreturn-type]
  294 |     }
      |     ^
gmake[2]: *** [CMakeFiles/Makefile2:313: CMakeFiles/chord_and_dhash.dir/all] Error 2
gmake[1]: *** [CMakeFiles/Makefile2:320: CMakeFiles/chord_and_dhash.dir/rule] Error 2
gmake: *** [Makefile:183: chord_and_dhash] Error 2
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • please show a [mre] of what you've tried and what error you encountered. `io_context` and `io_service` are mostly the same just with a different name – Alan Birtles Oct 16 '21 at 20:17

1 Answers1

1

The most straight-forward take on your initial function I could imagine is:

json::value MakeRequest(const std::string& ip_addr, uint16_t port,
                        const json::value& request)
{
    boost::asio::io_context io;

    tcp::socket s(io);

    // connect, send
    s.connect({boost::asio::ip::address::from_string(ip_addr), port});
    boost::asio::write(s, boost::asio::buffer(serialize(request)));
    s.shutdown(tcp::socket::shutdown_send);

    // read for max 5s
    boost::asio::steady_timer timer(io, 5s);
    timer.async_wait([&](error_code ec) { s.cancel(); });

    std::string reply_buf;
    error_code  reply_ec;
    async_read(s, boost::asio::dynamic_buffer(reply_buf, 2048),
           [&](error_code ec, size_t) { timer.cancel(); reply_ec = ec; });

    io.run();

    if (!reply_ec || reply_ec == boost::asio::error::eof) {
        return json::parse(reply_buf);
    } else {
        throw boost::system::system_error(reply_ec);
    }
}
sehe
  • 374,641
  • 47
  • 450
  • 633