SIGSEGV with `co_await (stdexec::get_scheduler() | ...);`
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 48/100
Direzione di ricerca
Esegui il riproduttore Godbolt e segui il percorso await attraverso include/stdexec/__detail/__as_awaitable.hpp, in particolare __sender_awaiter::await_suspend e __get_continuation. Confronta la regressione segnalata con il workaround e con l'esempio dell'ordinamento previsto. È completato quando sync_wait non restituisce prematuramente, la coroutine task non viene distrutta prima della ripresa e il riproduttore non raggiunge più SIGSEGV.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Summary
Using co_await (ex::get_scheduler() | ex::let_value([](auto sch) { return ex::schedule(sch); })); in a task leads to a premature exit of sync_wait and a subsequent use-after-free of the coroutine.
This is a regression introduced with 482c2605918d3aadbd472960e207126a21809140 #2078
Tested with Clang and GCC on x86_64
Workaround
auto sch = co_await ex::get_scheduler();
co_await (ex::just(sch) | ex::let_value([](auto sch) { return ex::schedule(sch); }));
Reproducer
https://godbolt.org/z/WE9fs5WPv
#include <coroutine>
#include <stdexec/execution.hpp>
namespace ex = STDEXEC;
void foo()
{
int order[2] = {0, 0};
size_t idx = 0;
ex::sync_wait(
[&]() -> ex::task<void> {
order[idx++] = 1;
co_await (ex::get_scheduler() | ex::let_value([](auto sch) { return ex::schedule(sch); }));
order[idx++] = 2;
}());
}
void foo_workaround()
{
int order[2] = {0, 0};
size_t idx = 0;
ex::sync_wait(
[&]() -> ex::task<void> {
order[idx++] = 1;
auto sch = co_await ex::get_scheduler();
co_await (ex::just(sch) | ex::let_value([](auto sch) { return ex::schedule(sch); }));
order[idx++] = 2;
}());
}
int main()
{
foo(); // crashes with SIGSEGV probaly use after free
foo_workaround();
}
/* my usage:
namespace Tools
{
// Give control back to the scheduler.
struct Yield : ex::sender_adaptor_closure<Yield>
{
auto operator()() const
{
return ex::get_scheduler() | ex::let_value([](auto sch) { return ex::schedule(sch); });
}
template<class Sndr>
auto operator()(Sndr sndr) const;
};
inline constexpr Yield yield{};
template<class Sndr>
auto Yield::operator()(Sndr sndr) const
{
return std::move(sndr) | ex::let_value([](auto... value) {
return ex::when_all(ex::just(std::move(value)...), Tools::yield());
});
}
}
void foo_intended_use()
{
int order[4] = {0, 0, 0, 0};
size_t idx = 0;
ex::sync_wait(
ex::when_all(
[&]() -> ex::task<void> {
order[idx++] = 1;
auto sch = co_await ex::get_scheduler();
co_await Tools::yield();
order[idx++] = 2;
}(),
[&]() -> ex::task<void> {
order[idx++] = 3;
auto sch = co_await ex::get_scheduler();
co_await Tools::yield();
order[idx++] = 4;
}()));
assert(order[0] == 1);
assert(order[1] == 3);
assert(order[2] == 2);
assert(order[3] == 4);
}
*/
Analysis
We analyzed this on 307b83c5689ea7c2e5b31561cdc428697705333e and fee4d651494014610a277540f209cae56011e47f with our intended code, but without the when_all.
We debugged it and it seems, that after the co_await Tools::yield() the sync_wait returns, because __coroutine_unhandled_stopped is called. This leads then to the destruction of the coroutine handle and then a use-after-free once the run_loop wants to execute the rest of the task:
include/stdexec/__detail/__as_awaitable.hpp
// When the sender is known to complete inline, we can connect and start the operation
// in await_suspend.
template <class _Promise, sender_in<env_of_t<_Promise&>> _Sender>
requires __completes_inline<_Sender, env_of_t<_Promise&>>
struct __sender_awaiter<_Promise, _Sender>
: __sender_awaiter_base<__value_t<_Sender, _Promise>, true>
{
...
auto await_suspend([[maybe_unused]] __std::coroutine_handle<> __continuation)
-> __std::coroutine_handle<>
{
STDEXEC_ASSERT(this->__continuation_.handle() == __continuation);
{
auto __opstate = STDEXEC::connect(static_cast<_Sender&&>(__sndr_), __receiver_t(*this));
// The following call to start will complete synchronously, writing its result
// into the __result_ variant.
STDEXEC::start(__opstate); // yields continuation was added here
}
return this->__get_continuation(); // sync_wait will return after this.
}
...
};
...
[[nodiscard]]
constexpr auto __get_continuation() const noexcept -> __std::coroutine_handle<>
{
// If the operation was stopped (__result_ is valueless), we should use the
// unhandled_stopped() continuation. Otherwise, should resume the __continuation_
// as normal.
if (__result_.__is_valueless()) // after Tools::yield() __result_ is considered valueless because set_value was not called
{
return STDEXEC::__coroutine_unhandled_stopped(__continuation_); // sync_wait returns because of this
}
else
{
return __continuation_.handle();
}
}
- Lingua principale
- C++
- Stelle
- 2.4k
- Fork
- 270
- Merge medio
- 2g 17h
- PR unite (30g)
- 37
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di NVIDIA/stdexec
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 68/100
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 66/100
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 74/100
Tutte le issue di NVIDIA/stdexec
Issue simili
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 90/100
AXERA-TECH/ax-llm#77 ·
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 90/100
games-on-whales/wolf#509 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
-
bug-unconfirmed
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
NVIDIA/cuda-samples#453 ·