ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
pool/facade.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "../../pool_statistics.hpp"
4#include "../../result.hpp"
5#include "../../shutdown_policy.hpp"
6#include "../../thread_config.hpp"
7#include "../../worker_count.hpp"
8#include "../../worker_registration.hpp"
9#include "../thread/control.hpp"
10#include "shutdown.hpp"
12
13#include <chrono>
14#include <cstddef>
15#include <future>
16#include <memory>
17#include <system_error>
18#include <thread>
19#include <type_traits>
20#include <utility>
21#include <vector>
22
24{
25
26template <typename Backend>
27[[nodiscard]] auto
28make_pool_backend(worker_count count, worker_registration registration) -> std::unique_ptr<Backend>
29{
30 if constexpr (std::is_same_v<Backend, work_stealing_pool_backend>)
31 return std::make_unique<Backend>(count.resolve(),
34 else
35 return std::make_unique<Backend>(count.resolve(), registration == worker_registration::global_registry);
36}
37
38template <typename T, typename = void>
39struct has_stolen_tasks : std::false_type
40{
41};
42
43template <typename T>
44struct has_stolen_tasks<T, std::void_t<decltype(std::declval<T const&>().stolen_tasks)>> : std::true_type
45{
46};
47
48template <typename Backend>
49void
50dispose_pool_backend(std::unique_ptr<Backend>& backend) noexcept
51{
52 if (!backend)
53 return;
54
55 if (!backend->is_current_worker())
56 {
57 try
58 {
59 backend->shutdown(shutdown_policy_backend::drain);
60 }
61 catch (...)
62 {
63 }
64 backend.reset();
65 return;
66 }
67
68 // The backend is still executing the callback that destroys its facade.
69 // Transfer ownership until a different thread can safely join every worker.
70 auto* pending = backend.release();
71 try
72 {
73 std::thread reaper(
74 [pending]() noexcept
75 {
76 std::unique_ptr<Backend> owner(pending);
77 try
78 {
79 owner->shutdown(shutdown_policy_backend::drain);
80 }
81 catch (...)
82 {
83 }
84 });
85 reaper.detach();
86 }
87 catch (...)
88 {
89 // Destroying the backend on this worker would terminate the process.
90 // Keeping it alive is the only non-terminating fallback.
91 (void)pending;
92 }
93}
94
95template <typename Backend>
97{
98public:
100 : impl_(make_pool_backend<Backend>(count, registration))
101 {
102 }
103
106 submitting_pool_facade(submitting_pool_facade&& other) noexcept : impl_(std::move(other.impl_)) {}
107 auto
109 {
110 if (this != &other)
111 {
113 impl_ = std::move(other.impl_);
114 }
115 return *this;
116 }
117
118 template <typename F, typename... Args>
119 auto
120 submit(F&& function, Args&&... args) -> result<std::future<bind_result_t<F, Args...>>>
121 {
122 if (!impl_)
123 return unexpected(std::make_error_code(std::errc::operation_canceled));
124 return try_result([&]() -> result<std::future<bind_result_t<F, Args...>>>
125 { return impl_->try_submit(std::forward<F>(function), std::forward<Args>(args)...); });
126 }
127
128 template <typename F, typename... Args>
129 auto
130 submit_or_throw(F&& function, Args&&... args) -> std::future<bind_result_t<F, Args...>>
131 {
132 auto submitted = submit(std::forward<F>(function), std::forward<Args>(args)...);
133 if (!submitted)
134 throw std::system_error(submitted.error(), "advanced pool submission");
135 return std::move(*submitted);
136 }
137
138 template <typename F, typename... Args>
139 auto
140 post(F&& function, Args&&... args) -> result<void>
141 {
142 if (!impl_)
143 return unexpected(std::make_error_code(std::errc::operation_canceled));
144 return try_result([&]() -> result<void>
145 { return impl_->try_post(std::forward<F>(function), std::forward<Args>(args)...); });
146 }
147
148 template <typename F, typename... Args>
149 void
150 post_or_throw(F&& function, Args&&... args)
151 {
152 auto posted = post(std::forward<F>(function), std::forward<Args>(args)...);
153 if (!posted)
154 throw std::system_error(posted.error(), "advanced pool post");
155 }
156
157 template <typename Iterator>
158 auto
159 submit_batch(Iterator begin, Iterator end) -> result<std::vector<std::future<void>>>
160 {
161 if (!impl_)
162 return unexpected(std::make_error_code(std::errc::operation_canceled));
163 return try_result([&]() -> result<std::vector<std::future<void>>> { return impl_->try_submit_batch(begin, end); });
164 }
165
166 template <typename Iterator>
167 auto
168 submit_batch_or_throw(Iterator begin, Iterator end) -> std::vector<std::future<void>>
169 {
170 auto submitted = submit_batch(begin, end);
171 if (!submitted)
172 throw std::system_error(submitted.error(), "advanced pool batch submission");
173 return std::move(*submitted);
174 }
175
176 template <typename Iterator, typename Function>
177 auto
178 parallel_for_each(Iterator begin, Iterator end, Function&& function) -> result<void>
179 {
180 if (!impl_)
181 return unexpected(std::make_error_code(std::errc::operation_canceled));
182 return try_result(
183 [&]() -> result<void>
184 {
185 impl_->parallel_for_each(begin, end, std::forward<Function>(function));
186 return {};
187 });
188 }
189
190 auto
192 {
193 if (!impl_)
194 return unexpected(std::make_error_code(std::errc::operation_canceled));
195 return try_result(
196 [&]() -> result<void>
197 {
198 impl_->wait_for_tasks();
199 return {};
200 });
201 }
202
203 auto
205 {
206 if (!impl_)
207 return unexpected(std::make_error_code(std::errc::operation_canceled));
208 return try_result([&]() -> result<void> { return impl_->configure_threads(to_native(config)); });
209 }
210
211 auto
213 {
214 if (!impl_)
215 return unexpected(std::make_error_code(std::errc::operation_canceled));
216 return try_result([&]() -> result<void> { return impl_->distribute_across_cpus(); });
217 }
218
219 auto
221 {
222 if (!impl_)
223 return {};
224 return try_result(
225 [&]() -> result<void>
226 {
227 impl_->shutdown(to_native(policy));
228 return {};
229 });
230 }
231
232 auto
233 shutdown_for(std::chrono::milliseconds timeout) -> result<bool>
234 {
235 if (!impl_)
236 return true;
237 return try_result([&]() -> result<bool> { return impl_->shutdown_for(timeout); });
238 }
239
240 [[nodiscard]] auto
241 size() const noexcept -> std::size_t
242 {
243 return impl_ ? impl_->size() : 0;
244 }
245
246 [[nodiscard]] auto
247 pending_tasks() const -> std::size_t
248 {
249 return impl_ ? impl_->pending_tasks() : 0;
250 }
251
252 [[nodiscard]] auto
253 is_current_worker() const noexcept -> bool
254 {
255 return impl_ && impl_->is_current_worker();
256 }
257
258 [[nodiscard]] auto
260 {
261 if (!impl_)
262 return {};
263 auto const source = impl_->get_statistics();
264 pool_statistics result_value{
265 source.total_threads, source.active_threads, source.pending_tasks, source.completed_tasks, 0,
266 source.tasks_per_second, source.avg_task_time
267 };
268 if constexpr (has_stolen_tasks<decltype(source)>::value)
269 result_value.stolen_tasks = source.stolen_tasks;
270 return result_value;
271 }
272
273protected:
278
279private:
280 std::unique_ptr<Backend> impl_;
281};
282
283template <typename Backend>
285{
286public:
288 : impl_(make_pool_backend<Backend>(count, registration))
289 {
290 }
291
294 lightweight_pool_facade(lightweight_pool_facade&& other) noexcept : impl_(std::move(other.impl_)) {}
295 auto
297 {
298 if (this != &other)
299 {
301 impl_ = std::move(other.impl_);
302 }
303 return *this;
304 }
305
306 template <typename F, typename... Args>
307 auto
308 post(F&& function, Args&&... args) -> result<void>
309 {
310 if (!impl_)
311 return unexpected(std::make_error_code(std::errc::operation_canceled));
312 return try_result([&]() -> result<void>
313 { return impl_->try_post(std::forward<F>(function), std::forward<Args>(args)...); });
314 }
315
316 template <typename F, typename... Args>
317 void
318 post_or_throw(F&& function, Args&&... args)
319 {
320 auto posted = post(std::forward<F>(function), std::forward<Args>(args)...);
321 if (!posted)
322 throw std::system_error(posted.error(), "advanced lightweight pool post");
323 }
324
325 auto
327 {
328 if (!impl_)
329 return unexpected(std::make_error_code(std::errc::operation_canceled));
330 return try_result([&]() -> result<void> { return impl_->configure_threads(to_native(config)); });
331 }
332
333 auto
335 {
336 if (!impl_)
337 return unexpected(std::make_error_code(std::errc::operation_canceled));
338 return try_result([&]() -> result<void> { return impl_->distribute_across_cpus(); });
339 }
340
341 auto
343 {
344 if (!impl_)
345 return {};
346 return try_result(
347 [&]() -> result<void>
348 {
349 impl_->shutdown(to_native(policy));
350 return {};
351 });
352 }
353
354 auto
355 shutdown_for(std::chrono::milliseconds timeout) -> result<bool>
356 {
357 if (!impl_)
358 return true;
359 return try_result([&]() -> result<bool> { return impl_->shutdown_for(timeout); });
360 }
361
362 [[nodiscard]] auto
363 size() const noexcept -> std::size_t
364 {
365 return impl_ ? impl_->size() : 0;
366 }
367
368 [[nodiscard]] auto
369 is_current_worker() const noexcept -> bool
370 {
371 return impl_ && impl_->is_current_worker();
372 }
373
374protected:
379
380private:
381 std::unique_ptr<Backend> impl_;
382};
383
384} // namespace threadschedule::detail
auto post(F &&function, Args &&... args) -> result< void >
lightweight_pool_facade(worker_count count, worker_registration registration)
auto configure_workers(thread_config const &config) -> result< void >
auto operator=(lightweight_pool_facade &&other) noexcept -> lightweight_pool_facade &
auto shutdown_for(std::chrono::milliseconds timeout) -> result< bool >
lightweight_pool_facade(lightweight_pool_facade const &)=delete
void post_or_throw(F &&function, Args &&... args)
auto operator=(lightweight_pool_facade const &) -> lightweight_pool_facade &=delete
lightweight_pool_facade(lightweight_pool_facade &&other) noexcept
auto size() const noexcept -> std::size_t
auto is_current_worker() const noexcept -> bool
auto shutdown(shutdown_policy policy=shutdown_policy::drain) -> result< void >
auto submit_batch_or_throw(Iterator begin, Iterator end) -> std::vector< std::future< void > >
auto submit_or_throw(F &&function, Args &&... args) -> std::future< bind_result_t< F, Args... > >
submitting_pool_facade(submitting_pool_facade const &)=delete
auto is_current_worker() const noexcept -> bool
auto submit(F &&function, Args &&... args) -> result< std::future< bind_result_t< F, Args... > > >
submitting_pool_facade(worker_count count, worker_registration registration)
auto size() const noexcept -> std::size_t
auto get_statistics() const -> pool_statistics
auto operator=(submitting_pool_facade &&other) noexcept -> submitting_pool_facade &
auto submit_batch(Iterator begin, Iterator end) -> result< std::vector< std::future< void > > >
void post_or_throw(F &&function, Args &&... args)
auto parallel_for_each(Iterator begin, Iterator end, Function &&function) -> result< void >
auto shutdown(shutdown_policy policy=shutdown_policy::drain) -> result< void >
submitting_pool_facade(submitting_pool_facade &&other) noexcept
auto post(F &&function, Args &&... args) -> result< void >
auto shutdown_for(std::chrono::milliseconds timeout) -> result< bool >
auto operator=(submitting_pool_facade const &) -> submitting_pool_facade &=delete
auto configure_workers(thread_config const &config) -> result< void >
Portable thread configuration bundle.
Value type for pool worker count.
Aggregates multiple thread_registry_backend instances into a single queryable view.
void dispose_pool_backend(std::unique_ptr< Backend > &backend) noexcept
@ other
Standard round-robin time-sharing.
std::invoke_result_t< decltype(bind_args(std::declval< F >(), std::declval< Args >()...))& > bind_result_t
Result of invoking the decayed callable and arguments stored by bind_args.
Definition bind.hpp:32
auto make_pool_backend(worker_count count, worker_registration registration) -> std::unique_ptr< Backend >
constexpr auto to_native(shutdown_policy policy) noexcept -> shutdown_policy_backend
Definition shutdown.hpp:10
auto try_result(Function &&function) -> decltype(std::forward< Function >(function)())
shutdown_policy
Defines how pools handle pending work during shutdown.
@ drain
Finish queued work before returning from shutdown.
worker_registration
Controls whether pool workers are registered in a thread registry.
@ global_registry
Register workers in the global registry for discovery/control.
Snapshot of pool activity metrics.
std::size_t total_threads
Number of worker threads owned by the pool.
Work-stealing deque and pool implementation.