| Home | Libraries | People | FAQ | More

### Per-Operation Cancellation

In Boost.Asio, various objects such as sockets and timers offer the ability to terminate all ongoing asynchronous operations globally through their `close` or `cancel` member functions. Beyond this global cancellation capability, Boost.Asio also provides mechanisms for cancelling specific asynchronous operations on an individual basis. This individual per-operation cancellation is enabled by specifying that a completion handler has an associated cancellation slot. If the asynchronous operation supports cancellation, then it will install a cancellation handler into the associated slot. The cancellation handler will be invoked when the user emits a cancellation signal into the cancellation slot. See Per-Operation Cancellation for more information.

The utility of per-operation cancellation becomes particularly evident when using `boost::asio::experimental::parallel_group` to launch work performed in parallel and wait for one or all of the operations to complete. For instance, in a parallel group awaiting the completion of any one operation among several, completing one operation will trigger individual cancellation of all the remaining operations. The same concept applies to the `awaitable operator ||`, which runs two awaitables in parallel, waiting for one to complete and subsequently cancelling the remaining one. See Co-ordinating Parallel Operations for more information.

Within the ``mqtt_client``, every asynchronous function is designed to support individual per-operation cancellation. This allows for associating of a cancellation slot with any `async_xxx` function call, enabling the emission of a cancellation signal as needed. The impact of emitting a cancellation signal varies depending on the signal type (terminal, total, partial) and the operation being cancelled. Detailed descriptions of how cancellation signals affect each `async_xxx` function are provided in the Per-Operation Cancellation paragraph in their respective sections of the ``mqtt_client`` reference documentation.

##### Example: associating a cancellation slot with an asynchronous operation

This example illustrates associating a cancellation slot with a `mqtt_client::async_publish` operation and emitting a terminal cancellation signal. Executing this sequence effectively results in the immediate cancellation of the entire client operation, mirroring the outcome of invoking `mqtt_client::cancel` directly.

If a total or partial cancellation signal were issued instead of a terminal one, the implications would be less severe. In such cases, the cancellation would specifically target resending the `PUBLISH` packet, preventing it from being retransmitted should the client reconnect during the ongoing operation.

    boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);

    client.brokers("<your-mqtt-broker>", 1883)
        .async_run(boost::asio::detached);

    boost::asio::cancellation_signal signal;

    client.async_publish<boost::mqtt5::qos_e::at_least_once>(
        "<topic>", "Hello world!",
        boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {},
        boost::asio::bind_cancellation_slot(
            signal.slot(),
            [&client](boost::mqtt5::error_code ec, boost::mqtt5::reason_code rc, boost::mqtt5::puback_props props ) {
                std::cout << ec.message() << std::endl;
            }
        )
    );

    signal.emit(boost::asio::cancellation_type_t::terminal);

#### parallel_group/operator || and asynchronous functions in the mqtt_client

As a result of supporting per-operation cancellation, all the asynchronous functions with the ``mqtt_client`` can be used in `parallel_group` or with `awaitable operator ||`. This feature is especially beneficial for executing operations that require a timeout mechanism.

Below are two examples illustrating how to implement a timeout:

- timeout_with_parallel_group.cpp
- timeout_with_awaitable_operators.cpp

#### About protocol-level cancellation

In the context of ``mqtt_client``, the handling of cancellation signals varies across different asynchronous operations. Except for `mqtt_client::async_receive`, all other `async_xxx` operations respond to a terminal cancellation signal by invoking `mqtt_client::cancel`. These operations will halt the resending of certain packets for total and partial cancellation signals.

It is worth noting that cancelling an `async_xxx` operation during an ongoing protocol exchange is not implemented because of a design decision to prevent protocol breaches. For example, if `mqtt_client::async_publish` with QoS 2 is in the middle of a communication with the Broker [6] and an attempt to cancel it is made, it could lead to a protocol violation. For instance, if the operation is cancelled after a `PUBREC` packet has been received from the Broker but before sending the `PUBREL` packet, that would breach the MQTT protocol by failing to send a necessary packet and leave the connection with the Broker in an invalid state.

Therefore, the design of ``mqtt_client``'s cancellation strategy carefully avoids these pitfalls to ensure continuous protocol compliance.

[6] Publishing with QoS 2 involves sending a `PUBLISH` packet, receiving a `PUBREC` acknowledgement from the Broker, transmitting a `PUBREL` packet, and finally receiving a `PUBCOMP` packet.