Reputation: 18081
According to the standard, a coroutine shall be destroyed only when it is suspended [dcl.fct.def.coroutine]
If destroy is called for a coroutine that is not suspended, the program has undefined behavior.
During evaluation of a await expression inside a coroutine A suppose this sequence of event corresponding to [expr.await]/5.1:
May coroutine A be destroyed after coroutine B is resumed but before it is suspended?
Exemple code:
#include <coroutine>
using namespace std;
struct task
{
struct promise_type;
using handle_type = coroutine_handle<promise_type>;
struct promise_type
{
handle_type resumer = nullptr;
auto
get_return_object(){
return task{handle_type::from_promise(*this)};
}
auto
initial_suspend(){
return suspend_always {};
}
auto
unhandled_exception(){}
auto
final_suspend(){
return suspend_always{};
}
void
return_void() {}
};
handle_type handle;
void await_resume(){
handle.resume();
}
auto
await_suspend(handle_type h){
handle.promise().resumer = h;
return handle;
}
auto
await_ready(){
return false;
}
};
int main(){
task coroutine_B = [&coroutine_B]() ->task
{
coroutine_B.handle.promise().resumer.destroy();
co_return;
}();//coroutine B supended at initial suspend
task coroutine_A = [&coroutine_B]() ->task
{
co_await coroutine_B;//set coroutine_B resumer to coroutine_A handle
//then resume coroutine_B.
}();//coroutine A supended at initial suspend.
coroutine_A.handle.resume();//execute co_await coroutine_B;
//is this UB?
}
As can be seen here, the code compiles and seems to run without any troubles.
On the other hand this apparently equivalent version here crashes.
Upvotes: 5
Views: 199
Reputation: 21307
The standard in dcl.fct.def.coroutine#11 says
The coroutine state is destroyed when ... the
destroy
member function of a coroutine handle that refers to the coroutine is invoked... Ifdestroy
is called for a coroutine that is not suspended, the program has undefined behavior.
In your example destroy
is called from coroutine_B
for the coroutine handle of coroutine_A
, which is suspended because of co_await
. So the destroy
member function is called for a suspended coroutine, so there is no undefined behavior in it.
And indeed Clang, GCC, MSVC all compile and execute the code fine: https://gcc.godbolt.org/z/zxePvsvx1
On the other hand this apparently equivalent version here crashes.
Here the problem is that coroutine_B.resumer
contains nullptr
address in GCC so coroutine_B.resumer.destroy()
is undefined behavior. Demo: https://gcc.godbolt.org/z/T8aa45ez1
Upvotes: 1