[/
 / Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com)
 /
 / Distributed under the Boost Software License, Version 1.0. (See accompanying
 / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 /]

[section:cancellation Per-Operation Cancellation]

[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 [link boost_asio.reference.experimental__make_parallel_group
experimental::make_parallel_group] or the [link
boost_asio.overview.composition.cpp20_coroutines.co_ordinating_parallel_coroutines
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 [link
boost_asio.reference.associated_cancellation_slot associated cancellation slot]
which satisfies the [link boost_asio.reference.CancellationSlot 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:

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

The associated cancellation slot must satisfy the CancellationSlot type
requirements.

By default, handlers use a default-constructed [link
boost_asio.reference.cancellation_slot `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 boost { 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 boost::asio

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

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

Boost.Asio provides a ready-to-use cancellation slot in the form of [link
boost_asio.reference.cancellation_slot `cancellation_slot`] and its counterpart [link
boost_asio.reference.cancellation_signal `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_),
          boost::asio::bind_cancellation_slot(
            cancel_signal_.slot(),
            [self](boost::system::error_code error, std::size_t n)
            {
              ...
            }
          )
        );
    }

    ...

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

    ...

    boost::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 [link
boost_asio.reference.cancellation_type 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 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.[br]
      [br]
      Wait-for-readiness operations that have no side effects, even when
      successful.[br]
      [br]
      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 `terminal`. 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.

[heading 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:

* timers
* sockets on POSIX and Windows
* POSIX descriptors
* Windows HANDLEs
* signal sets
* serial ports
* SSL streams
* all Boost.Asio-provided composed operations such as `async_read` and `async_write`
* compositions based on `async_compose`
* C++20 coroutines that use `awaitable`
* C++20 coroutines that use `experimental::coro`
* the `experimental::parallel_group` operation
* the `experimental::promise` class

[heading See Also]

[link boost_asio.reference.CancellationSlot CancellationSlot],
[link boost_asio.reference.associated_cancellation_slot associated_cancellation_slot],
[link boost_asio.reference.bind_cancellation_slot bind_cancellation_slot],
[link boost_asio.reference.cancellation_signal cancellation_signal],
[link boost_asio.reference.cancellation_slot cancellation_slot],
[link boost_asio.reference.cancellation_state cancellation_state],
[link boost_asio.reference.cancellation_type cancellation_type],
[link boost_asio.reference.get_associated_cancellation_slot get_associated_cancellation_slot],
[link boost_asio.reference.experimental__parallel_group experimental::parallel_group],
[link boost_asio.reference.experimental__make_parallel_group experimental::make_parallel_group]

[endsect]
