Is calling asio::io_service::poll() or poll_one() in a nested or recursive fashion (ie. from within a handler) valid?
A really basic test seems to imply that this works (I've only done the test on one platform) but I want to be sure that calling poll() again from within a handler is considered valid behavior.
I couldn't find any relevant information in the asio docs, so I'm hoping that someone with a bit more experience with asio's inner workings could verify this with an explanation or references.
Basic test:
struct NestedHandler
{
NestedHandler(std::string name, asio::io_service * service) :
name(name),
service(service)
{
// empty
}
void operator()()
{
std::cout << " { ";
std::cout << name;
std::cout << " ...calling poll again... ";
service->poll();
std::cout << " } ";
}
std::string name;
asio::io_service * service;
};
struct DefaultHandler
{
DefaultHandler(std::string name) :
name(name)
{
// empty
}
void operator()()
{
std::cout << " { ";
std::cout << name;
std::cout << " } ";
}
std::string name;
};
int main()
{
asio::io_service service;
service.post(NestedHandler("N",&service));
service.post(DefaultHandler("A"));
service.post(DefaultHandler("B"));
service.post(DefaultHandler("C"));
service.post(DefaultHandler("D"));
std::cout << "asio poll" << std::endl;
service.poll();
return 0;
}
// Output:
asio poll
{ N ...calling poll again... { A } { B } { C } { D } }
It is valid.
For the family of functions that process the io_service
, run()
is the only one with restrictions:
The
run()
function must not be called from a thread that is currently calling one ofrun()
,run_one()
,poll()
orpoll_one()
on the sameio_service
object.
However, I am inclined to think that the documentation should also include the same remark for run_one()
, as a nested call can result in it blocking indefinitely for either of the following cases[1]:
io_service
is the handler currently being executedio_service
has a concurrency hint of 1
For Windows I/O completion ports, demultiplexing is performed in all threads servicing the io_service
using GetQueuedCompletionStatus()
. At a high-level, threads calling GetQueuedCompletionStatus()
function as if they are part of a thread pool, allowing the OS to dispatch work to each thread. As no single thread is responsible for demultiplexing operations to other threads, nested calls to poll()
or poll_one()
do not affect operation dispatching for other threads. The documentation states:
Demultiplexing using I/O completion ports is performed in all threads that call
io_service::run()
,io_service::run_one()
,io_service::poll()
orio_service::poll_one()
.
For all other demultiplexing mechanisms systems, a single thread servicing io_service
is used to demultiplex I/O operations. The exact demultiplexing mechanism can be found in the Platform-Specific Implementation Notes:
Demultiplexing using [
/dev/poll
,epoll
,kqueue
,select
] is performed in one of the threads that callsio_service::run()
,io_service::run_one()
,io_service::poll()
orio_service::poll_one()
.
The implementation for the demultiplexing mechanism differs slightly, but at a high-level:
io_service
has a main queue from which threads consume ready-to-run operations to performio_service
creates a private queue on the stack that is used to manage operations in a lock-free mannerWhen the io_service
is constructed, it may be provided a concurrency hint, suggesting how many threads the implementation should allow to run concurrently. When non-I/O completion port implementations are provided a concurrency hint of 1
, they are optimized to use the private queue as much as possible and defer synchronization with the main queue. For example, when a handler is posted via post()
:
io_service
guarantees thread safety so it locks the main queue before enqueueing the handler.When a nested poll()
or poll_one()
is invoked, it becomes necessary for the private queue to be copied into the main queue, as operations to be performed will be consumed from the main queue. This case is explicitly checked within the implementation:
// We want to support nested calls to poll() and poll_one(), so any handlers
// that are already on a thread-private queue need to be put on to the main
// queue now.
if (one_thread_)
if (thread_info* outer_thread_info = ctx.next_by_key())
op_queue_.push(outer_thread_info->private_op_queue);
When either no concurrency hint or any value other than 1
is provided, then posted handlers are synchronized into the main queue each time. As the private queue does not need to be copied, nested poll()
and poll_one()
calls will function as normal.
1. In the networking-ts draft, it is noted that run_one()
must not be called from a thread that is currently calling run()
.
Is calling asio::io_service::poll() or poll_one() in a nested or recursive fashion (ie. from within a handler) valid?
Sytaxically, this is valid. But, its not good bacause in every handler you should run poll(). Also, your stack trace will grow to very big sizes, and you can get big problems with the stack.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With