asio C++ library

PrevUpHomeNext

Per-Operation Cancellation

[Note] Note

These type requirements and classes are the low level building blocks of cancellation. For most use cases, consider using a higher level abstraction, such as experimental::make_parallel_group or the logical operators for awaitable.

I/O objects, such as sockets and timers, support object-wide cancellation of outstanding asynchronous operations via their close or cancel member functions. However, certain asynchronous operations also support individual, targeted cancellation. This per-operation cancellation is enabled by specifying that a completion handler has an associated cancellation slot which satisfies the CancellationSlot type requirements. A cancellation slot is a lightweight channel used for delivering a cancellation request.

Given a copy of a user-defined Handler object h, if an asynchronous operation supports cancellation it will obtain a cancellation slot using the get_associated_cancellation_slot function. For example:

asio::associated_cancellation_slot_t<Handler> s
  = asio::get_associated_cancellation_slot(h);

The associated cancellation slot must satisfy the CancellationSlot type requirements.

By default, handlers use a default-constructed cancellation_slot, which means that per-operation cancellation is disabled. The cancellation slot may be customised for a particular handler type by specifying a nested type cancellation_slot_type and member function get_cancellation_slot():

class my_handler
{
public:
  // Custom implementation of CancellationSlot type requirements.
  typedef my_cancellation_slot cancellation_slot_type;

  // Return a custom cancellation slot implementation.
  cancellation_slot_type get_cancellation_slot() const noexcept
  {
    return my_cancellation_slot(...);
  }

  void operator()() { ... }
};

In more complex cases, the associated_cancellation_slot template may be partially specialised directly:

namespace asio {

  template <typename CancellationSlot>
  struct associated_cancellation_slot<my_handler, CancellationSlot>
  {
    // Custom implementation of CancellationSlot type requirements.
    typedef my_cancellation_slot type;

    // Return a custom cancellation_slot implementation.
    static type get(const my_handler&,
        const CancellationSlot& a = CancellationSlot()) noexcept
    {
      return my_cancellation_slot(...);
    }
  };

} // namespace asio

For convenience, a cancellation slot may be associated with a handler by using the bind_cancellation_slot function. This is particularly useful when associating a cancellation slot with a lambda:

asio::async_read(my_socket, my_buffer,
    asio::bind_cancellation_slot(
      my_cancellation_slot,
      [](asio::error_code e, std::size_t n)
      {
        ...
      }
    )
  );

Asio provides a ready-to-use cancellation slot in the form of cancellation_slot and its counterpart cancellation_signal. These two classes implement a one-to-one pairing of producer (signal) and consumer (slot) interfaces. The following example shows its use:

class session
  : public std::enable_shared_from_this<proxy>
{
  ...

  void do_read()
  {
    auto self = shared_from_this();
    socket_.async_read_some(
        buffer(data_),
        asio::bind_cancellation_slot(
          cancel_signal_.slot(),
          [self](std::error_code error, std::size_t n)
          {
            ...
          }
        )
      );
  }

  ...

  void request_cancel()
  {
    cancel_signal_.emit(asio::cancellation_type::total);
  }

  ...

  asio::cancellation_signal cancel_signal_;
};

A cancellation_signal contains a single slot, and consequently a cancellation signal/slot pair may be used with at most one operation at a time. However, the same slot may be reused for subsequent operations.

To support cancellation, an asynchronous operation installs a cancellation handler into the slot by calling the slot's assign or emplace functions. This handler will be invoked when a cancellation signal is emitted. A slot holds exactly one handler at a time, and installing a new handler will overwrite any previously installed handler.

When emitting a cancellation signal, the caller must specify a cancellation type. This value is a bitmask that dictates what guarantees the cancellation target must make if successful cancellation occurs. The possible bit values are, from weakest to strongest guarantee, are:

Table 1. cancellation types

Bit

Guarantee if cancellation is successful

Examples where this is the strongest supported guarantee

terminal

The operation had unspecified side effects, and it is only safe to close or destroy the I/O object.

A stateful implementation of a message framing protocol, where an asynchronous operation sends or receives a complete message. If cancellation occurs part-way through the message body, it is not possible to report a sensible state to the completion handler.

partial

The operation had well-defined side effects, and the completion handler for the operation indicates what these side effects were.

Composed operations such as async_read and async_write. If cancellation occurs before all bytes are transferred, the completion handler is passed the total bytes transferred so far. The caller may use this information to start another operation to transfer the remaining bytes.

total

The operation had no side effects that are observable through the API.

Low level system calls that transfer either zero or non-zero bytes.

Wait-for-readiness operations that have no side effects, even when successful.

A fully buffered message framing protocol implementation, where partial messages are stored so that they may be reused on the next operation.


For example, if application logic requires that an operation complete with all-or-nothing side effects, it should emit only the total cancellation type. If this type is unsupported by the target operation, no cancellation will occur.

Furthermore, a stronger guarantee always satisfies the requirements of a weaker guarantee. The partial guarantee still satisfies the terminal guarantee. The total guarantee satisfies both partial and total. This means that when an operation supports a given cancellation type as its strongest guarantee, it should honour cancellation requests for any of the weaker guarantees.

Cancellation requests should not be emitted during an asynchronous operation's initiating function. Cancellation requests that are emitted before an operation starts have no effect. Similarly, cancellation requests made after completion have no effect.

When emitting a cancellation signal, the thread safety rules apply as if calling a member function on the target operation's I/O object. For non-composed operations, this means that it is safe to emit the cancellation signal from any thread provided there are no other concurrent calls to the I/O object, and no other concurrent cancellation signal requests. For composed operations, care must be taken to ensure the cancellation request does not occur concurrently with the operation's intermediate completion handlers.

Supported Operations

Consult the documentation for individual asynchronous operations for their supported cancellation types, if any. The ability to cancel individual operations, or composed operations, is currently supported by:

See Also

CancellationSlot, associated_cancellation_slot, bind_cancellation_slot, cancellation_signal, cancellation_slot, cancellation_state, cancellation_type, get_associated_cancellation_slot, experimental::parallel_group, experimental::make_parallel_group


PrevUpHomeNext