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