This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
run_loop::finish should be noexceptSection: 33.12.1 [exec.run.loop] Status: New Submitter: Eric Niebler Opened: 2025-02-13 Last modified: 2025-06-13
Priority: 2
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.
[2025-06-13; Reflector poll]
Set priority to 2 after reflector poll.
"If this can call terminate(), we should explicitly say so
(c.f. 32.7.4 [thread.condition.condvar] p11)"
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: Changesstateis eitherstartingorrunning.statetofinishing. -10- Synchronization:finishsynchronizes with thepop-frontoperation that returnsnullptr.