Skip to content

Commit 07a4482

Browse files
authored
New issue from Dietmar Kühl: bulk vs. task_scheduler
1 parent cc8d0ca commit 07a4482

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

xml/issue4336.xml

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?xml version='1.0' encoding='utf-8' standalone='no'?>
2+
<!DOCTYPE issue SYSTEM "lwg-issue.dtd">
3+
4+
<issue num="4336" status="New">
5+
<title><code>bulk</code> vs. <code>task_scheduler</code></title>
6+
<section><sref ref="[exec.task.scheduler]"/></section>
7+
<submitter>Dietmar Kühl</submitter>
8+
<date>31 Aug 2025</date>
9+
<priority>99</priority>
10+
11+
<discussion>
12+
<p>
13+
Normally, the scheduler type used by an operation can be deduced
14+
when a sender is <code>connect</code>ed to a receiver from the
15+
receiver's environment. The body of a coroutine cannot know about
16+
the receiver the <code>task</code> sender gets <code>connect</code>ed
17+
to. The implication is that the type of the scheduler used by the
18+
coroutine needs to be known when the <code>task</code> is created.
19+
To still allow custom schedulers used when connecting, the type-erased
20+
scheduler <code>task_scheduler</code> is used. However, that leads
21+
to surprises when algorithms are customised for a scheduler as is,
22+
e.g., the case for <code>bulk</code> when used with a
23+
<code>parallel_scheduler</code>: if <code>bulk</code> is
24+
<code>co_awaited</code> within a coroutine using
25+
<code>task_scheduler</code> it will use the default implementation
26+
of <code>bulk</code> which sequentially executes the work, even if
27+
the <code>task_scheduler</code> was initialised with a
28+
<code>parallel_scheduler</code> (the exact invocation may actually
29+
be slightly different or need to use <code>bulk_chunked</code> or
30+
<code>bulk_unchunked</code> but that isn't the point being made):
31+
</p>
32+
33+
<pre>
34+
struct env {
35+
auto query(ex::get_scheduler_t) const noexcept { return ex::parallel_scheduler(); }
36+
};
37+
struct work {
38+
auto operator()(std::size_t s){ /*...*/ };
39+
};
40+
41+
ex::sync_wait(
42+
ex::write_env(ex::bulk(ex::just(), 16u, work{}),
43+
env{}
44+
));
45+
ex::sync_wait(ex::write_env(
46+
[]()->ex::task&lt;void, ex::env&lt;&gt;>&gt;{ co_await ex::bulk(ex::just(), 16u, work{}); }(),
47+
env{}
48+
));
49+
</pre>
50+
51+
<p>
52+
The two invocations should probably both execute the work in parallel
53+
but the coroutine version doesnt: it uses the <code>task_scheduler</code>
54+
which doesnt have a specialised version of <code>bulk</code> to
55+
potentially delegate in a type-erased form to the underlying
56+
scheduler. It is straight forward to move the <code>write_env</code>
57+
wrapper inside the coroutine which fixes the problem in this case
58+
but this need introduces the potential for a subtle performance
59+
bug. The problem is sadly not limited to a particular scheduler or
60+
a particular algorithm: any scheduler/algorithm combination which
61+
may get specialised can suffer from the specialised algorithm not
62+
being picked up.
63+
</p>
64+
65+
<p>
66+
There are a few ways this problem can be addressed (this list of
67+
options is almost certainly incomplete):
68+
</p>
69+
70+
<ul>
71+
<li>Accept the situation as is and advise users to be careful about
72+
customised algorithms like bulk when using
73+
<code>task_scheduler</code>.</li>
74+
<li>Extend the interface of <code>task_scheduler</code> to deal
75+
with a set of algorithms for which it provides a type-erased
76+
interface. The interface would likely be more constrained and it
77+
would use virtual dispatch at run-time. However, the set of covered
78+
algorithms would necessarily be limited in some form.</li>
79+
<li>To avoid the trap, make the use of known algorithms incompatible
80+
with the use of <code>task_scheduler</code>, i.e., customise these
81+
algorithms for <code>task_scheduler</code> such that a compile-time
82+
error is produced.</li>
83+
</ul>
84+
<p>
85+
A user who knows that the main purpose of a coroutine is to executed
86+
an algorithm customised for a certain scheduler can use <code>task&lt;T,
87+
E&gt;</code> with an environment <code>E</code> specifying exactly
88+
that scheduler type. However, this use may be nested within some
89+
sender being <code>co_awaited</code> and users need to be aware
90+
that the customisation wouldnt be picked up. Any approach I'm
91+
currently aware of will have the problem that customised versions
92+
of an algorithm are not used for algorithms we are currently unaware
93+
of.
94+
</p>
95+
</discussion>
96+
97+
<resolution>
98+
<p>
99+
</p>
100+
</resolution>
101+
102+
</issue>

0 commit comments

Comments
 (0)