One of the use cases for the coroutines is suspending execution until certain conditions are satisfied (e.g. value is produced, flag/event is
triggered). In some situations, the expected result may be already available at the point of the co_await
/co_yield
expression, and the execution can be resumed immediately.
The C++ standard provides an efficient method to suspend the coroutine conditionally. The result of await_ready
is used to determine
whether a coroutine should be suspended. Returning true
from this function avoids the cost of the coroutine suspension if it is not
needed (e.g., the result is already available). Furthermore, the bool
-returning version of await_suspend
allows immediate
resumption of the current coroutine in the case when false
is returned (returning true
indicates that the coroutine should
remain suspended). Compared to symmetric transfer, this method provides better optimization opportunities, as the continuation code is known to the
compiler - i.e., it is the code of the current coroutine, while in symmetric transfer the handle could point to an arbitrary coroutine.
This rule raises an issue on await_suspend
that can benefit from using conditional suspension.
Noncompliant code example
struct WaitForAwaiter {
Event& event;
/* .... */
std::coroutine_handle<> await_suspend(std::coroutine_handle<> current) { // Noncompliant
bool callback_registered = event.register_callback(current);
if (!callback_registered) {
return current;
} else {
return std::noop_coroutine();
}
}
};
struct ReadBytesAwaiter {
Socket& socket;
std::size_t count;
std::span<std::byte> buffer;
std::error_code error;
/* .... */
void await_suspend(std::coroutine_handle<> current) { // Noncompliant
auto callback = [&error_store=error, current](std::error_code ec) {
error_store = ec;
current.resume();
};
auto ec = socket.async_read(buffer, count, callback);
if (ec) {
error = ec;
current.resume();
}
}
};
Compliant solution
struct WaitForAwaiter {
Event& event;
/* .... */
bool await_ready() const {
return event.is_already_triggered();
}
bool await_suspend(std::coroutine_handle<> current) {
bool callback_registered = event.register_callback(current);
return callback_registered;
}
};
struct ReadBytesAwaiter {
Socket& socket;
std::size_t count;
std::span<std::byte> buffer;
std::error_code error;
/* .... */
bool await_ready() const {
return false; // no way to query before suspension
}
bool await_suspend(std::coroutine_handle<> current) {
auto callback = [&error_store=error, current](std::error_code ec) {
error_store = ec;
current.resume();
};
auto ec = socket.async_read(buffer, count, callback);
if (ec) {
error = ec;
return false;
}
return true;
}
};