Add thread_pool_scheduler opt-in utility policy#15
Conversation
A multi-consumer work pool with a lifetime-safe, deadlock-free fork/join latch, for action-level inter-op parallelism across SML actor dispatches. It sits alongside inline_scheduler / fifo_scheduler / coroutine_scheduler and exposes the same policy-introspection traits. Opt-in module: gated on C++20 and <semaphore> so the freestanding, dependency-free core stays thread-free by default. Uses a fixed MPMC task ring (no allocation on dispatch), warm-polling workers, and a spin-join that avoids destroy-during-notify on caller-owned join groups. Validated under clang 20 and g++ 13, clean under ASAN/UBSAN and TSAN, with a 20000-round x 8-lane fork/join regression guarding the join-latch deadlock (lane_count == worker_count).
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 190def8. Configure here.
190def8 to
699b0aa
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds a new opt-in concurrency utility to the Boost.SML utility policy set: a thread_pool_scheduler (and thread_pool_scheduler_ref + join_group) intended to provide a fork/join-capable, multi-consumer scheduling policy without impacting the thread-free core unless explicitly included.
Changes:
- Adds
include/boost/sml/utility/thread_pool_scheduler.hppimplementing a fixed-capacity MPMC task ring with worker threads and a spin-based fork/join latch. - Adds functional tests for the scheduler, including a high-iteration fork/join regression.
- Registers the new test target in CMake and documents the scheduler in the README.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| include/boost/sml/utility/thread_pool_scheduler.hpp | New opt-in thread pool scheduler policy + ref wrapper and join_group fork/join primitive. |
| test/ft/thread_pool_scheduler.cpp | New functional tests covering idle inline execution and repeated fork/join regression. |
| test/ft/CMakeLists.txt | Adds the new test executable and links pthread. |
| README.md | Documents the new scheduler in the utility list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 190def82c8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Gate __has_include(<semaphore>) behind defined(__has_include) so toolchains below C++20 or without the extension never have to parse it. - Clear join_group::accepted_ in wait() once pending_ has drained, so a stack-reused group starts each round clean and a prior rejection no longer sticks to later all-success rounds. Adds a regression test (worker-thread rejection followed by an all-success round) that fails without the reset. The MSVC cpu_relax pause-intrinsic fix landed in the previous commit.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 624ea67efa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Guard start_workers()'s try/catch/throw behind BOOST_SML_DISABLE_EXCEPTIONS so the opt-in header compiles under -fno-exceptions. A failed std::thread spawn there calls std::terminate, matching that configuration's contract. - Add include/stateforward/sml/utility/thread_pool_scheduler.hpp so the scheduler is reachable through the fork's public include path, consistent with co_sm / sm_pool / dispatch_table.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4e5b799dc7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
run_or_schedule_and_wait (and the ref's schedule / run_or_schedule_and_wait) may run work through a noexcept worker thunk that cannot propagate, so the conditional noexcept(noexcept(fn())) wrongly advertised propagation that only the inline branch could honor -- the same call would propagate or terminate by pool load. Mark the scheduled-capable entry points unconditionally noexcept (matching fifo_scheduler::schedule); pure-inline try_run_immediate stays conditional. Adds static_assert coverage of the contract.
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |

What
Adds
thread_pool_scheduler(plusthread_pool_scheduler_ref+ itsjoin_group) as an opt-in utility scheduler policy atboost/sml/utility/thread_pool_scheduler.hpp, alongside the existinginline_scheduler/fifo_scheduler/coroutine_scheduler.Why
The other schedulers are library-owned; a thread-pool fork/join scheduler is the natural multi-consumer member of that family. It is pure, general concurrency infrastructure with no application coupling, and it is exactly the kind of subtle primitive that benefits from being centrally owned and tested rather than re-derived downstream. It enables the action-level fork/join wait pattern — join already-submitted child dispatches before the action returns — for inter-op parallelism across actor graphs.
Design
<semaphore>viaBOOST_SML_UTILITY_THREAD_POOL_SCHEDULER_ENABLED, mirroringco_sm's gating. The dependency-free, freestanding core is untouched — nothing pays for<thread>/<semaphore>unless this header is included.std::threadmay allocate only at construction.join_group::wait()spin-joins onpending_(a completer's final touch is its decrement) instead of blocking on a per-group semaphore, so a caller-owned, stack-reused group can't be destroyed mid-release()/notify.run_or_schedule_and_waitspins on a local flag the worker sets last. Acpu_relax()spin hint and warm-polling workers keep the pool hot across fork/join bursts.guarantees_fifo,single_consumer,run_to_completion, …) plusmulti_consumer/owns_workers.Validation
-Wall -Wextra) under clang 20 and g++ 13 at C++20.test/ft/thread_pool_scheduler.cpp: runs inline when the pool is idle, fork/join runs every lane, and a 20000-round × 8-lane regression that guards the join-latch deadlock surfacing whenlane_count == worker_count.-DSML_BUILD_TESTS=ON); the disabled path compiles to a single no-op test when C++20/<semaphore>are unavailable.Note
Medium Risk
New concurrent infrastructure with subtle join/lifetime semantics; core SML remains untouched unless the header is included, but misuse of submit/schedule or queue saturation can terminate or reject work.
Overview
Adds an opt-in
thread_pool_schedulerutility policy (boost/sml/utility/thread_pool_scheduler.hpp, mirrored understateforward/sml/utility/) for multi-consumer, action-level fork/join parallelism alongside the existing inline/FIFO/coroutine schedulers.The implementation is feature-gated on C++20 and
<semaphore>viaBOOST_SML_UTILITY_THREAD_POOL_SCHEDULER_ENABLED, so the header-only core stays thread-free unless this header is included. It provides a fixed-size MPMC task ring with inline task storage (no heap on dispatch), warm worker threads, andthread_pool_scheduler_refwithjoin_groupfor lifetime-safe spin-join (avoiding per-group semaphore destroy-during-notify issues). Actor-facing APIs includerun_or_schedule_and_wait,schedule, andtry_submitwith worker-thread rejection rules.README documents the new feature. Functional tests cover idle inline execution, fork/join lanes, reused
join_groupafter rejection, noexcept contracts on scheduled paths, and a 20k-round regression against join-latch deadlocks; CMake wires the test with pthread.Reviewed by Cursor Bugbot for commit 4373d99. Bugbot is set up for automated code reviews on this repo. Configure here.