-
Notifications
You must be signed in to change notification settings - Fork 7
thread_pool improvements: jthread, stop, lazy #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughAdded a non-blocking Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant ThreadPool
participant TaskQueue
participant Worker
Client->>ThreadPool: post(task)
ThreadPool->>ThreadPool: ensure_started()
ThreadPool->>Worker: start jthread (with stop_token)
Client->>TaskQueue: enqueue(task)
Worker->>TaskQueue: wait for task or stop (condvar + stop_token)
TaskQueue-->>Worker: deliver task
Worker->>Worker: execute task
Client->>ThreadPool: stop()
ThreadPool->>Worker: request_stop (via stop_source -> stop_token)
ThreadPool->>TaskQueue: notify_all (wake condition variable)
Worker->>Worker: finish current task (or exit if none), then return/exit
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this 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.
|
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 03:37:12 UTC |
|
GCOVR code coverage report https://86.capy.prtest3.cppalliance.org/gcovr/index.html Build time: 2026-01-20 03:47:18 UTC |
There was a problem hiding this 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: Synchronizestop()with lazy-start and make stop sticky to prevent races and orphaned work.The implementation has two issues:
Data race on
threads_vector:stop()iteratesthreads_whileensure_started()modifies it undercall_once. Ifstop()is called concurrently withpost()(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.Non-sticky stop semantic: Calling
stop()before the firstpost()has no persistent effect. A subsequentpost()will triggerensure_started(), creating fresh threads that bypass the earlierstop()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()andensure_started()if already stoppedSuggested 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); }); }); }
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.