mrs_lib
Various reusable classes, functions and utilities for use in MRS projects
Loading...
Searching...
No Matches
task.hpp
1#ifndef MRS_LIB_CORO_TASK_HPP_
2#define MRS_LIB_CORO_TASK_HPP_
3
4#include <cassert>
5#include <concepts>
6#include <coroutine>
7#include <functional>
8#include <memory>
9#include <type_traits>
10#include <utility>
11
12#include <mrs_lib/coro/internal/attributes.hpp>
13
14// Note on ownership semantics:
15// Since we want to support cancellation at any point in the coroutine stacks,
16// each coroutine is owned by the object responsible for resuming it. If the
17// coroutine is running, it is responsible for it's own lifetime - it has to
18// either suspend and become continuation of some other task thus transferring
19// ownership or destruct itself once completed.
20
21namespace mrs_lib
22{
23
24 template <typename T = void>
25 requires(std::same_as<T, std::remove_cvref_t<T>>)
26 class Task;
27
28 namespace internal
29 {
30
34 template <typename T>
36 {
37 void operator()(std::coroutine_handle<T> handle)
38 {
39 handle.destroy();
40 }
41 using pointer = std::coroutine_handle<T>;
42 };
43
44 template <typename T = void>
45 using OwningCoroutineHandle = std::unique_ptr<std::coroutine_handle<T>, CoroutineDestroyer<T>>;
46
50 template <typename T>
52 {
53 public:
54 DeferredCoroutineDestroyer(std::coroutine_handle<T> handle) : handle_(handle)
55 {
56 }
58 {
59 std::invoke(CoroutineDestroyer<T>{}, handle_);
60 }
65
66 private:
67 std::coroutine_handle<T> handle_;
68 };
69
76 template <typename Derived>
78 {
84 class FinalAwaitable
85 {
86 public:
87 FinalAwaitable() = default;
88 ~FinalAwaitable() = default;
89 FinalAwaitable(const FinalAwaitable&) = delete;
90 FinalAwaitable& operator=(const FinalAwaitable&) = delete;
91 FinalAwaitable(FinalAwaitable&&) = delete;
92 FinalAwaitable& operator=(FinalAwaitable&&) = delete;
93
94 // Always suspend the ending task
95 bool await_ready() noexcept;
96
97 // SYMMETRIC TRANSFER IS BROKEN IN GCC and can result in stack
98 // overflow when many tasks complete synchronously.
99 // Because of this problem, the `await_suspend` uses the void signature
100 // and resumes the continuation on a thread-local scheduler as a workaround.
101 void await_suspend(std::coroutine_handle<Derived> task_handle) noexcept;
102
103 // This should be unreachable - ended task should not be resumed
104 void await_resume() noexcept;
105 };
106
107 public:
108 // The task is lazy and will only start when awaited
109 std::suspend_always initial_suspend();
110 // The coroutine will be suspended and the continuation will be resumed
111 FinalAwaitable final_suspend() noexcept;
112
113 void set_continuation(OwningCoroutineHandle<> continuation);
114
115 private:
116 OwningCoroutineHandle<> continuation_{std::noop_coroutine()};
117 };
118
122 template <typename T>
124 {
125 private:
126 enum class State
127 {
128 empty,
129 value,
130 exception,
131 };
132
133 public:
134 constexpr ResultStorage() noexcept : state_(State::empty)
135 {
136 }
137
143 constexpr void set_value(T&& val) noexcept(std::is_nothrow_move_constructible_v<T>)
144 {
145 assert(state_ == State::empty);
146 std::construct_at(&value_, std::move(val));
147 state_ = State::value;
148 }
149
155 void set_exception(std::exception_ptr eptr) noexcept
156 {
157 assert(state_ == State::empty);
158 std::construct_at(&exception_, std::move(eptr));
159 state_ = State::exception;
160 }
161
170 constexpr T get_value() &&
171 {
172 if (state_ == State::exception)
173 {
174 std::rethrow_exception(exception_);
175 }
176 assert(state_ == State::value);
177 return std::move(value_);
178 }
179
180 constexpr ~ResultStorage()
181 {
182 switch (state_)
183 {
184 case State::empty:
185 break;
186 case State::value:
187 std::destroy_at(&value_);
188 break;
189 case State::exception:
190 std::destroy_at(&exception_);
191 break;
192 }
193 }
194
195 private:
196 union
197 {
198 T value_;
199 std::exception_ptr exception_;
200 };
201 State state_;
202 };
203
209 template <typename T>
210 class PromiseType : public BasePromiseType<PromiseType<T>>
211 {
212 public:
213 Task<T> get_return_object();
214
215 void return_value(T&& ret_val);
216
217 void unhandled_exception();
218
219 T get_value()
220 {
221 return std::move(result_).get_value();
222 }
223
224 private:
225 ResultStorage<T> result_;
226 };
227
233 template <>
234 class PromiseType<void> : public BasePromiseType<PromiseType<void>>
235 {
236 public:
237 Task<void> get_return_object();
238
239 void return_void();
240
241 void unhandled_exception();
242
243 void get_value()
244 {
245 if (exception_)
246 {
247 std::rethrow_exception(exception_);
248 }
249 }
250
251 private:
252 std::exception_ptr exception_;
253 };
254
261 template <typename T>
263 {
265
266 public:
267 bool await_ready()
268 {
269 return false;
270 }
271
272 std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation)
273 {
274 task_handle_.promise().set_continuation(OwningCoroutineHandle<>(continuation));
275 return task_handle_;
276 }
277
278 T await_resume()
279 {
280 DeferredCoroutineDestroyer destroyer{this->task_handle_};
281 return this->task_handle_.promise().get_value();
282 }
283
284 ~TaskAwaitable() = default;
285 TaskAwaitable(const TaskAwaitable&) = delete;
286 TaskAwaitable& operator=(const TaskAwaitable&) = delete;
287 TaskAwaitable(TaskAwaitable&&) = delete;
288 TaskAwaitable& operator=(TaskAwaitable&&) = delete;
289
290 private:
291 TaskAwaitable(std::coroutine_handle<Promise> task_handle) : task_handle_(task_handle)
292 {
293 }
294
295 std::coroutine_handle<Promise> task_handle_;
296
297 friend class Task<T>;
298 };
299
300 } // namespace internal
301
310 template <typename T>
311 requires(std::same_as<T, std::remove_cvref_t<T>>)
312 class [[nodiscard("Task is lazy and does not run until `co_await`ed.")]] MRS_LIB_INTERNAL_CORO_RETURN_TYPE MRS_LIB_INTERNAL_CORO_LIFETIMEBOUND Task
313 {
314 public:
316
317 ~Task() = default;
318 Task(const Task&) = delete;
319 Task& operator=(const Task&) = delete;
320 Task(Task&&) = delete;
321 Task& operator=(Task&&) = delete;
322
323 friend internal::TaskAwaitable<T> operator co_await(Task task)
324 {
325 return internal::TaskAwaitable<T>(task.coroutine_.release());
326 }
327
328 private:
329 Task(internal::OwningCoroutineHandle<promise_type> coroutine) : coroutine_(std::move(coroutine))
330 {
331 }
332
333 internal::OwningCoroutineHandle<promise_type> coroutine_;
334
335 friend class internal::PromiseType<T>;
336 };
337
338} // namespace mrs_lib
339
340#ifndef MRS_LIB_CORO_TASK_IMPL_HPP_
341#include <mrs_lib/coro/task.impl.hpp> // IWYU pragma: export
342#endif
343
344#endif // MRS_LIB_CORO_TASK_HPP_
Task type for creating coroutines.
Definition task.hpp:313
Base class for the task's promise type.
Definition task.hpp:78
RAII class to destroy a coroutine at the end of a scope.
Definition task.hpp:52
Promise type for non-void task.
Definition task.hpp:211
A variant-like class for storing the result of non-void task.
Definition task.hpp:124
constexpr void set_value(T &&val) noexcept(std::is_nothrow_move_constructible_v< T >)
Store result of task.
Definition task.hpp:143
void set_exception(std::exception_ptr eptr) noexcept
Store exception into the result.
Definition task.hpp:155
constexpr T get_value() &&
Get previously stored result or exception.
Definition task.hpp:170
Awaitable used to co_await other tasks.
Definition task.hpp:263
All mrs_lib functions, classes, variables and definitions are contained in this namespace.
Definition attitude_converter.h:24
Deleter for std::unique_ptr that stores a coroutine handle.
Definition task.hpp:36