Lightweight 0.20260617.0
Loading...
Searching...
No Matches
ThreadPoolExecutor.hpp
1// SPDX-License-Identifier: Apache-2.0
2#pragma once
3
4#include "../Api.hpp"
5#include "Executor.hpp"
6
7#include <cstddef>
8#include <memory>
9
10namespace Lightweight::Async
11{
12
13/// A fixed-size pool of worker threads that run posted work concurrently.
14///
15/// This is the default "DB worker" offload target: blocking ODBC calls are posted here and
16/// executed on a worker thread while the awaiting coroutine is suspended. The pool is built on
17/// stdexec's @c exec::static_thread_pool (the C++26 @c std::execution scheduler model); each posted
18/// @c Work item is spawned as a @c schedule|then sender into an @c exec::async_scope so the
19/// destructor can wait for every in-flight item to finish, draining and joining. The pool must
20/// therefore outlive every coroutine that can resume on it.
21///
22/// The stdexec machinery lives in a pimpl defined in the translation unit, so this public header
23/// pulls in no stdexec headers — keeping them out of the C++20 module's global module fragment and
24/// off every downstream consumer that does not opt in.
25class LIGHTWEIGHT_API ThreadPoolExecutor final: public IExecutor, public IResumeScheduler
26{
27 public:
28 /// Constructs the pool and starts @p threadCount worker threads.
29 ///
30 /// @param threadCount Number of worker threads to start (must be >= 1).
31 /// @throws std::invalid_argument if @p threadCount is 0 or exceeds the supported maximum
32 /// (@c std::uint32_t, the width the underlying stdexec pool accepts).
33 explicit ThreadPoolExecutor(std::size_t threadCount);
34
36 ThreadPoolExecutor& operator=(ThreadPoolExecutor const&) = delete;
38 ThreadPoolExecutor& operator=(ThreadPoolExecutor&&) = delete;
39
40 /// Waits for all in-flight work to drain, then stops and joins the worker threads.
41 ///
42 /// @note Must not be invoked from one of this pool's own worker threads (i.e. the pool must
43 /// outlive every coroutine that can resume on it). The drain blocks the calling thread,
44 /// so destroying the pool from a thread it owns would deadlock — the same constraint the
45 /// previous join-based teardown had.
47
48 void Post(Work work) override;
49 void Resume(std::coroutine_handle<> handle) override;
50
51 /// @return the configured worker count (the value passed to the constructor).
52 /// @note This is the requested count, not a live count of OS threads; it is fixed for the
53 /// pool's lifetime and does not reflect any internal clamping the scheduler might apply.
54 [[nodiscard]] std::size_t ThreadCount() const noexcept
55 {
56 return _threadCount;
57 }
58
59 private:
60 /// Holds the stdexec @c static_thread_pool and @c async_scope; defined in the .cpp so the
61 /// stdexec headers never reach this public header (nor the module's global module fragment).
62 struct Impl;
63
64 std::size_t _threadCount; ///< Configured worker count (exposed via ThreadCount()).
65 std::unique_ptr<Impl> _impl; ///< stdexec pool + scope; destroyed (and drained) in ~ThreadPoolExecutor.
66};
67
68} // namespace Lightweight::Async
void Resume(std::coroutine_handle<> handle) override
ThreadPoolExecutor(std::size_t threadCount)
std::size_t ThreadCount() const noexcept