async_scope set_error calls std::terminate and exception is lost
williamspatrick opened this issue · 3 comments
If you spawn an exec::task
to the async_scope, and that task throws an exception you end up with a fairly non-useful log due to std::terminate:
terminate called without an active exception
I see in the code applicable code this BUGBUG:
// BUGBUG NOT TO SPEC spawn shouldn't accept senders that can fail.
template <same_as<set_error_t> _Tag>
[[noreturn]] friend void
tag_invoke(_Tag, __t&&, const std::exception_ptr&) noexcept {
std::terminate();
}
I'm not sure what the intended solution would be here. It sounds like this shouldn't have even compiled?
Would it be reasonable to change the code to something like this?
template <same_as<set_error_t> _Tag>
[[noreturn]] friend void
tag_invoke(_Tag, __t&&, const std::exception_ptr& e) noexcept {
try { std::rethrow_exception(e); } catch (...) { std::terminate(); }
}
I believe the intention is for the code to not compile: if you want to put a sender with a non-empty set if set_error completion signatures into an async_scope you'll need to adapt it to deal with the error. Having a default error handler likely just introduces unhandled errors.
Right, I think the best solution is to force users to deal with errors themselves. Although I kind of like the idea to terminate with the exception:
template <same_as<set_error_t> _Tag>
[[noreturn]] friend void
tag_invoke(_Tag, __t&&, std::exception_ptr __eptr) noexcept {
std::rethrow_exception(std::move(__eptr));
}
The exception hits the noexcept
and the runtime will report the error while shutting down the process. That's certainly an improvement over the status quo.
After #1220, this program:
exec::async_scope scope;
auto work = just() | then([] {throw std::runtime_error("oops");});
scope.spawn(std::move(work));
terminates with the following (w/ clang 17) for me:
terminate called after throwing an instance of 'std::runtime_error'
what(): oops
Aborted (core dumped)