ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
task_group.hpp
Go to the documentation of this file.
1#pragma once
2
14#include "../result.hpp"
15
16#include <exception>
17#include <functional>
18#include <future>
19#include <memory>
20#include <mutex>
21#include <system_error>
22#include <type_traits>
23#include <utility>
24#include <vector>
25
27{
28template <typename Pool, typename = void>
30{
31};
32
33template <typename Pool>
34struct task_group_has_current_worker_query<Pool, std::void_t<decltype(std::declval<Pool const&>().is_current_worker())>>
35 : std::true_type
36{
37};
38
39template <typename Pool>
40[[nodiscard]] auto
41task_group_is_current_worker(Pool const& pool) noexcept -> bool
42{
44 return pool.is_current_worker();
45 return false;
46}
47} // namespace threadschedule::detail
48
50{
51
81template <typename Pool>
83{
84 struct context_token
85 {
86 };
87
88public:
89 explicit task_group(Pool& pool) : pool_(pool), token_(std::make_shared<context_token>()) {}
90
91 task_group(task_group const&) = delete;
92 auto operator=(task_group const&) -> task_group& = delete;
93
95 {
96 try
97 {
98 wait();
99 }
100 catch (...)
101 {
102 }
103 }
104
111 template <typename F>
112 auto
113 submit(F&& f)
114 {
115 using function_type = std::decay_t<F>;
116 auto token = token_;
117 auto grouped = [token, function = function_type(std::forward<F>(f))]() mutable
118 {
119 context_guard guard(current_group_, token.get());
120 std::invoke(std::move(function));
121 };
122
123 if (current_group_ == token.get() || ::threadschedule::detail::task_group_is_current_worker(pool_))
124 return run_inline(std::move(grouped));
125 return track(pool_.submit(std::move(grouped)));
126 }
127
128private:
129 class context_guard
130 {
131 public:
132 context_guard(context_token*& slot, context_token* current) noexcept : slot_(slot), previous_(slot)
133 {
134 slot_ = current;
135 }
136
137 ~context_guard()
138 {
139 slot_ = previous_;
140 }
141
142 context_guard(context_guard const&) = delete;
143 auto operator=(context_guard const&) -> context_guard& = delete;
144
145 private:
146 context_token*& slot_;
147 context_token* previous_;
148 };
149
150 template <typename F>
151 auto
152 run_inline(F&& function) -> result<void>
153 {
154 std::packaged_task<void()> task(std::forward<F>(function));
155 auto future = task.get_future();
156 task();
157 return track(std::move(future));
158 }
159
160 auto
161 track(std::future<void> future) -> result<void>
162 {
163 std::lock_guard<std::mutex> lock(mutex_);
164 futures_.push_back(std::move(future));
165 return {};
166 }
167
168 auto
169 track(result<std::future<void>> submitted) -> result<void>
170 {
171 if (!submitted)
172 return unexpected(submitted.error());
173 return track(std::move(*submitted));
174 }
175
176public:
186 void
188 {
189 if (current_group_ == token_.get())
190 throw_worker_wait_error();
191
192 std::exception_ptr first_error;
193 while (true)
194 {
195 std::vector<std::future<void>> local;
196 {
197 std::lock_guard<std::mutex> lock(mutex_);
198 if (futures_.empty())
199 break;
200 local.swap(futures_);
201 }
202
203 for (auto& future : local)
204 {
205 try
206 {
207 future.get();
208 }
209 catch (...)
210 {
211 if (!first_error)
212 first_error = std::current_exception();
213 }
214 }
215 }
216
217 if (first_error)
218 std::rethrow_exception(first_error);
219 }
220
224 [[nodiscard]] auto
225 pending() const -> size_t
226 {
227 std::lock_guard<std::mutex> lock(mutex_);
228 return futures_.size();
229 }
230
231private:
232 [[noreturn]] static void
233 throw_worker_wait_error()
234 {
235 throw std::system_error(std::make_error_code(std::errc::resource_deadlock_would_occur),
236 "task_group::wait from a tracked pool task");
237 }
238
239 inline static thread_local context_token* current_group_ = nullptr;
240 Pool& pool_;
241 std::shared_ptr<context_token> token_;
242 mutable std::mutex mutex_;
243 std::vector<std::future<void>> futures_;
244};
245
246} // namespace threadschedule::advanced
Scoped task group that ensures all submitted work completes before the group is destroyed.
auto submit(F &&f)
Submit a void() callable to the group.
void wait()
Block until all submitted tasks complete.
auto operator=(task_group const &) -> task_group &=delete
auto pending() const -> size_t
Number of pending (not yet waited) tasks.
task_group(task_group const &)=delete
Aggregates multiple thread_registry_backend instances into a single queryable view.
auto task_group_is_current_worker(Pool const &pool) noexcept -> bool
expected< T, std::error_code > result
Standard result type used by public APIs.
Definition result.hpp:22