Search code examples
c++boost-asioasio

What is the most basic Asio execution context for manually driving non-blocking native handles?


Let's say I have an existing program with an existing main loop done "the traditional way", and I can register to be notified when a file descriptor is readable, etc. For the purposes of this question, let's presume that we don't want to rework this loop to use Asio.

It seems that I can then take, for example, an Asio datagram socket, set it to native_non_blocking(), monitor its fd for read events, and then react to such events by performing "blocking" receive_from() calls and expect them to return the equivalent of EAGAIN or EWOULDBLOCK via an error_code. Similarly for write.

(Let's ignore whether this is "in the spirit" of Asio. That's not really the purpose of the question.)

My question then is, what is the minimum execution context that I would need to provide to do this? It seems like I wouldn't really need an io_context (which, on Linux, for example, opens a few additional fds for epoll, signalfd, and timerfd) because I should never actually use it. It also seems like I wouldn't really want a system_context or a thread_pool because I don't really want anything to run on some other threads.

Is there some pre-defined "same thread" context that would be appropriate here? If now, how would one go about constructing such a thing? Or, does the exposed API on an Asio socket still "sometimes" actually use the functionality of an io_context under the covers even when the making "blocking" receive_from() or send_to() calls on a non-blocking socket in some hidden micro event loop (and if so, why)?


Solution

  • Is there some pre-defined "same thread" context that would be appropriate here?

    io_context does only provide the context, not subsuming anything about what threads use it. Therefore

    boost::asio::io_context io;
    io.run();
    

    Is already your minimal context.

    Your confusion then seems to come from the fact that run() intends to provide the event-loop, which appears as blocking behaviour. There are two ways about this:

    • do not call run() anywhere (this limits your options, but if indeed you ONLY vow to use the native backdoors (native_handle() and friends) you shouldn't care.

    • integrate run_one() or poll_one() into your event loop and be happy. This allows you to use the full extent of Asio services that build on the io_context abstraction and its executor(s), while you can obviously also use the backdoors¹.

    Side note: do not forget to properly handle exceptions emanating from {run,poll}[_one]()


    ¹ at your own risk, because combining them means you understand the implications on all the platforms you are going to support