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