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 bo
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.
reactor::init_task()
).io_service
to initialize for task. This causes a marker operation to be added to the io_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.perform()
member function will try to read data from the socket, and its complete()
member function will invoke the user's completion handler.descriptor_data
for the socket, locks the descriptor-specific mutex, pushes the operation into the descriptor-specific operation queue, informs io_service
that there is work, then unlocks the mutex.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 invokes reactor::run().epoll_wait
. When the socket's file descriptor has activity, then the event is identified. The descriptor_data
is extracted from the event, and pushes it into the caller's operation queue, as descriptor_data
is an operation.io_service
's operation queue, which now contains the descriptor_data
operation and the original marker operation.descriptor_data
operation is popped from the io_service
queue, and invokes the complete()
member function, causing epoll_reactor::descriptor_state::do_complete to run.do_complete
invokes epoll_reactor::descriptor_state::perform_io
, where in the operations in the descriptor_data
's operation queue are iterated over and perform()
is invoked. This includes the boost::asio::detail::reactive_socket_recv_op
operation that was pushed into the queue during the initiation of the asynchronous operation.perform()
member function will invoke socket_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 the io_service
via task_io_service::post_deferred_completion.io_service
now has two operations in its queue: the completed read operation and the marker operation. The reactor
has no operations in its queue.complete()
member function invokes. Within reactive_socket_recv_op::do_complete, the user handler invokes with the error code and bytes transferred.reactor::cancel_ops
with descriptor_data
.descriptor_data
's operation queue. Each operation is popped from the descriptor_data
's operation queue, has its error code set to boost::asio::error::operation_aborted
, then added to the io_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 the descriptor_data
's operation queue and added to the io_service
operation queue.