Search code examples
c++memory-leaksc++20c++-coroutine

Is coroutine frame automatically destroyed and freed (i.e allocated frame is freed?) after co_return?


I know that if co_return (implicit or explicit) is reached co_await promise.final_suspend() is called. In final_suspend() one can definitely call ``handle.destroy() which to my understanding destroys all the objects in the coroutine frame (CF), lastly calls ~promise_type(), then it frees the allocated CF. See below Lewis Baker recommends this.

However:

  1. If final_suspend() does not call handle.destroy() or returns std::suspend_never or , will CF be destroyed properly right after co_await promise.final_suspend() completes?

  2. What happens if final_suspend() returns std::suspend_always?

  3. Is freeing CF somehow tied to the compiler deduced destructor of ~promise_type() ?

*Lewis Baker recommends this here:

Note that while it is allowed to have a coroutine not suspend at the final_suspend point, it is recommended that you structure your coroutines so that they do suspend at final_suspend where possible. This is because this forces you to call .destroy() on the coroutine from outside of the coroutine (typically from some RAII object destructor) and this makes it much easier for the compiler to determine when the scope of the lifetime of the coroutine-frame is nested inside the caller. This in turn makes it much more likely that the compiler can elide the memory allocation of the coroutine frame.

Also, reading the comments to this blog below[1] made me wonder if using std::suspend_never guarantees that there is no leakage. 1


Solution

    1. Yes. See [dcl.fct.def.coroutine]/11: "The coroutine state is destroyed when control flows off the end of the coroutine or the destroy member function ([coroutine.handle.resumption]) of a coroutine handle ([coroutine.handle]) that refers to the coroutine is invoked. [...]"

    2. The coroutine gets suspended at the final suspend point and control flow returns to the function that called the coroutine or most recently resumed it, just like if the coroutine were to be suspended at any other point. (Note that you can implement an await_suspend that actually specifies which coroutine to resume other than the current caller/resumer, but that's beyond the scope of what this question. std::suspend_always always resumes the current caller/resumer.)

    3. No. The invocation of the promise object's destructor is simply one of the steps that occurs as part of the destruction of the coroutine frame. This is because the promise object is a local variable in the desugared coroutine ([dcl.fct.def.coroutine]/5).

    Also, reading the comments to this blog below made me wonder if using std::suspend_never guarantees that there is no leakage

    It prevents one particular type of memory leak, i.e., the one where you forget to explicitly destroy the coroutine after it gets suspended at the final suspend point.