Boost Asio Async Connection Race Condition? - c++

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?

The Asio library is gearing up to be standardized as NetworkingTS. This part is indeed the deal:
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
You are correct in concluding that the whole example is 100% single-threaded¹. There cannot be a race.
I personally feel the best resource is the Threads and Boost.Asio page:
By only calling io_context::run() from a single thread, the user's code can avoid the development complexity associated with synchronisation. For example, a library user can implement scalable servers that are single-threaded (from the user's point of view).
It also reiterates the truth from earlier:
[...] the following guarantee:
Asynchronous completion handlers will only be called from threads that are currently calling io_context::run().
¹ except potential internal service threads depending on platform/extensions, as the threads page details

Related

ASIO IO completion callbacks order vs the order of actual IO operations

It is obvious from the implementation that IO completion callbacks are invoked in the same order as the actual IO operations when running in a single thread mode, but I cannot find the respective part of the documentation confirming that. Is it written explicitly anywhere?
The documentation of all of the async_xxx methods on io-object classes have a passage like this:
Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a manner equivalent to using boost::asio::io_service::post().
Looking at the documentation of boost::asio::io_service::post()...
This function is used to ask the io_service to execute the given handler, but without allowing the io_service to call the handler from inside this function.
The io_service guarantees that the handler will only be called in a thread in which the run(), run_one(), poll() or poll_one() member functions is currently being invoked.
And that is the full extent of your guarantee.
If your code relies on the temporal ordering of asynchronous events, then it is not asynchronous code.
Even the documentation of run_one() make no guarantees about which handler it will dispatch:
The run_one() function blocks until one handler has been dispatched, or until the io_service has been stopped.
If you must sequence individual async operations (such as reads), then you are obliged to either:
initiate the second operation from the handler of the first, or
keep a flag set while an operations' handler is outstanding, and only initiate another operation when the flag is false.

Do boost asio sockets have proper RAII cleanup

I tried looking through source but I cant navigate that much of a template code.
Basically: this is what documentation says (for close()):
Remarks
For portable behaviour with respect to graceful
closure of a connected socket, call shutdown() before closing the socket.
I can do that manually, but if possible it would be nice to rely on RAII.
So if I have socket going out of scope do I need to call shutdown() and close() on it, or it will be done automatically?
One can rely on the socket performing proper cleanup with RAII.
When an IO object, such as socket, is destroyed, its destructor will invoke destroy() on the IO object's service, passing in an instance of the implementation_type on which the IO object's service will operate. The SocketService requirements state that destroy() will implicitly cancel asynchronous operations as-if by calling the close() on the service, which has a post condition that is_open() returns false. Furthermore, the service's close() will cause outstanding asynchronous operations to complete as soon as possible. Handlers for cancelled operations will be passed the error code boost::asio::error::operation_aborted, and scheduled for deferred invocation within the io_service. These handlers are removed from the io_service if they are either invoked from a thread processing the event loop or the io_service is destroyed.

C++ - several Boost.Asio related questions

io_service::run() is called by thread A. Is it safe to call async_write from thread B?
io_service::run() is called by thread A. Are async operations executed by thread A, or is thread A only guaranteed to call handlers and behind the scenes there could be additional threads that execute the operations?
io_service::run() is called by thread A. Some thread calls async_read and async_write using the same buffer. Is it safe to assume that the buffer will be accessed by at most one operation at a time? Or is it so that only handlers are called serially, but behind the scenes reads and writes can occur simultaneously?
The documentation says "The program must ensure that the stream performs no other read operations (such as async_read, the stream's async_read_some function, or any other composed operations that perform reads) until this operation completes.". Is it correct to interpret this as "You must not perform more than one read operation on a socket at a time. But you may perform 10 read operations on 10 distinct sockets."?
Having a socket that indefinitely accepts data, is it a good idea to call async_read and call it again from async_read's handler?
Does io_service::stop() stop all pending async operations or simply stops accepting new ones and executes the pending ones?
Yes, providing the io_service is tied to whatever is calling async_write. However, it should be noted that it is safe to call async_write from thread B even if the run is not called: it'll get queued in the io_service and wait until one of the run-ing calls are completed.
The callbacks posted to the io_service will run on thread A. Other async operations (such as timer operations) can happen on other threads. What is guarenteed to be on A and what is on its own thread is defined by the specific object being used, not by io_service.
Nope. Yup-ish. Depends on the class calling io_service.
Yes.
Yes, in fact this is super common, as it both ensures that only 1 async_read call is running at a time for a given socket and that there is always "work" for the io_service.
It usually finished the last callback and then stops accepting new ones and stops processing pending ones. It actually still accepts new ones but forces a reset is called before any other callbacks are called.
io_service is a message queue (basically), while a socket that posts its messages to the io_service is something else entirely.
1: Yes
4: Yes, it's okay to perform distinct operations on distinct sockets.
5: Yes, if you check the examples that's how they do it.
6: Considering the reference manual says
All invocations of its run() or run_one() member functions should return as soon as possible.
I would say it might do any.
For number 2 and 6, the source is available so the best way to answer those question is by downloading and reading it.

Clear boost::asio::io_service after stop()

I am using (single threaded) a boost::asio:io_service to handle a lot of tcp connections. For each connection I use a deadline_timer to catch timeouts. If any of the connections times out, I can use none of the results of the other connections. Therefore I want to completely restart my io_service. I thought that calling io_service.stop() would allow "finished" handlers in the queue to be called and would call handlers in the queue with an error.
However it looks like the handlers remain in the queue and therefore calling io_service.reset() and later io_service.run() brings the old handlers back up.
Can anyone confirm that the handlers indeed remain in the queue even after io_service.stop() is called. And if so, what are the possibilities to completly reset the io_service, e.g. remove all queued handlers?
io_service::stop() and io_service::reset() only control the state of the io_service's event loop; neither affect the lifespan of handlers scheduled for deferred invocation (ready-to-run) or user-defined handler objects.
The destructor for io_service will cause all outstanding handlers to be destroyed:
Each service object associated with the io_service will have its shutdown_service() member function invoked. Per the Service type requirement, the shutdown_service() member function will destroy all copies of user-defined handler objects that are held by the service.
Uninvoked handler objects scheduled for deferred invocation are destroyed for the io_service and any of its strands.
Consider either:
Controlling the lifespan of the io_service object. One approach can be found in this answer.
Running the io_service to completion. This often requires setting state, cancelling outstanding operations, and preventing completion handlers from posting additional work into the io_service. Boost.Asio provides an official timeout example, and a timeout approach with running to the io_service to completion is also shown here.

Boost asio - stopping io_service

I'm using boost::asio to do some very basic UDP packet collection. The io_service object is instantiated in a worker thread, and io_service.run() is called from inside that thread. My problem is getting io_service.run() to return when I am done collecting packets.
I'm not clear on what methods of io_service can be called from other threads when it comes time to stop my worker thread. I have a reference to the io_service object, and from a different thread I make this call:
ios.dispatch( boost::bind( &udp_server::handle_kill, this ) );
In my udp_server class, the handler for that function cancels the pending work from a single boost::asio::ip::udp::socket and a single boost::asio::deadline_timer object. Both have pending async work to do. At that point I call ios.stop():
void udp_server::handle_kill()
{
m_socket.cancel();
m_timer.cancel();
m_ios.stop();
}
With no work pending, I expect at this point that my call to ios.run() should return - but this does not happen.
So why does it not return? The most likely explanation to me is that I shouldn't be calling io_service::dispatch() from another thread. But the dispatch() method kind of seems like it was built to do just that - dispatch a function call in the thread that io_service::run() is working in. And it seems to do just that.
So this leaves me with a few related questions:
Am I using io_service::dispatch() correctly?
If all tasks are canceled, is there any reason that io_service::run() should not return?
socket::upd::cancel() doesn't seem to be the right way to close a socket and abort all work. What is the right way?
asio is behaving pretty well for me, but I need to get a better understanding of this bit of architecture.
More data
socket::udp::cancel() is apparently an unsupported operation on an open socket under Win32 - so this operation fails by throwing an exception - which does in fact cause an exit from io_service::run(), but definitely not the desired exit.
socket::udp::close() doesn't seem to cancel the pending async_receive_from() task, so calling it instead of socket::udp::cancel() seems to leave the thread somewhere inside io_service::run().
Invoking io_service::stop from another thread is safe, this is well described in the documentation
Thread Safety
Distinct objects: Safe.
Shared objects: Safe, with the
exception that calling reset() while
there are unfinished run(), run_one(),
poll() or poll_one() calls results in
undefined behaviour.
as the comments to your question indicate, you really need to boil this down to a reproducible example.