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#include <variant>
14
15// Note on ownership semantics:
16// Since we want to support cancellation at any point in the coroutine stacks,
17// each coroutine is owned by the object responsible for resuming it. If the
18// coroutine is running, it is responsible for it's own lifetime - it has to
19// either suspend and become continuation of some other task thus transferring
20// ownership or destruct itself once completed.
21
22namespace mrs_lib
23{
24
25 template <typename T = void>
26 requires(std::same_as<T, std::remove_cvref_t<T>>)
27 class Task;
28
29 namespace internal
30 {
31
35 template <typename T>
37 {
38 void operator()(std::coroutine_handle<T> handle)
39 {
40 handle.destroy();
41 }
42 using pointer = std::coroutine_handle<T>;
43 };
44
45 template <typename T = void>
46 using OwningCoroutineHandle = std::unique_ptr<std::coroutine_handle<T>, CoroutineDestroyer<T>>;
47
51 template <typename T>
53 {
54 public:
55 DeferredCoroutineDestroyer(std::coroutine_handle<T> handle) : handle_(handle)
56 {
57 }
59 {
60 std::invoke(CoroutineDestroyer<T>{}, handle_);
61 }
66
67 private:
68 std::coroutine_handle<T> handle_;
69 };
70
77 template <typename Derived>
79 {
85 class FinalAwaitable
86 {
87 public:
88 FinalAwaitable() = default;
89 ~FinalAwaitable() = default;
90 FinalAwaitable(const FinalAwaitable&) = delete;
91 FinalAwaitable& operator=(const FinalAwaitable&) = delete;
92 FinalAwaitable(FinalAwaitable&&) = delete;
93 FinalAwaitable& operator=(FinalAwaitable&&) = delete;
94
95 // Always suspend the ending task
96 bool await_ready() noexcept;
97
98 // SYMMETRIC TRANSFER IS BROKEN IN GCC and can result in stack
99 // overflow when many tasks complete synchronously.
100 // Because of this problem, the `await_suspend` uses the void signature
101 // and resumes the continuation on a thread-local scheduler as a workaround.
102 void await_suspend(std::coroutine_handle<Derived> task_handle) noexcept;
103
104 // This should be unreachable - ended task should not be resumed
105 void await_resume() noexcept;
106 };
107
108 public:
109 // The task is lazy and will only start when awaited
110 std::suspend_always initial_suspend();
111 // The coroutine will be suspended and the continuation will be resumed
112 FinalAwaitable final_suspend() noexcept;
113
114 void set_continuation(OwningCoroutineHandle<> continuation);
115
116 private:
117 OwningCoroutineHandle<> continuation_{std::noop_coroutine()};
118 };
119
123 template <typename T>
125 {
126 private:
127 // Not enum class to allow usage in functions like std::get
128 enum State : size_t
129 {
130 empty = 0,
131 value = 1,
132 exception = 2,
133 };
134
135 public:
136 constexpr ResultStorage() noexcept : data_()
137 {
138 }
139
145 constexpr void set_value(T&& val) noexcept(std::is_nothrow_move_constructible_v<T>)
146 {
147 assert(data_.index() == State::empty);
148 data_.template emplace<State::value>(std::move(val));
149 }
150
156 void set_exception(std::exception_ptr eptr) noexcept
157 {
158 assert(data_.index() == State::empty || data_.valueless_by_exception());
159 data_.template emplace<State::exception>(std::move(eptr));
160 }
161
170 constexpr T get_value() &&
171 {
172 size_t state = data_.index();
173 if (state == State::exception)
174 {
175 std::rethrow_exception(std::get<State::exception>(data_));
176 }
177 assert(state == State::value);
178 return std::get<State::value>(std::move(data_));
179 }
180
181 private:
182 std::variant<std::monostate, T, std::exception_ptr> data_;
183 };
184
190 template <typename T>
191 class PromiseType : public BasePromiseType<PromiseType<T>>
192 {
193 public:
194 Task<T> get_return_object();
195
196 void return_value(T&& ret_val);
197
198 void unhandled_exception();
199
200 T get_value()
201 {
202 return std::move(result_).get_value();
203 }
204
205 private:
206 ResultStorage<T> result_;
207 };
208
214 template <>
215 class PromiseType<void> : public BasePromiseType<PromiseType<void>>
216 {
217 public:
218 Task<void> get_return_object();
219
220 void return_void();
221
222 void unhandled_exception();
223
224 void get_value()
225 {
226 if (exception_)
227 {
228 std::rethrow_exception(exception_);
229 }
230 }
231
232 private:
233 std::exception_ptr exception_;
234 };
235
242 template <typename T>
244 {
246
247 public:
248 bool await_ready()
249 {
250 return false;
251 }
252
253 std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation)
254 {
255 task_handle_.promise().set_continuation(OwningCoroutineHandle<>(continuation));
256 return task_handle_;
257 }
258
259 T await_resume()
260 {
261 DeferredCoroutineDestroyer destroyer{this->task_handle_};
262 return this->task_handle_.promise().get_value();
263 }
264
265 ~TaskAwaitable() = default;
266 TaskAwaitable(const TaskAwaitable&) = delete;
267 TaskAwaitable& operator=(const TaskAwaitable&) = delete;
268 TaskAwaitable(TaskAwaitable&&) = delete;
269 TaskAwaitable& operator=(TaskAwaitable&&) = delete;
270
271 private:
272 TaskAwaitable(std::coroutine_handle<Promise> task_handle) : task_handle_(task_handle)
273 {
274 }
275
276 std::coroutine_handle<Promise> task_handle_;
277
278 friend class Task<T>;
279 };
280
281 } // namespace internal
282
291 template <typename T>
292 requires(std::same_as<T, std::remove_cvref_t<T>>)
293 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
294 {
295 public:
297
298 ~Task() = default;
299 Task(const Task&) = delete;
300 Task& operator=(const Task&) = delete;
301 Task(Task&&) = delete;
302 Task& operator=(Task&&) = delete;
303
304 friend internal::TaskAwaitable<T> operator co_await(Task task)
305 {
306 return internal::TaskAwaitable<T>(task.coroutine_.release());
307 }
308
309 private:
310 Task(internal::OwningCoroutineHandle<promise_type> coroutine) : coroutine_(std::move(coroutine))
311 {
312 }
313
314 internal::OwningCoroutineHandle<promise_type> coroutine_;
315
316 friend class internal::PromiseType<T>;
317 };
318
319} // namespace mrs_lib
320
321#ifndef MRS_LIB_CORO_TASK_IMPL_HPP_
322#include <mrs_lib/coro/task.impl.hpp> // IWYU pragma: export
323#endif
324
325#endif // MRS_LIB_CORO_TASK_HPP_
Task type for creating coroutines.
Definition task.hpp:294
Base class for the task's promise type.
Definition task.hpp:79
RAII class to destroy a coroutine at the end of a scope.
Definition task.hpp:53
Promise type for non-void task.
Definition task.hpp:192
A variant-like class for storing the result of non-void task.
Definition task.hpp:125
constexpr void set_value(T &&val) noexcept(std::is_nothrow_move_constructible_v< T >)
Store result of task.
Definition task.hpp:145
void set_exception(std::exception_ptr eptr) noexcept
Store exception into the result.
Definition task.hpp:156
constexpr T get_value() &&
Get previously stored result or exception.
Definition task.hpp:170
Awaitable used to co_await other tasks.
Definition task.hpp:244
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:37