I am looking at the Boost Asio Blocking TCP Client timeout example with a special interest on how connection timeouts are implmented. How do we know from the documentation that the callback handler and subsequent checks don't introduce a race condition?
The Asynchronous connection command
boost::asio::async_connect(socket_, iter, var(ec) = _1);
executes the var(ec) = _1
which is the handler for setting the error code once execute. Alternatively, a full and explicit lambda could be used here.
At the same time, the check_deadline
function appears to be
called by the deadline_
member. The timeout appears to be enforced by having the deadline forcibly close the socket whereup we assume that perhaps that the blocking statement
do io_service_.run_one(); while (ec == boost::asio::error::would_block);
would return. At first I thought that the error code must be atomic but that doesn't appear to be the case. Instead, this page, appears to indicate that the strand model will work whenever the calls to the socket/context come from the same thread.
So we assume that each callback for the deadline (which is in Asio) and the handle for the async_connect
routine will not be run concurrently. Pages such as this in the documentation hint that handlers will only execute during run()
calls which will prevent the command while(ec == whatever)
from behind executed during the handler currently changing its value.
How do I know this explicitly? What in the documentation that tells me explicitly that no handlers will ever execute outside these routines? If true, the page on the proactor design pattern must infer this, but never explicitly where the "Initiator" leads to the "Completion Handler".
The closes I've found is the documentation for io_context saying
Synchronous operations on I/O objects implicitly run the io_context object for an individual operation. The io_context functions run(), run_one(), run_for(), run_until(), poll() or poll_one() must be called for the io_context to perform asynchronous operations on behalf of a C++ program. Notification that an asynchronous operation has completed is delivered by invocation of the associated handler. 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.
This implies that if I have one thread running the run_one()
command then its control path will wait until a handler is available and eventually wind its way through a handler whereupon it will return and check ther ec
value.
Is this correct and is "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." the best statement to find for understanding how the code will always function? Is there any other exposition?