Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent boost::asio::io_context stopping on empty poll call

This code calls the posted handle

    boost::asio::io_context ioc;
    boost::asio::post(ioc, []{ std::cout << "lol" << std::endl; });
    ioc.poll();

whereas this doesn't:

    boost::asio::io_context ioc;
    ioc.poll(); // empty, set internal state to stopped_
    boost::asio::post(ioc, []{ std::cout << "lol" << std::endl; });
    ioc.poll(); // doesn't work as stopped() now returns true

Live example

Is it by design? If is, why? Can I configure io_context somehow to change this behavior?

like image 236
Mariusz Jaskółka Avatar asked Oct 23 '25 22:10

Mariusz Jaskółka


2 Answers

io_service/io_context are designed to stop when they run out of work¹.

The docs for io_service and io_context contain:

Stopping the io_context from running out of work

Some applications may need to prevent an io_context object's run() call from returning when there is no more work to do. For example, the io_context may be being run in a background thread that is launched prior to the application's asynchronous operations. The run() call may be kept running by creating an object of type boost::asio::executor_work_guard<io_context::executor_type>:

boost::asio::io_context io_context;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type>
  = boost::asio::make_work_guard(io_context);
...

To effect a shutdown, the application will then need to call the io_context object's stop() member function. This will cause the io_context run() call to return as soon as possible, abandoning unfinished operations and without permitting ready handlers to be dispatched.

Alternatively, if the application requires that all operations and handlers be allowed to finish normally, the work object may be explicitly reset.

boost::asio::io_context io_context;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type>
  = boost::asio::make_work_guard(io_context);
...
work.reset(); // Allow run() to exit.

Note that the "old-fashioned" Asio interface used a less generalized io_service::work object:

io_service ios;
io_service::work work(ios); // old interface!

This would then require you to do extra work for to be able to reset it:

asio::io_service ios;
std::optional<asio::io_service::work> work(ios);
// ...
work.reset();

Restarting

Finally when the context did run out of work, you will have to restart() it before re-using it:

enter image description here

Rationale

I think the rationale for the design comes from the library not having any opinions about how the service is run in terms of scheduling and threading, in combination with the guarantee that io_context/io_service must be thread-safe². See docs for background.


¹ Sidenote: Likewise, thread_pool (which is-a execution_context just like io_context is) is not designed for re-use (see e.g. Boost asio thread_pool join does not wait for tasks to be finished)

² Except object lifetime (construction/destruction), of course

like image 140
sehe Avatar answered Oct 25 '25 14:10

sehe


This documented (not in best place, it should be mentioned in poll documentation):

io_context::restart - develop

io_context::restart

Restart the io_context in preparation for a subsequent run() invocation.

void restart();

This function must be called prior to any second or later set of invocations of the run(), run_one(), poll() or poll_one() functions when a previous invocation of these functions returned due to the io_context being stopped or running out of work. After a call to restart(), the io_context object's stopped() function will return false.

This function must not be called while there are any unfinished calls to the run(), run_one(), poll() or poll_one() functions.

So basically you need to add restart to make it work.

https://wandbox.org/permlink/aXzz5GCAIMIvStnl

Here is an extra clue why.

like image 32
Marek R Avatar answered Oct 25 '25 14:10

Marek R