PrevUpHomeNext

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.

async_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<async_mqtt5::qos_e::at_least_once>(
	"<topic>", "Hello world!",
	async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
	boost::asio::bind_cancellation_slot(
		signal.slot(),
		[&client](async_mqtt5::error_code ec, async_mqtt5::reason_code rc, async_mqtt5::puback_props props ) {
			std::cout << ec.message() << std::endl;
		}
	)
);

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

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:

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.


PrevUpHomeNext