From f7dcc9dbabd5b7b179ee4efe19a3c2b526fbdabe Mon Sep 17 00:00:00 2001 From: Steve Gerbino Date: Wed, 11 Mar 2026 22:55:26 +0100 Subject: [PATCH] Dispatch run(ex) task through target executor run_awaitable_ex::await_suspend was resuming the child task via direct symmetric transfer, bypassing the target executor. This meant the first instructions of the task body ran inline on the caller's thread rather than on the executor's thread. For strands, this violated the serialization invariant from the very first instruction. Route the initial resumption through ex_.dispatch(h) so the executor decides where the task starts. Add a strand-based test that verifies running_in_this_thread() is true at the first instruction of the child task. --- include/boost/capy/ex/run.hpp | 2 +- test/unit/ex/run.cpp | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/include/boost/capy/ex/run.hpp b/include/boost/capy/ex/run.hpp index 020f0bf9..40d13aad 100644 --- a/include/boost/capy/ex/run.hpp +++ b/include/boost/capy/ex/run.hpp @@ -241,7 +241,7 @@ struct [[nodiscard]] run_awaitable_ex env_.frame_allocator = caller_env->frame_allocator; p.set_environment(&env_); - return h; + return ex_.dispatch(h); } // Non-copyable diff --git a/test/unit/ex/run.cpp b/test/unit/ex/run.cpp index 7a11866c..a968ef60 100644 --- a/test/unit/ex/run.cpp +++ b/test/unit/ex/run.cpp @@ -19,6 +19,10 @@ #include "test/unit/custom_task.hpp" #include "test/unit/test_helpers.hpp" +#include +#include + +#include #include namespace boost { @@ -468,6 +472,34 @@ struct run_test BOOST_TEST(result); } + void + testRunExStrandFirstInstruction() + { + // Verify that the first instructions of a task passed + // to run(strand) execute inside the strand's serialization, + // not inline on an unprotected thread. + thread_pool pool(2, "str-pool-"); + strand s(pool.get_executor()); + bool inside_strand = false; + std::latch done(1); + + auto inner = [&]() -> task { + inside_strand = s.running_in_this_thread(); + co_return; + }; + + auto outer = [&]() -> task { + co_await capy::run(s)(inner()); + }; + + run_async(pool.get_executor(), + [&]() { done.count_down(); })(outer()); + done.wait(); + + BOOST_TEST(inside_strand); + pool.join(); + } + void run() { @@ -490,6 +522,7 @@ struct run_test testStopTokenOverrideOuterStopped(); testAllocatorPropagation(); testAllocatorPropagationThroughRun(); + testRunExStrandFirstInstruction(); } };