fix(exec): task should report custom error_types in completion_signat… - #2247
Conversation
|
Hey @ericniebler, this PR fixes #2222 (task completion signatures ignoring custom error_types). It's a 9-line addition to The fix is verified locally — 974/974 tests pass. Would appreciate your review when you get a chance! 🙏 |
|
Merely providing stdexec/include/stdexec/__detail/__task.hpp Lines 551 to 563 in 4754c76 the receiver would still be completed with set_error(receiver, exception_ptr).
|
36b55f6 to
0e26897
Compare
|
Thanks — you're right that get_completion_signatures alone was insufficient, and I verified it: with only the signature change, the connected path still delivered set_error(rcvr, exception_ptr) through __connect_awaitable's catch-all, and the #2222 repro actually failed to compile. The updated commit addresses this by adding a constrained task::connect member alongside the get_completion_signatures member. Its operation state drives the task's coroutine to completion (mirroring the existing __awaiter machinery, including stop-callback registration and symmetric transfer to a noop continuation) and completes the receiver directly from the task's typed error variant — so a task whose environment declares error_types = {std::error_code} now delivers set_error(rcvr, error_code) rather than exception_ptr, matching what get_completion_signatures advertises. Added regression tests for the #2222 repro chain (task | upon_error | into_variant now compiles and yields tuple<error_code>) and for direct receiver connection (value / error_code / stopped delivery). All test.stdexec + test.exec + test.scratch tests pass locally; the co_await path between coroutines remains exception-based by design. The C++ module build couldn't be checked on this machine (no clang-scan-deps in the local toolchain) and will be covered by CI. |
ericniebler
left a comment
There was a problem hiding this comment.
nice! i've made a few small tweaks.
|
/ok to test f6caf58 |
f6caf58 to
935b668
Compare
|
/ok to test 935b668 |
|
@alwaysprince05 @ericniebler The MSVC coroutine implementation prior to version 14.50 has a bug: if A workaround is to avoid using symmetric transfer in }
static constexpr auto await_suspend(__std::coroutine_handle<__promise> __coro) noexcept //
- -> __std::coroutine_handle<>
{
- return __coro.promise().__state_->__completed();
+ __std::coroutine_handle<> const continuation = __coro.promise().__state_->__completed();
+# ifdef STDEXEC_MSVC_CORO_DESTROY_BUG_WORKAROUND
+ /// MSVC bug workaround: see https://developercommunity.visualstudio.com/t/Incorrect-code-generation-for-symmetric-/1659260
+ continuation.resume();
+# else
+ return continuation;
+# endif
}
static constexpr void await_resume() noexcept {}
However, this may lead to more stack overflows, causing certain test cases in |
Fixes NVIDIA#2222 task<T, TaskEnv> ignored custom error_types declared in TaskEnv: its completion signatures always reported set_error_t(std::exception_ptr) via the __co_await_completions_t fallback, and connecting a task delivered errors to the receiver as exception_ptr regardless of the declared types. Add constrained get_completion_signatures and connect members to task so that, when connected to a compatible environment, its completions reflect TaskEnv's error_types and errors are delivered to the receiver with their declared types. Add regression tests for the issue's repro chain and for direct receiver connection. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
MSVC prior to 14.50 stores the coroutine handle returned from await_suspend in the suspended coroutine's frame, so when a task connected to a receiver completes and __opstate::__completed destroys the coroutine frame before await_suspend returns, symmetric transfer resumes a use-after-free. When STDEXEC_MSVC_CORO_DESTROY_BUG_WORKAROUND is defined (MSVC < 14.50), resume the continuation directly instead of returning it, and disable the deep inline task-chaining stack-overflow test under that macro since a plain nested resume grows the stack. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
88d5f20 to
43355fb
Compare
|
@ericniebler I've rebased onto main and applied the MSVC workaround @Cra3z suggested (commit 43355fb): __completed_awaiter::await_suspend now resumes the continuation directly when STDEXEC_MSVC_CORO_DESTROY_BUG_WORKAROUND is defined, and the "test task can await a just_int sender without stack overflow" case is disabled under that macro. Full local suite passes (615 test cases). Could you /ok to test 43355fb? |
|
/ok to test 0a7f494 |
|
thank you, @alwaysprince05 and @Cra3z! |
Fixes #2222
task<T, TaskEnv>ignored customerror_typesdeclared inTaskEnv: its completion signatures always reportedset_error_t(std::exception_ptr)(via the__co_await_completions_tfallback), and connecting a task delivered errors to the receiver asexception_ptrregardless of the declared types.This PR adds two constrained members to
task, both gated on__has_compatible_environment_withso that a task remains unusable as a sender in arbitrary environments:get_completion_signatures— reports the task's actual completion signatures, including theerror_typesfromTaskEnv.connect— returns an operation state that runs the task's coroutine and completes the receiver directly from the task's typed error variant, so errors such asstd::error_codeare delivered asset_error(rcvr, error_code)instead of being thrown as exceptions and caught asstd::exception_ptr. Value, reference,void, stopped, and cancellation completions are handled by the same operation state.Added regression tests: the #2222 repro chain (
task | upon_error | into_variant) now compiles and yieldstuple<error_code>, plus direct-receiver checks for value /error_code/ stopped delivery.All
test.stdexec,test.exec, andtest.scratchtests pass locally. Theco_awaitpath between coroutines remains exception-based by design. The C++ module build could not be checked on this machine (noclang-scan-depsin the local toolchain) and will be covered by CI.