In the following example, the timer was associated with the io_context executor. But then, the handler is told to execute in the thread-pool. The reason is, because the handler actually executes blocking code, and I don't want to block the run
function of the io_context.
But the documentation states
Handlers are invoked only by a thread that is currently calling any overload of run(), run_one(), run_for(), run_until(), poll() or poll_one() for the io_context.
As the code shows, the handler is invoked by the thread_pool, outside of run
. Is this behavior well-defined? Does it work also for sockets?
#include <boost/asio.hpp>
#include <iostream>
int main() {
using namespace boost::asio;
thread_pool tp(4);
io_context ioc;
deadline_timer dt(ioc, boost::posix_time::milliseconds(500));
dt.async_wait(bind_executor(tp, [&](boost::system::error_code){
std::cout << "running in tp: " << tp.get_executor().running_in_this_thread() << std::endl;
std::cout << "running in ioc: " << ioc.get_executor().running_in_this_thread() << std::endl;
exit(0);
}));
auto wg = make_work_guard(ioc);
ioc.run();
}
running in tp: 1 running in ioc: 0
CodePudding user response:
First things first.
You can run a handler anywhere you want. Whether it results in UB depends on what you do in the handler.
I'll interpret your question as asking "Does the observed behaviour contradict the documented requirements/guarantees for handler invocation?"
Does This Contradict Documentation?
It does not.
You have two different execution contexts. One of them is io_context
(which you linked to the documentation of), and the other is thread_pool
.
You've asked to invoke the handler on tp
(by binding the handler to its executor).
Therefore, the default executor that deadline_timer
was bound to on construction is overruled by the handler's associated executor.
You're confusing yourself by looking at the documentation for io_context
that doesn't govern this case: The completion to the deadline-timer is never posted to the io_context
. It's posted to the thread_pool
's context, as per your specific request.
As expected, the handler is invoked on a thread running the execution context tp
.
Simplify And Elaborate
Here's a simplified example that uses post
directly, instead of going through an arbitrary async initiation function to do that.
In addition it exercises all the combinations of target executor and associated executors, if any.
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
int main() {
std::cout << std::boolalpha << std::left;
asio::io_context ioc;
asio::thread_pool tp(4);
auto xioc = ioc.get_executor();
auto xtp = tp.get_executor();
static std::mutex mx; // prevent jumbled output
auto report = [=](char const* id, auto expected) {
std::lock_guard lk(mx);
std::cout << std::setw(11) << id << " running in tp/ioc? "
<< xtp.running_in_this_thread() << '/'
<< xioc.running_in_this_thread() << " "
<< (expected.running_in_this_thread() ? "Ok" : "INCORRECT")
<< std::endl;
};
asio::post(tp, [=] { report("direct tp", xtp); });
asio::post(ioc, [=] { report("direct ioc", xioc); });
asio::post(ioc, bind_executor (tp, [=] { report("bound tp.A", xtp); }));
asio::post(tp, bind_executor (tp, [=] { report("bound tp.B", xtp); }));
asio::post( bind_executor (tp, [=] { report("bound tp.C", xtp); }));
asio::post(ioc, bind_executor (ioc, [=] { report("bound ioc.A", xioc); }));
asio::post(tp, bind_executor (ioc, [=] { report("bound ioc.B", xioc); }));
asio::post( bind_executor (ioc, [=] { report("bound ioc.C", xioc); }));
// system_executor doesn't have .running_in_this_thread()
// asio::post([=] { report("system", asio::system_executor{}); });
ioc.run();
tp.join();
}
Prints e.g.
direct tp running in tp/ioc? true/false Ok
direct ioc running in tp/ioc? false/true Ok
bound tp.C running in tp/ioc? true/false Ok
bound tp.B running in tp/ioc? true/false Ok
bound tp.A running in tp/ioc? true/false Ok
bound ioc.C running in tp/ioc? false/true Ok
bound ioc.B running in tp/ioc? false/true Ok
bound ioc.A running in tp/ioc? false/true Ok
Note that
- the bound executor always prevails
- there's a fallback to system executor (see its effect)
For semantics of post
see the docs or e.g. When must you pass io_context to boost::asio::spawn? (C )