The boost docs say that cancelled async connect, send and receive finish immediately, and the handlers for cancelled operations will be passed the boost::asio::error::operation_aborted error.
I would like to find out if the cancelled handler gets to run (and see the operation_aborted error) before other (non-cancelled, and newly scheduled) completion handlers run.
Here is the timeline that concerns me:
acceptHandler and readHandler are running on the same event loop and the same thread.
- time t0 - readHandler is running on oldConnectionSocket
- time t1 - acceptHandler runs
- time t2 - acceptHandler calls oldConnectionSocket.cancel
- time t3 - acceptHandler closes oldConnectionSocket
- time t4 - acceptHandler calls newConnectionSocket.async_read(...readHandler...)
- time t5 - readHandler is called (from which context?)
Is it possible at t5 for readHandler to be called in the newConnectionSocket context before it is called with the operation_aborted error in the oldConnectionSocket context?
Cancelled operations will immediately post their handlers for deferred invocation. However, the io_service
makes no guarantees on the invocation order of handlers. Thus, the io_service
could choose to invoke the ReadHandlers in either order. Currently, only a strand
specifies guaranteed ordering under certain conditions.
Within a completion handler, if the goal is to know which I/O object was associated with the operation, then consider constructing the completion handler so that it has an explicit handle to the I/O object. This is often accomplished using any of the following:
- a custom functor
std::bind()
or boost::bind()
- a C++11 lambda
One common idiom is to have the I/O object be managed by a single class that inherits from boost::enable_shared_from_this<>
. When a class inherits from boost::enable_shared_from_this
, it provides a shared_from_this()
member function that returns a valid shared_ptr
instance to this
. A copy of the shared_ptr
is passed to completion handlers, such as a capture-list in lambdas or passed as the instance handle to boost::bind()
. This allows for the handlers to know the I/O object on which the operation was performed, and causes the lifetime of the I/O object to be extended to at least as long as the handler. See the Boost.Asio asynchronous TCP daytime server tutorial for an example using this approach.
class tcp_connection
: public boost::enable_shared_from_this<tcp_connection>
{
public:
// ...
void start()
{
boost::asio::async_write(socket_, ...,
boost::bind(&tcp_connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_write(
const boost::system::error_code& error,
std::size_t bytes_transferred)
{
// I/O object is this->socket_.
}
tcp::socket socket_;
};
On the other hand, if the goal is to determine if one handler has executed before the other, then:
- the application will need to explicitly manage the state
- trying to manage multiple dependent call chains may be introducing unnecessary complexity and often indicates a need to reexamine the design
- custom handlers could be used to prioritize the order in which handlers are executed. The Boost.Asio Invocation example uses custom handlers that are added to a priority queue, which are then executed at a later point in time.