run_loop::finish
should be noexcept
Section: 33.12.1 [exec.run.loop] Status: New Submitter: Eric Niebler Opened: 2025-02-13 Last modified: 2025-02-23
Priority: Not Prioritized
View all issues with New status.
Discussion:
Imported from cplusplus/sender-receiver #329.
run_loop::finish
puts the run_loop
into the finishing
state so that the next
time the work queue is empty, run_loop::run
will return instead of waiting for more work.
.finish()
on a run_loop
instance can potentially throw (finish()
is not marked noexcept
),
that is because one valid implementation involves acquiring a lock on a std::mutex
— a potentially throwing operation.
But failing to put the run_loop
into the finishing
state is problematic in the same way
that a failing destructor is problematic: shutdown and clean-up code depends on it succeeding.
Consider sync_wait
's use of run_loop
:
sync-wait-state<Sndr> state; auto op = connect(sndr, sync-wait-receiver<Sndr>{&state}); start(op); state.loop.run(); if (state.error) { rethrow_exception(std::move(state.error)); } return std::move(state.result);
It is the job of sync-wait-receiver
to put the run_loop
into the finishing
state
so that the invocation of state.loop.run()
will return. It does that in its completion functions, like so:
void set_stopped() && noexcept;Effects: Equivalent to
state->loop.finish()
.
Here we are not handling the fact that state->loop.finish()
is potentially throwing. Given that this
function is noexcept
, this will lead to the application getting terminated. Not good.
state.result
to be rethrown later, we still have a problem.
Since run_loop::finish()
threw, the run_loop
has not been placed into the finishing
state.
That means that state.loop.run()
will never return, and sync_wait
will hang forever.
Simply put, run_loop::finish()
has to be noexcept
. The implementation must find a way to put the run_loop
into the finishing
state. If it cannot, it should terminate. Throwing an exception and foisting the
problem on the caller — who has no recourse — is simply wrong.
Proposed resolution:
This wording is relative to N5001.
Modify 33.12.1.1 [exec.run.loop.general] as indicated:
namespace std::execution { class run_loop { // 33.12.1.2 [exec.run.loop.types], associated types class run-loop-scheduler; // exposition only class run-loop-sender; // exposition only struct run-loop-opstate-base { // exposition only virtual void execute() = 0; // exposition only run_loop* loop; // exposition only run-loop-opstate-base* next; // exposition only }; template<class Rcvr> using run-loop-opstate = unspecified; // exposition only // 33.12.1.4 [exec.run.loop.members], member functions run-loop-opstate-base* pop-front(); // exposition only void push-back(run-loop-opstate-base*); // exposition only public: // 33.12.1.3 [exec.run.loop.ctor], constructor and destructor run_loop() noexcept; run_loop(run_loop&&) = delete; ~run_loop(); // 33.12.1.4 [exec.run.loop.members], member functions run-loop-scheduler get_scheduler(); void run(); void finish() noexcept; }; }
Modify 33.12.1.4 [exec.run.loop.members] as indicated:
void finish() noexcept;-8- Preconditions:
-9- Effects: Changesstate
is eitherstarting
orrunning
.state
tofinishing
. -10- Synchronization:finish
synchronizes with thepop-front
operation that returnsnullptr
.