What's the name of this property of asynchrono

2019-01-27 07:28发布

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.

1条回答
老娘就宠你
2楼-- · 2019-01-27 07:48

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

  1. When the socket object is created, its service, reactive_socket_service_base informs the reactor to initialize task (reactor::init_task()).
  2. The reactor will then inform the io_service to initialize for task. This causes a marker operation to be added to the io_service's operation queue.
  3. When the socket opens with a valid file descriptor, a struct of data associated with the file descriptor (descriptor_data) is registered with the reactor. This struct has its own operation queue, and is actually treated as an operation itself.
  4. The reactor extracts the file descriptor from descriptor_data and adds it to the list of file descriptors to observe within the reactor.

Initiation of asynchronous read operation

  1. boost::asio::detail::reactive_socket_service_base::async_receive() creates an boost::asio::detail::reactive_socket_recv_op object. This object is an operation object. Its perform() member function will try to read data from the socket, and its complete() member function will invoke the user's completion handler.
  2. boost::asio::detail::reactive_socket_service_base::start_op() is invoked with the read operation, which invokes the reactor::start_op().
  3. The reactor obtains the 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.

The socket gets data, and the completion of asynchronous read operation starts

  1. io_service::run*() is invoked.
  2. The 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().
  3. The reactor will block on 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.
  4. The operations passed back to the caller are then added to the io_service's operation queue, which now contains the descriptor_data operation and the original marker operation.
  5. The 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.
  6. 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.
  7. The read operation's 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.
  8. The operation is removed from the descriptor_data's operation queue, and added to the io_service via task_io_service::post_deferred_completion.

Notification of the asynchronous read operation

  1. The 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.
  2. The read operation is eventually iterated to, and its complete() member function invokes. Within reactive_socket_recv_op::do_complete, the user handler invokes with the error code and bytes transferred.

Cancellation

  1. boost::asio::detail::reactive_socket_service_base::cancel() invokes reactor::cancel_ops with descriptor_data.
  2. epoll_reactor::cancel_ops() iterates over the 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.

查看更多
登录 后发表回答