Skip to content

Commit e338921

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

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed

xml/issue4215.xml

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

0 commit comments

Comments
 (0)