Skip to content

Commit 20b950b

Browse files
committed
New issue from Eric: "run_loop::finish should be noexcept"
1 parent 4fbe46a commit 20b950b

File tree

1 file changed

+127
-0
lines changed

1 file changed

+127
-0
lines changed

xml/issue4215.xml

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?xml version='1.0' encoding='utf-8' standalone='no'?>
2+
<!DOCTYPE issue SYSTEM "lwg-issue.dtd">
3+
4+
<issue num="4215" status="New">
5+
<title>`run_loop::finish` should be `noexcept`</title>
6+
<section>
7+
<sref ref="[exec.run.loop]"/>
8+
</section>
9+
<submitter>Eric Niebler</submitter>
10+
<date>13 Feb 2025</date>
11+
<priority>99</priority>
12+
13+
<discussion>
14+
<p>
15+
`run_loop::finish` puts the `run_loop` into the <tt><i>finishing</i></tt> state so that the next
16+
time the work queue is empty, `run_loop::run` will return instead of waiting for more work.
17+
<p/>
18+
Calling `.finish()` on a `run_loop` instance can potentially throw (`finish()` is not marked `noexcept`),
19+
that is because one valid implementation involves acquiring a lock on a `std::mutex` &mdash; a potentially throwing operation.
20+
<p/>
21+
But failing to put the `run_loop` into the <tt><i>finishing</i></tt> state is problematic in the same way
22+
that a failing destructor is problematic: shutdown and clean-up code depends on it succeeding.
23+
<p/>
24+
Consider `sync_wait`'s use of `run_loop`:
25+
</p>
26+
<blockquote><pre>
27+
<i>sync-wait-state</i>&lt;Sndr&gt; state;
28+
auto op = connect(sndr, <i>sync-wait-receiver</i>&lt;Sndr&gt;{&amp;state});
29+
start(op);
30+
31+
state.loop.run();
32+
if (state.error) {
33+
rethrow_exception(std::move(state.error));
34+
}
35+
return std::move(state.result);
36+
</pre></blockquote>
37+
<p>
38+
It is the job of <tt><i>sync-wait-receiver</i></tt> to put the `run_loop` into the <tt><i>finishing</i></tt> state
39+
so that the invocation of `state.loop.run()` will return. It does that in its completion functions, like so:
40+
</p>
41+
<blockquote><pre>
42+
void set_stopped() &amp;&amp; noexcept;
43+
</pre>
44+
<blockquote>
45+
<p>
46+
<i>Effects</i>: Equivalent to <tt>state-&gt;loop.finish()</tt>.
47+
</p>
48+
</blockquote>
49+
</blockquote>
50+
<p>
51+
Here we are not handling the fact that <tt>state-&gt;loop.finish()</tt> is potentially throwing. Given that this
52+
function is `noexcept`, this will lead to the application getting terminated. Not good.
53+
<p/>
54+
But even if we handle the exception and save it into `state.result` to be rethrown later, we still have a problem.
55+
Since `run_loop::finish()` threw, the `run_loop` has not been placed into the <tt><i>finishing</i></tt> state.
56+
That means that `state.loop.run()` will never return, and `sync_wait` will hang forever.
57+
<p/>
58+
Simply put, `run_loop::finish()` has to be `noexcept`. The implementation must find a way to put the `run_loop`
59+
into the <tt><i>finishing</i></tt> state. If it cannot, it should terminate. Throwing an exception and foisting the
60+
problem on the caller &mdash; who has no recourse &mdash; is simply wrong.
61+
</p>
62+
</discussion>
63+
64+
<resolution>
65+
<p>
66+
This wording is relative to <paper num="N5001"/>.
67+
</p>
68+
69+
<ol>
70+
<li><p>Modify <sref ref="[exec.run.loop.general]"/> as indicated:</p>
71+
72+
<blockquote><pre>
73+
namespace std::execution {
74+
class run_loop {
75+
<i>// <sref ref="[exec.run.loop.types]"/>, associated types</i>
76+
class <i>run-loop-scheduler</i>; <i>// exposition only</i>
77+
class <i>run-loop-sender</i>; <i>// exposition only</i>
78+
struct <i>run-loop-opstate-base</i> { <i>// exposition only</i>
79+
virtual void <i>execute</i>() = 0; <i>// exposition only</i>
80+
run_loop* <i>loop</i>; <i>// exposition only</i>
81+
run-loop-opstate-base* <i>next</i>; <i>// exposition only</i>
82+
};
83+
template&lt;class Rcvr&gt;
84+
using <i>run-loop-opstate</i> = <i>unspecified</i>; <i>// exposition only</i>
85+
86+
<i>// <sref ref="[exec.run.loop.members]"/>, member functions</i>
87+
<i>run-loop-opstate-base</i>* <i>pop-front</i>(); <i>// exposition only</i>
88+
void <i>push-back</i>(<i>run-loop-opstate-base</i>*); <i>// exposition only</i>
89+
90+
public:
91+
<i>// <sref ref="[exec.run.loop.ctor]"/>, constructor and destructor</i>
92+
run_loop() noexcept;
93+
run_loop(run_loop&amp;&amp;) = delete;
94+
~run_loop();
95+
96+
<i>// <sref ref="[exec.run.loop.members]"/>, member functions</i>
97+
<i>run-loop-scheduler</i> get_scheduler();
98+
void run();
99+
void finish() <ins>noexcept</ins>;
100+
};
101+
}
102+
</pre></blockquote>
103+
</li>
104+
105+
<li><p>Modify <sref ref="[exec.run.loop.members]"/> as indicated:</p>
106+
107+
<blockquote>
108+
<pre>
109+
void finish() <ins>noexcept</ins>;
110+
</pre>
111+
<blockquote>
112+
<p>
113+
-8- <i>Preconditions</i>: <tt><i>state</i></tt> is either <tt><i>starting</i></tt> or <tt><i>running</i></tt>.
114+
<p/>
115+
-9- <i>Effects</i>: Changes <tt><i>state</i></tt> to <tt><i>finishing</i></tt>.
116+
<p/>
117+
-10- <i>Synchronization</i>: `finish` synchronizes with the <tt><i>pop-front</i></tt> operation that returns <tt>nullptr</tt>.
118+
</p>
119+
</blockquote>
120+
</blockquote>
121+
</li>
122+
123+
</ol>
124+
125+
</resolution>
126+
127+
</issue>

0 commit comments

Comments
 (0)