The property could be described like so: if the action gets cancelled, it's handler is guaranteed to be executed with an error.
For example, the boost::asio::deadline_timer doesn't have this property as described in the Remarks section of documentation for deadline_timer::cancel function. So it is possible that even though one cancels a waiting operation on a timer, its callback gets executed without an error.
On the other hand, the property holds for asio sockets (at least I hope so :) as there are no such remarks in the documentation).
EDIT: A pseudo-code demonstrating the lack of this property in deadline timers:
1# User calls timer.async_wait with a handler H which is to be
executed when the action finishes.
2# Time passes.
3# Timeout has been reached, asio internally inserts the handler H into
a queue for later execution, but with error code indicating success.
User is unaware of when this step takes place.
4# User calls cancel on the timer, thus would expect the handler to be
executed with an error code indicating failure.
5# Asio takes the handler H from the queue and executes it with error
code indicating success as set in the step #3.
It is easy to work around this problem by just setting a boolean flag in step #4 and then check it in step #5, so that is not the issue.
Neither the documentation nor the Networking Library Proposal for TR2 defines a term for asynchronous operations with a guaranteed error upon cancellation. However, all asynchronous operations, including
boost::asio::deadline_timer::async_wait()
, exhibit this behavior. Handlers are designed so that they always provide the status of the associated operation. Without this guarantee, developers would be in an unknown state within handlers when either cancellation or multiple operations occur.Cancellation only works on operations that have not yet taken place. I believe the documentation accentuates this only for the timer classes because of the difference in the visibility of the asynchronous operations. The operations on I/O objects have high visibility. For example, one could sniff the network to observe an asynchronous write operation on a socket. On the other hand, the timers' operations have low visibility. The wait operation's mechanics are an implementation detail within Boost.Asio, and the API does not provide the ability to perform external monitoring of the operation.
In the pseudo-code, the timeout has been reached, indicating the asynchronous wait operation has completed. As such, it can no longer be cancelled as the action has already taken place. Therefore, the handler will not be invoked with an error of
boost::asio::error::operation_aborted
. It is critical to understand that cancellation is an action, not a state change. Thus, there is no way to query the timer to see if cancellation had occurred. Also, if a user expects the cancellation to modify the handler's error code, then the user may be lost in the inherit complexity resulting from the separation in time between the initiation, completion, and notification of asynchronous operations.Everything below is very specific on implementation details. In this scenario, an asynchronously read is occuring on a socket using boost::asio::ip::tcp::socket::async_receive on system where Boost.Asio will use epoll for its reactor.
Initialization
reactive_socket_service_base
informs the reactor to initialize task (reactor::init_task()
).io_service
to initialize for task. This causes a marker operation to be added to theio_service
's operation queue.descriptor_data
) is registered with the reactor. This struct has its own operation queue, and is actually treated as an operation itself.descriptor_data
and adds it to the list of file descriptors to observe within the reactor.Initiation of asynchronous read operation
boost::asio::detail::reactive_socket_service_base::async_receive()
creates anboost::asio::detail::reactive_socket_recv_op
object. This object is an operation object. Itsperform()
member function will try to read data from the socket, and itscomplete()
member function will invoke the user's completion handler.boost::asio::detail::reactive_socket_service_base::start_op()
is invoked with the read operation, which invokes thereactor::start_op()
.descriptor_data
for the socket, locks the descriptor-specific mutex, pushes the operation into the descriptor-specific operation queue, informsio_service
that there is work, then unlocks the mutex.The socket gets data, and the completion of asynchronous read operation starts
io_service::run*()
is invoked.io_service
will only check in its operation queue if any operations are ready to run. In this case, the marker operation that was created during initialization is in the queue. The operation is identified as the marker, which indicates a reactor exists, and invokesreactor::run()
.epoll_wait
. When the socket's file descriptor has activity, then the event is identified. Thedescriptor_data
is extracted from the event, and pushes it into the caller's operation queue, asdescriptor_data
is an operation.io_service
's operation queue, which now contains thedescriptor_data
operation and the original marker operation.descriptor_data
operation is popped from theio_service
queue, and invokes thecomplete()
member function, causingepoll_reactor::descriptor_state::do_complete
to run.do_complete
invokesepoll_reactor::descriptor_state::perform_io
, where in the operations in thedescriptor_data
's operation queue are iterated over andperform()
is invoked. This includes theboost::asio::detail::reactive_socket_recv_op
operation that was pushed into the queue during the initiation of the asynchronous operation.perform()
member function will invokesocket_ops::non_blocking_recv()
, where the actual data is attempted to be read from the socket. The error code and bytes transferred are stored in the operation.descriptor_data
's operation queue, and added to theio_service
viatask_io_service::post_deferred_completion
.Notification of the asynchronous read operation
io_service
now has two operations in its queue: the completed read operation and the marker operation. Thereactor
has no operations in its queue.complete()
member function invokes. Withinreactive_socket_recv_op::do_complete
, the user handler invokes with the error code and bytes transferred.Cancellation
boost::asio::detail::reactive_socket_service_base::cancel()
invokesreactor::cancel_ops
withdescriptor_data
.epoll_reactor::cancel_ops()
iterates over thedescriptor_data
's operation queue. Each operation is popped from thedescriptor_data
's operation queue, has its error code set toboost::asio::error::operation_aborted
, then added to theio_service
's operation queue.Thus, cancellation only affects operations that have not yet been invoked by removing them from the
descriptor_data
's operation queue. If an operation has been invoked, then it has already been removed from thedescriptor_data
's operation queue and added to theio_service
operation queue.