I created the following simple coroutine example:
#include <cassert> // std::assert
#include <coroutine> // std::suspend_never, suspend_always
#include <cstdio> // printf
class test {
public:
class promise_type {
public:
explicit promise_type() {}
void get_return_object() const {}
std::suspend_never initial_suspend() const { return {}; }
void unhandled_exception() const { assert(false); }
void return_void() const {}
std::suspend_always final_suspend() const noexcept { return {}; }
};
};
test process() {
co_return;
}
int main() {
process();
}
Compiling this with GCC (with -fcoroutines
) or Clang and then running the executable with Valgrind produces the following output:
==135311== Memcheck, a memory error detector
==135311== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==135311== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info
==135311== Command: test
==135311==
==135311==
==135311== HEAP SUMMARY:
==135311== in use at exit: 40 bytes in 1 blocks
==135311== total heap usage: 2 allocs, 1 frees, 72,744 bytes allocated
==135311==
==135311== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==135311== at 0x4842003: operator new(unsigned long) (vg_replace_malloc.c:434)
==135311== by 0x1091BE: process() (test.cpp:25)
==135311== by 0x109466: main (test.cpp:28)
==135311==
==135311== LEAK SUMMARY:
==135311== definitely lost: 40 bytes in 1 blocks
==135311== indirectly lost: 0 bytes in 0 blocks
==135311== possibly lost: 0 bytes in 0 blocks
==135311== still reachable: 0 bytes in 0 blocks
==135311== suppressed: 0 bytes in 0 blocks
==135311==
==135311== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
This memory leak is not reported when returning std::suspend_never
from promise_type::final_suspend
. However, according to https://en.cppreference.com/w/cpp/language/coroutines and many other sources, resuming a coroutine after promise_type::final_suspend
has been called is undefined behavior. Is this memory leak expected or can I do something else to prevent it?
It is the responsibility of your coroutine machinery to actually clean up the coroutine when your machinery is finished with it. In typical cases, test
would store a coroutine_handle
which it would dutifully destroy
in its destructor. In other cases, handles are given out to other code which itself is responsible for destroying them.
In your case, you would need the promise to destroy the handle somewhere in final_suspend
.
Put simply, ownership over the coroutine_handle
is your code's responsibility. Coroutines cease to exist only when you make them.