Tracking issue for upstream gcc bug PR 124584 - [C++20 Coroutines] Destructor of tuple-protocol structured binding from co_await skipped at -O1+ (regression in 15) - as it affects seastar.
The bug, in one line: gcc 15+ miscompiles tuple-protocol structured bindings in coroutine loops. When auto [...] = co_await ...; sits in a while / for body inside a coroutine, gcc's lifetime tracking of the hidden tuple that backs the binding is broken across loop iterations, so the destructor either gets skipped (leaks) or runs on memory that was never tuple-constructed (UBSan / ASan aborts).
Credit: Avi root-caused this class of failure to the gcc bug while investigating the pollable_fd_state leak in the cross-shard accept path (see #3394). Several other seastar failures are downstream of the same compiler bug.
Symptoms seen in seastar
- Destructor skipped between loop iterations - the structured-binding-captured object (a
pollable_fd wrapper, an intrusive_ptr, etc.) is never destroyed at end-of-iteration, surfacing as a memory leak. Seen on the accept loops.
- Destructor runs on uninitialized memory - at end-of-iteration the hidden tuple's destructor fires on stack that was never tuple-constructed, hitting ASan/UBSan. In sanitize mode this aborts both rpc client and server loops at startup:
/usr/include/c++/15/optional:337:12: runtime error:
load of value 190, which is not a valid value for type 'bool'
Reproduced with gcc-15.2 on ubuntu 26.04, sanitize+C++23, detect_stack_use_after_return=1. Clang appears unaffected.
Affected sites in seastar
All four are structured bindings decomposed from a co_await inside a coroutine loop, all introduced ~Aug 2025 when these loops were coroutinized:
Replacing either rpc structured binding with a named local variable + std::get<> makes the UBSan abort disappear, confirming the structured-binding hidden-tuple lifetime is the cause.
Related work already in flight in this repo
Workaround in this repo until gcc fixes 124584
.github/workflows/gen-matrix.py excludes g++ + debug and g++ + sanitize from the CI matrix. To be reverted once a fixed gcc is in the CI containers:
(Add further items here if other workarounds accumulate.)
Tracking issue for upstream gcc bug PR 124584 - [C++20 Coroutines] Destructor of tuple-protocol structured binding from co_await skipped at -O1+ (regression in 15) - as it affects seastar.
The bug, in one line: gcc 15+ miscompiles tuple-protocol structured bindings in coroutine loops. When
auto [...] = co_await ...;sits in awhile/forbody inside a coroutine, gcc's lifetime tracking of the hidden tuple that backs the binding is broken across loop iterations, so the destructor either gets skipped (leaks) or runs on memory that was never tuple-constructed (UBSan / ASan aborts).Credit: Avi root-caused this class of failure to the gcc bug while investigating the
pollable_fd_stateleak in the cross-shard accept path (see #3394). Several other seastar failures are downstream of the same compiler bug.Symptoms seen in seastar
pollable_fdwrapper, anintrusive_ptr, etc.) is never destroyed at end-of-iteration, surfacing as a memory leak. Seen on the accept loops.Reproduced with gcc-15.2 on ubuntu 26.04, sanitize+C++23,
detect_stack_use_after_return=1. Clang appears unaffected.Affected sites in seastar
All four are structured bindings decomposed from a
co_awaitinside a coroutine loop, all introduced ~Aug 2025 when these loops were coroutinized:auto [fd, sa] = co_await _lfd.accept();inposix_server_socket_impl::accept- worked around in net: posix: fix pollable_fd_state leak on cross-shard connection forwarding #3394 by explicitly closingfdafter extracting the rawfile_descacceptcoroutine - addressed in net: posix: close abandoned proxy protocol connection early #3393 by closing abandoned connections eagerly rather than relying on coroutine-frame destructionauto&& [msg_id, ht, data] = co_await read_response_frame_compressed(...)inclient::loop(587096a)auto [expire, type, msg_id, data] = co_await read_request_frame_compressed(...)inserver::connection::process()(ee2007f)Replacing either rpc structured binding with a named local variable +
std::get<>makes the UBSan abort disappear, confirming the structured-binding hidden-tuple lifetime is the cause.Related work already in flight in this repo
g++ + sanitizein CI and the one that surfaced the rpc UBSan symptom above.Workaround in this repo until gcc fixes 124584
.github/workflows/gen-matrix.pyexcludesg++ + debugandg++ + sanitizefrom the CI matrix. To be reverted once a fixed gcc is in the CI containers:g++ + debugandg++ + sanitizerows fromEXCLUDED_PAIRSin.github/workflows/gen-matrix.py.(Add further items here if other workarounds accumulate.)