Skip to content

Conversation

@vinniefalco
Copy link
Member

@vinniefalco vinniefalco commented Jan 20, 2026

Summary by CodeRabbit

  • New Features

    • Added a non-blocking stop() to signal worker threads to stop; threads finish current work before exiting.
  • Improvements

    • Lazy-start of worker threads with sensible default sizing.
    • More cooperative shutdown and safer startup/shutdown ordering that ensures queued work is drained.
  • Tests

    • Simplified unit test build configuration and removed the separate buffers test project.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

Added a non-blocking stop() to thread_pool and refactored implementation to use std::jthread/std::stop_token, std::condition_variable_any, lazy startup, cooperative shutdown, and adjusted test Jamfiles removing the buffers test project. (49 words)

Changes

Cohort / File(s) Summary
Header Declaration
include/boost/capy/ex/thread_pool.hpp
Added public declaration BOOST_CAPY_DECL void stop() noexcept;.
Implementation & Threading
src/ex/thread_pool.cpp
Replaced std::threadstd::jthread and std::condition_variablestd::condition_variable_any; introduced stop_token-aware worker loop, stop() implementation signaling stop_source and notifying the condition variable, lazy ensure_started() with num_threads_/start_flag_, and revised destructor/shutdown sequencing and queue draining.
Tests / Build config
test/unit/Jamfile, test/unit/buffers/Jamfile
test/unit/Jamfile narrowed globs and removed explicit buffers build; test/unit/buffers/Jamfile deleted (buffers test project removed).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Pool as ThreadPool
    participant Queue as TaskQueue
    participant Worker as Worker

    Client->>Pool: post(task)
    Pool->>Pool: ensure_started() (call_once, launch jthreads)
    Pool->>Worker: start jthread (with stop_token)
    Client->>Queue: enqueue(task)
    Worker->>Queue: wait (condvar_any) or stop requested
    Queue-->>Worker: deliver task
    Worker->>Worker: execute task
    Client->>Pool: stop()
    Pool->>Pool: request_stop (stop_source)
    Pool->>Queue: notify_all()
    Worker->>Worker: finish current task or exit if stopped and queue drained
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nudged the pool to hush and drop,

jthreads listen, then they stop,
A gentle flag, they finish through,
Tasks drained softly, no waiting glue,
🌿 a quiet hop — the threads don’t stop the crop.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the three main improvements introduced in this pull request: adoption of std::jthread, addition of a stop() method, and lazy initialization of worker threads.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/ex/thread_pool.cpp`:
- Around line 87-92: The stop() currently calls stop_source_.request_stop()
which does not affect the std::jthread instances because their functions receive
the jthread-owned stop_token; change stop() to iterate the thread container
(e.g., the vector of std::jthread instances used by this pool) and call
request_stop() on each jthread (or call .request_stop() on each thread's
associated stop_source if you've stored one), then notify_all() on cv_; after
this, remove stop_source_ or alternatively make worker thread functions accept
and use the shared stop_source_.get_token() so the original stop_source_ becomes
effective (update worker thread launch code and stop() accordingly).
- Around line 124-130: The destructor currently deletes impl_ before running
shutdown() and destroy(), creating a use-after-free if those methods or
callbacks (e.g., post(), impl_->stop()) access impl_; fix it by reordering
thread_pool::~thread_pool() so it first calls shutdown() and destroy() and only
after those complete deletes impl_, ensuring impl_ remains valid during
base-class cleanup and any callbacks.

@cppalliance-bot
Copy link

cppalliance-bot commented Jan 20, 2026

An automated preview of the documentation is available at https://86.capy.prtest3.cppalliance.org/index.html

If more commits are pushed to the pull request, the docs will rebuild at the same URL.

2026-01-20 10:32:05 UTC

@cppalliance-bot
Copy link

cppalliance-bot commented Jan 20, 2026

GCOVR code coverage report https://86.capy.prtest3.cppalliance.org/gcovr/index.html
LCOV code coverage report https://86.capy.prtest3.cppalliance.org/genhtml/index.html
Coverage Diff https://86.capy.prtest3.cppalliance.org/gcovr/coverage_diff.txt

Build time: 2026-01-20 10:41:28 UTC

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ex/thread_pool.cpp (1)

47-103: Synchronize stop() with lazy-start and make stop sticky to prevent races and orphaned work.

The implementation has two issues:

  1. Data race on threads_ vector: stop() iterates threads_ while ensure_started() modifies it under call_once. If stop() is called concurrently with post() (a reasonable scenario where the main thread stops the pool while executor operations trigger post internally), this is an unsynchronized read/write on a vector.

  2. Non-sticky stop semantic: Calling stop() before the first post() has no persistent effect. A subsequent post() will trigger ensure_started(), creating fresh threads that bypass the earlier stop() signal. This is semantically wrong even in single-threaded usage—once stopped, the pool should not create new work threads.

The proposed fix correctly addresses both by introducing:

  • An atomic stopped_ flag to make stop sticky
  • A start_mutex_ to synchronize thread creation/iteration
  • Early returns in post() and ensure_started() if already stopped
Suggested fix
+#include <atomic>
+
 class thread_pool::impl
 {
     std::mutex mutex_;
     std::condition_variable_any cv_;
     intrusive_queue<work> q_;
     std::vector<std::jthread> threads_;
     std::size_t num_threads_;
     std::once_flag start_flag_;
+    std::mutex start_mutex_;
+    std::atomic<bool> stopped_{false};
 ...
     void post(any_coro h)
     {
+        if(stopped_.load(std::memory_order_acquire))
+            return;
         ensure_started();
         auto* w = new work(h);
         ...
     }
 ...
     void stop() noexcept
     {
+        stopped_.store(true, std::memory_order_release);
+        std::scoped_lock lk(start_mutex_);
         for (auto& t : threads_)
             t.request_stop();
         cv_.notify_all();
     }
 ...
     void ensure_started()
     {
+        if(stopped_.load(std::memory_order_acquire))
+            return;
         std::call_once(start_flag_, [this]{
+            std::scoped_lock lk(start_mutex_);
+            if(stopped_.load(std::memory_order_acquire))
+                return;
             threads_.reserve(num_threads_);
             for(std::size_t i = 0; i < num_threads_; ++i)
                 threads_.emplace_back([this](std::stop_token st){ run(st); });
         });
     }

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@test/unit/Jamfile`:
- Around line 29-32: The buffers tests lost the ASIO dependency and required
Windows define when the Jamfiles were consolidated; update the Jamfile by adding
a dedicated glob-tree-ex loop for buffers sources (e.g., match asio.cpp) that
invokes run $(f) with the ASIO library stanza
(<library>/boost/asio//boost_asio/<warnings-as-errors>off) and the define
_WIN32_WINNT=0x0601, or alternatively add that ASIO library and define to the
top-level project requirements so the asio.cpp test will find ASIO headers and
build successfully.

Comment on lines +29 to 32
for local f in [ glob-tree-ex . : *.cpp : file*.cpp ]
{
run $(f) ;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for any remaining buffers-specific configuration in Jamfiles
rg -n --glob 'Jamfile*' -C2 'buffers'

Repository: cppalliance/capy

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Check the current structure of test/unit directory
fd --type f 'Jamfile*' test/unit | head -20

Repository: cppalliance/capy

Length of output: 77


🏁 Script executed:

#!/bin/bash
# Get the content of test/unit/Jamfile to see the full context
cat test/unit/Jamfile

Repository: cppalliance/capy

Length of output: 1274


🏁 Script executed:

#!/bin/bash
# Check for any buffers subdirectory and its contents
fd --type f '\.(cpp|h)$' test/unit/buffers 2>/dev/null | head -20

Repository: cppalliance/capy

Length of output: 694


🏁 Script executed:

#!/bin/bash
# List all subdirectories in test/unit to see the current structure
fd --type d . test/unit | head -20

Repository: cppalliance/capy

Length of output: 195


🏁 Script executed:

#!/bin/bash
# Check git history for buffers/Jamfile
git log --oneline --all -- 'test/unit/buffers/Jamfile' 2>/dev/null | head -10

Repository: cppalliance/capy

Length of output: 188


🏁 Script executed:

#!/bin/bash
# Search for any comments or notes about buffers consolidation
rg -n 'buffers' test/unit/Jamfile

Repository: cppalliance/capy

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Check if there are any special dependencies or requirements mentioned in buffers test files
rg -l 'boost/filesystem|requirements' test/unit/buffers/*.cpp | head -5

Repository: cppalliance/capy

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Check the commits that touched buffers/Jamfile to see what was there
git show a915f4d:test/unit/buffers/Jamfile 2>/dev/null || echo "File not in that commit"
git show 3e0ebab:test/unit/buffers/Jamfile 2>/dev/null || echo "File not in that commit"

Repository: cppalliance/capy

Length of output: 1994


🏁 Script executed:

#!/bin/bash
# Check if buffers/Jamfile still exists
ls -la test/unit/buffers/Jamfile 2>/dev/null || echo "Jamfile does not exist in buffers"

Repository: cppalliance/capy

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Check the diff of the commit that may have removed it
git show --stat a915f4d | grep -i jamfile

Repository: cppalliance/capy

Length of output: 242


🏁 Script executed:

#!/bin/bash
# Check if buffers tests include ASIO headers
rg 'boost/asio|boost.asio' test/unit/buffers/ 2>/dev/null | head -10

Repository: cppalliance/capy

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Check the asio.cpp test specifically
cat test/unit/buffers/asio.cpp

Repository: cppalliance/capy

Length of output: 5304


Add ASIO library dependency for buffers tests.

The consolidation removed the <library>/boost/asio//boost_asio/<warnings-as-errors>off and Windows define (<define>_WIN32_WINNT=0x0601) requirements that were in test/unit/buffers/Jamfile. The asio.cpp test depends on ASIO headers and won't build without this library dependency. Consider adding a separate glob-tree-ex loop for buffers tests (similar to the file*.cpp loop on lines 34-39) with the required ASIO library, or add ASIO to the top-level project requirements.

🤖 Prompt for AI Agents
In `@test/unit/Jamfile` around lines 29 - 32, The buffers tests lost the ASIO
dependency and required Windows define when the Jamfiles were consolidated;
update the Jamfile by adding a dedicated glob-tree-ex loop for buffers sources
(e.g., match asio.cpp) that invokes run $(f) with the ASIO library stanza
(<library>/boost/asio//boost_asio/<warnings-as-errors>off) and the define
_WIN32_WINNT=0x0601, or alternatively add that ASIO library and define to the
top-level project requirements so the asio.cpp test will find ASIO headers and
build successfully.

@vinniefalco vinniefalco merged commit f125ed0 into cppalliance:develop Jan 20, 2026
15 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 23, 2026
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