NVIDIA/stdexec

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)