From 2c7dc691a192e344004cac694a0acadb6f873c22 Mon Sep 17 00:00:00 2001 From: alwaysprince05 Date: Tue, 15 Sep 2026 00:46:24 +0530 Subject: [PATCH 1/2] fix: provide the scheduler_concept nested alias on all stdexec schedulers (#2134) The scheduler concept in [exec.sched] requires a scheduler_concept nested alias derived from scheduler_tag, and exec::task's internal __any_scheduler conversion checks it via __semi_scheduler. Most of stdexec's schedulers define that typedef, but several did not, so co_starting an exec::task on them failed to compile (see https://github.com/NVIDIA/stdexec/issues/2134). Add the missing `using scheduler_concept = scheduler_tag;` to the schedulers that lacked it: static_thread_pool, thread_pool_base (also covering the tbb and taskflow pools), timed_thread_scheduler, libdispatch_scheduler, io_uring_context, windows_thread_pool, trampoline_scheduler, reschedule, parallel_scheduler, and the nvexec stream and multi-GPU stream schedulers. Schedulers that already define the alias are unchanged, and include/exec/task.hpp is untouched. Also add regression tests that co_start an exec::task on the schedulers of exec::static_thread_pool and exec::timed_thread_context and verify they run, guarding the fix going forward. Fixes #2134 Generated with Codebuff Co-Authored-By: Codebuff --- include/exec/libdispatch_queue.hpp | 2 + include/exec/linux/io_uring_context.hpp | 2 + include/exec/reschedule.hpp | 2 + include/exec/static_thread_pool.hpp | 2 + include/exec/thread_pool_base.hpp | 2 + include/exec/timed_thread_scheduler.hpp | 1 + include/exec/trampoline_scheduler.hpp | 2 + include/exec/windows/windows_thread_pool.hpp | 1 + include/nvexec/multi_gpu_context.cuh | 2 + include/nvexec/stream_context.cuh | 2 + .../stdexec/__detail/__parallel_scheduler.hpp | 2 + test/exec/test_task.cpp | 40 +++++++++++++++++++ 12 files changed, 60 insertions(+) diff --git a/include/exec/libdispatch_queue.hpp b/include/exec/libdispatch_queue.hpp index 5f717cacf..1c9bb6065 100644 --- a/include/exec/libdispatch_queue.hpp +++ b/include/exec/libdispatch_queue.hpp @@ -117,6 +117,8 @@ namespace experimental::execution struct libdispatch_scheduler { + using scheduler_concept = STDEXEC::scheduler_tag; + bool operator==(libdispatch_scheduler const &) const = default; struct domain diff --git a/include/exec/linux/io_uring_context.hpp b/include/exec/linux/io_uring_context.hpp index 011cf1fb6..1ebc197d5 100644 --- a/include/exec/linux/io_uring_context.hpp +++ b/include/exec/linux/io_uring_context.hpp @@ -1226,6 +1226,8 @@ namespace experimental::execution class __scheduler { public: + using scheduler_concept = STDEXEC::scheduler_tag; + __context* __context_; auto operator==(__scheduler const &) const -> bool = default; diff --git a/include/exec/reschedule.hpp b/include/exec/reschedule.hpp index 51b30d3c6..736e90f01 100644 --- a/include/exec/reschedule.hpp +++ b/include/exec/reschedule.hpp @@ -48,6 +48,8 @@ namespace experimental::execution struct __scheduler { + using scheduler_concept = scheduler_tag; + struct __sender { using sender_concept = sender_tag; diff --git a/include/exec/static_thread_pool.hpp b/include/exec/static_thread_pool.hpp index 1dcee0aa0..8946b59bc 100644 --- a/include/exec/static_thread_pool.hpp +++ b/include/exec/static_thread_pool.hpp @@ -341,6 +341,8 @@ namespace experimental::execution struct scheduler { + using scheduler_concept = scheduler_tag; + private: template friend struct _opstate; diff --git a/include/exec/thread_pool_base.hpp b/include/exec/thread_pool_base.hpp index 47a892204..09c278d46 100644 --- a/include/exec/thread_pool_base.hpp +++ b/include/exec/thread_pool_base.hpp @@ -138,6 +138,8 @@ namespace experimental::execution struct scheduler { + using scheduler_concept = STDEXEC::scheduler_tag; + private: template friend struct _pool_::opstate; diff --git a/include/exec/timed_thread_scheduler.hpp b/include/exec/timed_thread_scheduler.hpp index b84f1d8f4..1721d5ad1 100644 --- a/include/exec/timed_thread_scheduler.hpp +++ b/include/exec/timed_thread_scheduler.hpp @@ -365,6 +365,7 @@ namespace experimental::execution class timed_thread_scheduler { public: + using scheduler_concept = STDEXEC::scheduler_tag; using time_point = std::chrono::steady_clock::time_point; using duration = std::chrono::steady_clock::duration; diff --git a/include/exec/trampoline_scheduler.hpp b/include/exec/trampoline_scheduler.hpp index c11a8eb63..449385ab1 100644 --- a/include/exec/trampoline_scheduler.hpp +++ b/include/exec/trampoline_scheduler.hpp @@ -111,6 +111,8 @@ namespace experimental::execution std::size_t __max_recursion_size_; public: + using scheduler_concept = scheduler_tag; + constexpr __scheduler() noexcept : __attrs{16} , __max_recursion_size_(4096) diff --git a/include/exec/windows/windows_thread_pool.hpp b/include/exec/windows/windows_thread_pool.hpp index 0ca885c68..632d92a20 100644 --- a/include/exec/windows/windows_thread_pool.hpp +++ b/include/exec/windows/windows_thread_pool.hpp @@ -891,6 +891,7 @@ namespace experimental::execution::__win32 class windows_thread_pool::scheduler { public: + using scheduler_concept = STDEXEC::scheduler_tag; using time_point = filetime_clock::time_point; [[nodiscard]] diff --git a/include/nvexec/multi_gpu_context.cuh b/include/nvexec/multi_gpu_context.cuh index 77a452245..3f5940693 100644 --- a/include/nvexec/multi_gpu_context.cuh +++ b/include/nvexec/multi_gpu_context.cuh @@ -31,6 +31,8 @@ namespace nv::execution { struct multi_gpu_stream_scheduler : private stream_scheduler_env { + using scheduler_concept = STDEXEC::scheduler_tag; + multi_gpu_stream_scheduler(int num_devices, context ctx) : num_devices_(num_devices) , ctx_(ctx) diff --git a/include/nvexec/stream_context.cuh b/include/nvexec/stream_context.cuh index 5d5b60342..deb5fc4ef 100644 --- a/include/nvexec/stream_context.cuh +++ b/include/nvexec/stream_context.cuh @@ -69,6 +69,8 @@ namespace nv::execution struct stream_scheduler : private stream_scheduler_env { + using scheduler_concept = STDEXEC::scheduler_tag; + explicit stream_scheduler(context ctx) noexcept : ctx_(ctx) {} diff --git a/include/stdexec/__detail/__parallel_scheduler.hpp b/include/stdexec/__detail/__parallel_scheduler.hpp index 954e9d074..821e439f7 100644 --- a/include/stdexec/__detail/__parallel_scheduler.hpp +++ b/include/stdexec/__detail/__parallel_scheduler.hpp @@ -293,6 +293,8 @@ namespace STDEXEC class parallel_scheduler { public: + using scheduler_concept = scheduler_tag; + parallel_scheduler() = delete; /// Returns `true` iff `*this` refers to the same scheduler as the argument. diff --git a/test/exec/test_task.cpp b/test/exec/test_task.cpp index dfc1e2d0e..8984625c4 100644 --- a/test/exec/test_task.cpp +++ b/test/exec/test_task.cpp @@ -21,7 +21,9 @@ # include # include # include +# include # include +# include # include @@ -370,6 +372,44 @@ namespace CHECK(count == 3); } # endif // !STDEXEC_NO_STDCPP_EXCEPTIONS() + + // Regression tests for https://github.com/NVIDIA/stdexec/issues/2134: the + // schedulers of exec::static_thread_pool and exec::timed_thread_context used + // to be missing the `scheduler_concept` nested alias required by the + // scheduler concept, which made exec::task's internal __any_scheduler + // conversion fail to compile when such a task was co_started on them. + TEST_CASE("task - can be started on a static_thread_pool scheduler", "[types][task]") + { + exec::static_thread_pool pool{2}; + bool ran = false; + // Keep the closure a temporary inside the full expression: the task is + // lazy, so the closure (and its by-reference capture) must outlive the + // point at which the pool thread resumes the coroutine. + auto op_state = + STDEXEC::sync_wait(STDEXEC::starts_on(pool.get_scheduler(), + [&]() -> exec::task + { + ran = true; + co_return; + }())); + REQUIRE(op_state); + CHECK(ran); + } + + TEST_CASE("task - can be started on a timed_thread_context scheduler", "[types][task]") + { + exec::timed_thread_context ctx; + bool ran = false; + auto op_state = + STDEXEC::sync_wait(STDEXEC::starts_on(ctx.get_scheduler(), + [&]() -> exec::task + { + ran = true; + co_return; + }())); + REQUIRE(op_state); + CHECK(ran); + } } // namespace #endif From 7b4b2c5902a90667f82fdd2f3a1a87f157207d23 Mon Sep 17 00:00:00 2001 From: alwaysprince05 Date: Tue, 15 Sep 2026 14:05:08 +0530 Subject: [PATCH 2/2] style: fix clang-format violations in files touched by the PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align the `=` in the time_point/duration aliases and re-indent the new test_task.cpp tests so `clang-format-21 --dry-run --Werror` (the CI style check) passes. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- include/exec/timed_thread_scheduler.hpp | 4 +-- include/exec/windows/windows_thread_pool.hpp | 2 +- test/exec/test_task.cpp | 30 +++++++++----------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/include/exec/timed_thread_scheduler.hpp b/include/exec/timed_thread_scheduler.hpp index 1721d5ad1..45a8914d8 100644 --- a/include/exec/timed_thread_scheduler.hpp +++ b/include/exec/timed_thread_scheduler.hpp @@ -366,8 +366,8 @@ namespace experimental::execution { public: using scheduler_concept = STDEXEC::scheduler_tag; - using time_point = std::chrono::steady_clock::time_point; - using duration = std::chrono::steady_clock::duration; + using time_point = std::chrono::steady_clock::time_point; + using duration = std::chrono::steady_clock::duration; class schedule_at_sender { diff --git a/include/exec/windows/windows_thread_pool.hpp b/include/exec/windows/windows_thread_pool.hpp index 632d92a20..f41776d52 100644 --- a/include/exec/windows/windows_thread_pool.hpp +++ b/include/exec/windows/windows_thread_pool.hpp @@ -892,7 +892,7 @@ namespace experimental::execution::__win32 { public: using scheduler_concept = STDEXEC::scheduler_tag; - using time_point = filetime_clock::time_point; + using time_point = filetime_clock::time_point; [[nodiscard]] auto schedule() const noexcept -> schedule_sender diff --git a/test/exec/test_task.cpp b/test/exec/test_task.cpp index 8984625c4..97f036f59 100644 --- a/test/exec/test_task.cpp +++ b/test/exec/test_task.cpp @@ -381,17 +381,16 @@ namespace TEST_CASE("task - can be started on a static_thread_pool scheduler", "[types][task]") { exec::static_thread_pool pool{2}; - bool ran = false; + bool ran = false; // Keep the closure a temporary inside the full expression: the task is // lazy, so the closure (and its by-reference capture) must outlive the // point at which the pool thread resumes the coroutine. - auto op_state = - STDEXEC::sync_wait(STDEXEC::starts_on(pool.get_scheduler(), - [&]() -> exec::task - { - ran = true; - co_return; - }())); + auto op_state = STDEXEC::sync_wait(STDEXEC::starts_on(pool.get_scheduler(), + [&]() -> exec::task + { + ran = true; + co_return; + }())); REQUIRE(op_state); CHECK(ran); } @@ -399,14 +398,13 @@ namespace TEST_CASE("task - can be started on a timed_thread_context scheduler", "[types][task]") { exec::timed_thread_context ctx; - bool ran = false; - auto op_state = - STDEXEC::sync_wait(STDEXEC::starts_on(ctx.get_scheduler(), - [&]() -> exec::task - { - ran = true; - co_return; - }())); + bool ran = false; + auto op_state = STDEXEC::sync_wait(STDEXEC::starts_on(ctx.get_scheduler(), + [&]() -> exec::task + { + ran = true; + co_return; + }())); REQUIRE(op_state); CHECK(ran); }