What if when the destructor of a asio::strand is called, there are still some ready/unready handlers on this strand?

boost, boost-asio

Solution

From the documentation:

Handlers posted through the `strand` that have not yet been invoked will still be dispatched in a way that meets the guarantee of non-concurrency.

Basically, a `strand` can be thought of as being associated, but not owning, a queue of handlers. If a handler queue has no handlers currently posted into an `io_service`, then it will pop one handler from itself and post it into the associated `io_service`. This flow guarantees that handlers posted into the same `strand` will not be invoked concurrently.

All handlers posted into a `strand` are considered ready-to-run. Hence the lack of completion conditions on the `strand` API. When `strand`s are used with asynchronous operations, the initiating function is often provided a handler returned from `strand::wrap()`:

async_op(..., s.wrap(a));

When `async_op()` is completed, it will invoke the handler returned from `s.wrap(a)`. This handler will then post the user provided handler `a` into a `strand` that is associated with the same queue as the original `s` strand. As the lifetime of the `strand` object does not affect the lifetime of the handler queue, `async_op()` does not need to concern itself with the lifetime of `s`.

When the `io_service` associated with the `strand` is destroyed, the the uninvoked handlers are destroyed. The documentation states:

Uninvoked handler objects that were scheduled for deferred invocation on the `io_service`, or any associated `strand`, are destroyed.

To go into a bit more detail, a strand implementation is responsible for queuing handlers and guaranteeing that only one of its handlers have been posted to an `io_service` at a time, and a `strand` only provides a public API that delegates to the strand implementation. Each `strand` is associated with a strand implementation, and a strand implementation may be associated with many `strand`s. This relationship allows for a single strand implementation to be shared by many different `strand` objects. Hence, the documentation remarks:

The implementation makes no guarantee that handlers posted or dispatched through different strand objects will be invoked concurrently.

Boost.Asio controls the lifetime of these strand implementations, and lazily allocates them within a fixed size pool of strand implementations. The size of the pool can be configured by defining `BOOST_ASIO_STRAND_IMPLEMENTATIONS` to the desired number. As the lifetime of a `strand` object does not affect the lifetime of the strand implementation, asynchronous operations do not need to concern themselves with the lifetime of the `strand` in which the handler was wrapped.

Problem

What if when the destructor of a `asio::strand` is called, there are still some ready/unready handlers on this strand? Per the documentation: Handlers posted through the strand that have not yet been invoked will still be dispatched in a way that meets the guarantee of non-concurrency. Does this mean it will still invoke all handlers, including waiting the unready handlers to be ready, just as if it was not being destroyed (maybe except not accepting new handlers)? Or maybe I am conceptually wrong about "unready handler" --- an async operation won't post its handler on the strand before its completion? If the destructor of the target strand is called, will this async operation be aware of that?

Original source