ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
scheduled_pool.hpp
Go to the documentation of this file.
1#pragma once
2
11#include "detail/time.hpp"
12#include "scheduled_task.hpp"
13#include "shutdown_policy.hpp"
14#include "task_error.hpp"
15#include "thread_config.hpp"
16#include "worker_count.hpp"
18
19#include <atomic>
20#include <chrono>
21#include <cstddef>
22#include <functional>
23#include <memory>
24#include <system_error>
25#include <thread>
26#include <type_traits>
27#include <utility>
28
29namespace threadschedule
30{
35{
36public:
38 auto
40 {
41 worker_count_ = value;
42 return *this;
43 }
45 auto
47 {
48 registration_ = value;
49 return *this;
50 }
52 auto
54 {
55 workers_ = std::move(value);
56 return *this;
57 }
59 auto
61 {
62 scheduler_ = std::move(value);
63 return *this;
64 }
66 auto
68 {
69 shutdown_ = value;
70 return *this;
71 }
73 auto
75 {
76 on_task_error_ = std::move(value);
77 return *this;
78 }
79
81 [[nodiscard]] auto
82 get_worker_count() const noexcept -> worker_count
83 {
84 return worker_count_;
85 }
87 [[nodiscard]] auto
89 {
90 return registration_;
91 }
93 [[nodiscard]] auto
94 get_worker_config() const noexcept -> thread_config const&
95 {
96 return workers_;
97 }
99 [[nodiscard]] auto
100 get_scheduler_config() const noexcept -> thread_config const&
101 {
102 return scheduler_;
103 }
105 [[nodiscard]] auto
107 {
108 return shutdown_;
109 }
111 [[nodiscard]] auto
112 get_error_callback() const noexcept -> error_callback const&
113 {
114 return on_task_error_;
115 }
116
117private:
118 worker_count worker_count_{ worker_count::automatic() };
120 thread_config workers_;
121 thread_config scheduler_;
123 error_callback on_task_error_;
124};
125
130{
131public:
134
139 explicit scheduled_pool(worker_count count) : scheduled_pool(scheduled_pool_config{}.set_worker_count(count)) {}
140
147 : config_(std::move(config)),
148 impl_(std::make_unique<detail::scheduled_pool_backend>(
149 config_.get_worker_count().resolve(), config_.get_registration() == worker_registration::global_registry))
150 {
152 {
153 auto configured = impl_->configure_threads(detail::to_native(config_.get_worker_config()));
154 if (!configured)
155 throw std::system_error(configured.error(), "scheduled_pool worker configuration");
156 }
157
159 {
160 auto configured = impl_->configure_scheduler_thread(detail::to_native(config_.get_scheduler_config()));
161 if (!configured)
162 throw std::system_error(configured.error(), "scheduled_pool scheduler configuration");
163 }
164 }
165
167 : config_(std::move(other.config_)), impl_(std::move(other.impl_)),
168 stopped_(other.stopped_.load(std::memory_order_acquire))
169 {
170 other.stopped_.store(true, std::memory_order_release);
171 }
172
173 auto
175 {
176 if (this != &other)
177 {
178 dispose_impl();
179 config_ = std::move(other.config_);
180 impl_ = std::move(other.impl_);
181 stopped_.store(other.stopped_.load(std::memory_order_acquire), std::memory_order_release);
182 other.stopped_.store(true, std::memory_order_release);
183 }
184 return *this;
185 }
187 auto operator=(scheduled_pool const&) -> scheduled_pool& = delete;
188
193 {
194 dispose_impl();
195 }
196
202 static auto
204 {
205 return detail::try_result([&config]() -> result<scheduled_pool> { return scheduled_pool(std::move(config)); });
206 }
207
214 template <typename Rep, typename Period, typename F>
215 auto
216 schedule_after(std::chrono::duration<Rep, Period> delay, F&& function) -> result<scheduled_task>
217 {
218 if (stopped_.load(std::memory_order_acquire))
219 return unexpected(std::make_error_code(std::errc::operation_canceled));
220 return detail::try_result(
222 {
223 auto native_delay = detail::checked_duration_cast<detail::scheduled_pool_backend::duration>(delay);
224 if (!native_delay)
225 return unexpected(native_delay.error());
226 auto handle = impl_->schedule_after(native_delay.value(), wrap_task(std::forward<F>(function)));
227 if (handle.is_cancelled())
228 return unexpected(std::make_error_code(std::errc::operation_canceled));
229 return scheduled_task(std::move(handle));
230 });
231 }
232
238 template <typename F>
239 auto
240 schedule_at(std::chrono::steady_clock::time_point time, F&& function) -> result<scheduled_task>
241 {
242 if (stopped_.load(std::memory_order_acquire))
243 return unexpected(std::make_error_code(std::errc::operation_canceled));
244 return detail::try_result(
246 {
247 auto handle = impl_->schedule_at(time, wrap_task(std::forward<F>(function)));
248 if (handle.is_cancelled())
249 return unexpected(std::make_error_code(std::errc::operation_canceled));
250 return scheduled_task(std::move(handle));
251 });
252 }
253
260 template <typename Rep, typename Period, typename F>
261 auto
262 schedule_periodic(std::chrono::duration<Rep, Period> interval, F&& function) -> result<scheduled_task>
263 {
264 if (stopped_.load(std::memory_order_acquire))
265 return unexpected(std::make_error_code(std::errc::operation_canceled));
266 auto const native_interval = detail::checked_duration_cast<detail::scheduled_pool_backend::duration>(interval);
267 if (!native_interval)
268 return unexpected(native_interval.error());
269 if (native_interval.value() <= detail::scheduled_pool_backend::duration::zero())
270 return unexpected(std::make_error_code(std::errc::invalid_argument));
271 return detail::try_result(
273 {
274 auto handle = impl_->schedule_periodic(native_interval.value(), wrap_task(std::forward<F>(function)));
275 if (handle.is_cancelled())
276 return unexpected(std::make_error_code(std::errc::operation_canceled));
277 return scheduled_task(std::move(handle));
278 });
279 }
280
287 template <typename InitialRep, typename InitialPeriod, typename IntervalRep, typename IntervalPeriod, typename F>
288 auto
289 schedule_periodic_after(std::chrono::duration<InitialRep, InitialPeriod> initial_delay,
290 std::chrono::duration<IntervalRep, IntervalPeriod> interval, F&& function)
292 {
293 if (stopped_.load(std::memory_order_acquire))
294 return unexpected(std::make_error_code(std::errc::operation_canceled));
295 auto const native_interval = detail::checked_duration_cast<detail::scheduled_pool_backend::duration>(interval);
296 if (!native_interval)
297 return unexpected(native_interval.error());
298 if (native_interval.value() <= detail::scheduled_pool_backend::duration::zero())
299 return unexpected(std::make_error_code(std::errc::invalid_argument));
300 auto const native_delay = detail::checked_duration_cast<detail::scheduled_pool_backend::duration>(initial_delay);
301 if (!native_delay)
302 return unexpected(native_delay.error());
303 return detail::try_result(
305 {
306 auto handle = impl_->schedule_periodic_after(native_delay.value(), native_interval.value(),
307 wrap_task(std::forward<F>(function)));
308 if (handle.is_cancelled())
309 return unexpected(std::make_error_code(std::errc::operation_canceled));
310 return scheduled_task(std::move(handle));
311 });
312 }
313
315 auto
317 {
318 return shutdown(config_.get_shutdown_policy());
319 }
320
325 auto
327 {
328 if (!impl_)
329 return {};
330 return detail::try_result(
331 [&]() -> result<void>
332 {
333 impl_->shutdown(detail::to_native(policy));
334 stopped_.store(true, std::memory_order_release);
335 return {};
336 });
337 }
338
340 [[nodiscard]] auto
341 scheduled_count() const -> std::size_t
342 {
343 return impl_ ? impl_->scheduled_count() : 0;
344 }
345
346private:
347 void
348 dispose_impl() noexcept
349 {
350 if (!impl_)
351 return;
352
353 auto const policy = detail::to_native(config_.get_shutdown_policy());
354 if (!impl_->is_current_context())
355 {
356 try
357 {
358 impl_->shutdown(policy);
359 }
360 catch (...)
361 {
362 }
363 impl_.reset();
364 return;
365 }
366
367 auto* backend = impl_.release();
368 try
369 {
370 std::thread reaper(
371 [backend, policy]() noexcept
372 {
373 std::unique_ptr<detail::scheduled_pool_backend> owner(backend);
374 try
375 {
376 owner->shutdown(policy);
377 }
378 catch (...)
379 {
380 }
381 });
382 reaper.detach();
383 }
384 catch (...)
385 {
386 (void)backend;
387 }
388 }
389
390 template <typename F>
391 auto
392 wrap_task(F&& function)
393 {
394 using function_type = std::decay_t<F>;
395 auto callback = config_.get_error_callback();
396 return [function = function_type(std::forward<F>(function)), callback = std::move(callback)]() mutable
397 {
398 try
399 {
400 std::invoke(function);
401 }
402 catch (...)
403 {
404 if (callback)
405 {
406 try
407 {
408 callback(task_error::capture());
409 }
410 catch (...)
411 {
412 }
413 }
414 }
415 };
416 }
417
418 scheduled_pool_config config_{};
419 std::unique_ptr<detail::scheduled_pool_backend> impl_;
420 std::atomic<bool> stopped_{ false };
421};
422
423} // namespace threadschedule
Builder-style configuration for scheduled_pool.
auto get_shutdown_policy() const noexcept -> shutdown_policy
Return configured shutdown policy.
auto get_registration() const noexcept -> worker_registration
Return configured worker registration mode.
auto get_worker_config() const noexcept -> thread_config const &
Return worker-thread configuration.
auto get_error_callback() const noexcept -> error_callback const &
Return configured asynchronous error callback.
auto get_scheduler_config() const noexcept -> thread_config const &
Return scheduler-thread configuration.
auto get_worker_count() const noexcept -> worker_count
Return configured worker count.
auto set_worker_count(worker_count value) noexcept -> scheduled_pool_config &
Set number of worker threads executing scheduled callbacks.
auto set_shutdown_policy(shutdown_policy value) noexcept -> scheduled_pool_config &
Set shutdown behavior for pending scheduled tasks.
auto set_scheduler_config(thread_config value) -> scheduled_pool_config &
Set configuration applied to the scheduler coordination thread.
auto set_error_callback(error_callback value) -> scheduled_pool_config &
Set callback invoked when scheduled callbacks throw.
auto set_worker_config(thread_config value) -> scheduled_pool_config &
Set configuration applied to worker threads.
auto set_registration(worker_registration value) noexcept -> scheduled_pool_config &
Configure registry registration behavior for scheduler workers.
Scheduler that executes delayed and periodic tasks on worker threads.
scheduled_pool(scheduled_pool &&other) noexcept
auto schedule_after(std::chrono::duration< Rep, Period > delay, F &&function) -> result< scheduled_task >
Schedule a one-shot task to run after a delay.
auto schedule_periodic_after(std::chrono::duration< InitialRep, InitialPeriod > initial_delay, std::chrono::duration< IntervalRep, IntervalPeriod > interval, F &&function) -> result< scheduled_task >
Schedule a periodic task with initial delay.
scheduled_pool()
Construct with default scheduler configuration.
auto scheduled_count() const -> std::size_t
Return count of tasks currently tracked by the scheduler backend.
auto shutdown() -> result< void >
Shutdown using configured shutdown policy.
scheduled_pool(scheduled_pool const &)=delete
static auto create(scheduled_pool_config config={}) -> result< scheduled_pool >
Create a scheduled pool without exceptions.
auto schedule_periodic(std::chrono::duration< Rep, Period > interval, F &&function) -> result< scheduled_task >
Schedule a periodic task.
auto shutdown(shutdown_policy policy) -> result< void >
Shutdown with explicit policy.
~scheduled_pool()
Shutdown scheduler and workers according to configured policy.
scheduled_pool(scheduled_pool_config config)
Construct from full configuration.
scheduled_pool(worker_count count)
Construct with explicit worker count.
auto operator=(scheduled_pool const &) -> scheduled_pool &=delete
auto operator=(scheduled_pool &&other) noexcept -> scheduled_pool &
auto schedule_at(std::chrono::steady_clock::time_point time, F &&function) -> result< scheduled_task >
Schedule a one-shot task at an absolute time point.
Portable thread configuration bundle.
Value type for pool worker count.
static constexpr auto automatic() noexcept -> worker_count
Create automatic worker count.
constexpr auto to_native(shutdown_policy policy) noexcept -> shutdown_policy_backend
Definition shutdown.hpp:10
auto has_thread_configuration(thread_config const &config) noexcept -> bool
Definition control.hpp:123
auto try_result(Function &&function) -> decltype(std::forward< Function >(function)())
auto global_registry() -> thread_registry &
shutdown_policy
Defines how pools handle pending work during shutdown.
@ drain
Finish queued work before returning from shutdown.
std::function< void(task_error const &)> error_callback
Callback signature for asynchronous task error reporting.
worker_registration
Controls whether pool workers are registered in a thread registry.
@ disabled
Do not register workers in the global registry.
Cancellable handle returned by scheduled task APIs.
Portable pool shutdown behavior.
static auto capture(std::string description={}) -> task_error
Capture failure context for the current exception state.
Portable task failure reporting.
Portable thread startup and runtime configuration.
Worker-thread count configuration for pool types.
Worker registration behavior for pool-managed threads.