Skip to content

Add thread_pool_scheduler opt-in utility policy#15

Merged
gabewillen merged 4 commits into
mainfrom
thread-pool-scheduler
Jun 29, 2026
Merged

Add thread_pool_scheduler opt-in utility policy#15
gabewillen merged 4 commits into
mainfrom
thread-pool-scheduler

Conversation

@gabewillen

@gabewillen gabewillen commented Jun 29, 2026

Copy link
Copy Markdown

What

Adds thread_pool_scheduler (plus thread_pool_scheduler_ref + its join_group) as an opt-in utility scheduler policy at boost/sml/utility/thread_pool_scheduler.hpp, alongside the existing inline_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

  • Opt-in; thread-free core preserved. The header self-gates on C++20 + <semaphore> via BOOST_SML_UTILITY_THREAD_POOL_SCHEDULER_ENABLED, mirroring co_sm's gating. The dependency-free, freestanding core is untouched — nothing pays for <thread>/<semaphore> unless this header is included.
  • No allocation on dispatch. Fixed MPMC ring (Vyukov-style bounded queue) with inline task storage; std::thread may allocate only at construction.
  • Lifetime-safe, deadlock-free fork/join. join_group::wait() spin-joins on pending_ (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_wait spins on a local flag the worker sets last. A cpu_relax() spin hint and warm-polling workers keep the pool hot across fork/join bursts.
  • Exposes the same policy-introspection traits as the sibling schedulers (guarantees_fifo, single_consumer, run_to_completion, …) plus multi_consumer / owns_workers.

Validation

  • Compiles clean (-Wall -Wextra) under clang 20 and g++ 13 at C++20.
  • ASAN/UBSAN clean and TSAN clean.
  • New 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 when lane_count == worker_count.
  • Builds + runs through the existing CMake/ctest (-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_scheduler utility policy (boost/sml/utility/thread_pool_scheduler.hpp, mirrored under stateforward/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> via BOOST_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, and thread_pool_scheduler_ref with join_group for lifetime-safe spin-join (avoiding per-group semaphore destroy-during-notify issues). Actor-facing APIs include run_or_schedule_and_wait, schedule, and try_submit with worker-thread rejection rules.

README documents the new feature. Functional tests cover idle inline execution, fork/join lanes, reused join_group after 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.

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).
Copilot AI review requested due to automatic review settings June 29, 2026 02:34

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ 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.

Comment thread include/boost/sml/utility/thread_pool_scheduler.hpp
Comment thread include/boost/sml/utility/thread_pool_scheduler.hpp
@gabewillen gabewillen force-pushed the thread-pool-scheduler branch from 190def8 to 699b0aa Compare June 29, 2026 02:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.hpp implementing 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.

Comment thread include/boost/sml/utility/thread_pool_scheduler.hpp Outdated
Comment thread include/boost/sml/utility/thread_pool_scheduler.hpp
Comment thread include/boost/sml/utility/thread_pool_scheduler.hpp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread include/boost/sml/utility/thread_pool_scheduler.hpp
- 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread include/boost/sml/utility/thread_pool_scheduler.hpp
Comment thread README.md
- 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread include/boost/sml/utility/thread_pool_scheduler.hpp Outdated
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.
@gabewillen

Copy link
Copy Markdown
Author

@codex

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 4373d991c0

ℹ️ 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".

@gabewillen gabewillen merged commit 78d9d40 into main Jun 29, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants