Oliv
Oliv

Reputation: 18081

Can a coroutine be destroyed during evaluation of an inner await expression?

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:

  1. coroutine A is suspended because await-ready is false;
  2. before giving control to coroutine A caller, a coroutine B is resumed;
  3. once B is also suspended control flows returns to coroutine A caller.

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

Answers (1)

Fedor
Fedor

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... If destroy 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

Related Questions