5
votes

My code uses boost::asio and io_service in a single thread to perform various socket operations. All operations are asynchronous and every handler depends on the boost::system::error_code (particularly boost::asio::error::operation_aborted) to determine the result of the operation.

It's been working perfectly well until I changed the logic to make several concurrent connections and pick the fastest one. That is, when the first async_read_some handler fires, I cancel other sockets (shutdown, close - everything) and proceed with the current one. In 95% of cases other sockets' read handlers are invoked with the operation_aborted error. However sometimes, these read handlers are invoked without errors, telling me that they have successfully received N bytes.

But the documentation for socket::cancel() states:

This function causes all outstanding asynchronous connect, send and receive operations to finish immediately, and the handlers for cancelled operations will be passed the boost::asio::error::operation_aborted error.

So, the questions: Can I really rely on the operation_aborted error in production code? If I can, is it a bug in Asio from boost 1.46.1? If I can't, is there any official documentation regarding this?

2
It seems in your case that multiple handlers have "succeeded" before the cancel was invoked. You can rely on the operation_aborted to be passed to any handlers that have not already executed (and are waiting to be called).Chad

2 Answers

13
votes

Ok, the answers:

  1. No, I cannot rely on the operation_aborted error only.
  2. Of course, it's not a bug in Asio, just a lack of experience on my side.
  3. There is a little bit of official documentation. It's for timers, not sockets, however the same principles apply:

If the timer has already expired when cancel() is called, then the handlers for asynchronous wait operations will:

  • have already been invoked; or
  • have been queued for invocation in the near future.

Basically, I was wrong in assumption that if I use a single thread for io_service, then every operation will be blocked while some handler executes.

The behavior I'm reporting actually makes a lot of sense and it seems that everyone who uses Asio knows that. I've combed through Asio's mailing lists and have found a lot of discussion on the subject here, here, here and here.

For instance, a write operation may complete successfully while you are inside a handler but before you have got around to calling socket cancel, causing its completion handler to be posted to the queue. As I understand it, the error code is determined by the status of the operation that completed, not the state of the socket at the time the handler is picked off the queue and executed.

2
votes

Consider two connections establishing at the same time. Both handlers will fire, one will be handled first, second is in the queue (or being handled on a different thread). One can think of more examples like that.

So in order to implement your requirement you need a bit more logic.