Created on 2025-02-13.00:00:00 last changed 6 months ago
Proposed resolution:
This wording is relative to N5001.
Modify [exec.run.loop.general] as indicated:
namespace std::execution {
class run_loop {
// [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
// [exec.run.loop.members], member functions
run-loop-opstate-base* pop-front(); // exposition only
void push-back(run-loop-opstate-base*); // exposition only
public:
// [exec.run.loop.ctor], constructor and destructor
run_loop() noexcept;
run_loop(run_loop&&) = delete;
~run_loop();
// [exec.run.loop.members], member functions
run-loop-scheduler get_scheduler();
void run();
void finish() noexcept;
};
}
Modify [exec.run.loop.members] as indicated:
void finish() noexcept;-8- Preconditions: state is either starting or running.
-9- Effects: Changes state to finishing. -10- Synchronization: `finish` synchronizes with the pop-front operation that returns nullptr.
[ 2025-06-13; Reflector poll ]
Set priority to 2 after reflector poll.
"If this can call `terminate()`, we should explicitly say so (c.f. [thread.condition.condvar] p11)"
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.
Calling `.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.
But even if we handle the exception and save it into `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.| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2025-06-13 10:51:10 | admin | set | messages: + msg14811 |
| 2025-02-23 16:13:33 | admin | set | messages: + msg14658 |
| 2025-02-13 00:00:00 | admin | create | |