Skip to content

Commit d5a0406

Browse files
committed
Make run() post on both trips at cross-executor boundaries
run_awaitable_ex::await_suspend and the return trampoline's final_suspend both called dispatch(). On executors with a thread-check fast path (strand, blocking_executor) dispatch can fire an inline symmetric transfer, which does not enqueue the target and does not give the caller's executor a fresh tick on the return. run(ex)(task) then fails to actually run task on ex and leaves the caller resuming on the wrong frame. Switch both trips to post + std::noop_coroutine(). Also rename dispatch_trampoline to boundary_trampoline; the type's purpose is bridging the executor boundary, and the old name named a mechanism that no longer applies. The five previously-failing tests in boost.capy.run.priority, boost.capy.ex.run, and boost.capy.strand now pass; full suite green at 76743 assertions.
1 parent efe3909 commit d5a0406

3 files changed

Lines changed: 22 additions & 19 deletions

File tree

doc/continuation-rationale.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ their own `continuation`:
432432
433433
- `when_all_core::continuation_` (parent handle for combinator)
434434
- `when_any_core::continuation_` (same)
435-
- `dispatch_trampoline::parent_` (cross-executor trampoline)
435+
- `boundary_trampoline::parent_` (cross-executor trampoline)
436436
- `run_awaitable_ex::task_cont_` (initial task dispatch)
437437
- `run_async_trampoline::task_cont_` (same)
438438

include/boost/capy/detail/await_suspend_helper.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ namespace detail {
3535
The return value is written to the now-destroyed frame.
3636
3737
@li `await_suspend` hands the continuation to another thread
38-
via `executor::dispatch()`, which may resume the parent.
39-
The parent can destroy this frame before the runtime reads
40-
`__$ReturnUdt$` (e.g. `dispatch_trampoline` final_suspend).
38+
via an executor handoff (e.g. `post()` or `dispatch()`),
39+
which may resume the parent. The parent can destroy this
40+
frame before the runtime reads `__$ReturnUdt$` (e.g.
41+
`boundary_trampoline` final_suspend).
4142
4243
On MSVC this function calls `h.resume()` on the current stack
4344
and returns `void`, causing unconditional suspension. The

include/boost/capy/ex/run.hpp

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,17 @@ namespace boost::capy::detail {
7070
7171
The trampoline never touches the task's result.
7272
*/
73-
struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE dispatch_trampoline
73+
struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE boundary_trampoline
7474
{
7575
struct promise_type
7676
: frame_alloc_mixin
7777
{
7878
executor_ref caller_ex_;
7979
continuation parent_;
8080

81-
dispatch_trampoline get_return_object() noexcept
81+
boundary_trampoline get_return_object() noexcept
8282
{
83-
return dispatch_trampoline{
83+
return boundary_trampoline{
8484
std::coroutine_handle<promise_type>::from_promise(*this)};
8585
}
8686

@@ -96,8 +96,9 @@ struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE dispatch_trampoline
9696
auto await_suspend(
9797
std::coroutine_handle<>) noexcept
9898
{
99+
p_->caller_ex_.post(p_->parent_);
99100
return detail::symmetric_transfer(
100-
p_->caller_ex_.dispatch(p_->parent_));
101+
std::noop_coroutine());
101102
}
102103

103104
void await_resume() const noexcept {}
@@ -111,20 +112,20 @@ struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE dispatch_trampoline
111112

112113
std::coroutine_handle<promise_type> h_{nullptr};
113114

114-
dispatch_trampoline() noexcept = default;
115+
boundary_trampoline() noexcept = default;
115116

116-
~dispatch_trampoline()
117+
~boundary_trampoline()
117118
{
118119
if(h_) h_.destroy();
119120
}
120121

121-
dispatch_trampoline(dispatch_trampoline const&) = delete;
122-
dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
122+
boundary_trampoline(boundary_trampoline const&) = delete;
123+
boundary_trampoline& operator=(boundary_trampoline const&) = delete;
123124

124-
dispatch_trampoline(dispatch_trampoline&& o) noexcept
125+
boundary_trampoline(boundary_trampoline&& o) noexcept
125126
: h_(std::exchange(o.h_, nullptr)) {}
126127

127-
dispatch_trampoline& operator=(dispatch_trampoline&& o) noexcept
128+
boundary_trampoline& operator=(boundary_trampoline&& o) noexcept
128129
{
129130
if(this != &o)
130131
{
@@ -135,11 +136,11 @@ struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE dispatch_trampoline
135136
}
136137

137138
private:
138-
explicit dispatch_trampoline(std::coroutine_handle<promise_type> h) noexcept
139+
explicit boundary_trampoline(std::coroutine_handle<promise_type> h) noexcept
139140
: h_(h) {}
140141
};
141142

142-
inline dispatch_trampoline make_dispatch_trampoline()
143+
inline boundary_trampoline make_boundary_trampoline()
143144
{
144145
co_return;
145146
}
@@ -170,7 +171,7 @@ struct [[nodiscard]] run_awaitable_ex
170171
frame_memory_resource<Alloc> resource_;
171172
std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
172173
io_env env_;
173-
dispatch_trampoline tr_;
174+
boundary_trampoline tr_;
174175
continuation task_cont_;
175176
Task inner_; // Last: destroyed first, while env_ is still valid
176177

@@ -224,7 +225,7 @@ struct [[nodiscard]] run_awaitable_ex
224225

225226
std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
226227
{
227-
tr_ = make_dispatch_trampoline();
228+
tr_ = make_boundary_trampoline();
228229
tr_.h_.promise().caller_ex_ = caller_env->executor;
229230
tr_.h_.promise().parent_.h = cont;
230231

@@ -245,7 +246,8 @@ struct [[nodiscard]] run_awaitable_ex
245246

246247
p.set_environment(&env_);
247248
task_cont_.h = h;
248-
return ex_.dispatch(task_cont_);
249+
ex_.post(task_cont_);
250+
return std::noop_coroutine();
249251
}
250252

251253
// Non-copyable

0 commit comments

Comments
 (0)