Steven Faludi
Steven Faludi

Reputation: 31

When do handlers for cancelled boost::asio handlers get to run?

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.

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?

Upvotes: 3

Views: 1120

Answers (1)

Tanner Sansbury
Tanner Sansbury

Reputation: 51891

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.

Upvotes: 5

Related Questions