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 "mrs_lib/coro/internal/continuation.hpp"
14#include "mrs_lib/coro/internal/result_storage.hpp"
15#include "mrs_lib/coro/internal/thread_local_continuation_scheduler.hpp"
16
17// Note on ownership semantics:
18// Since we want to support cancellation at any point in the coroutine stacks,
19// each coroutine is owned by the object responsible for resuming it. If the
20// coroutine is running, it is responsible for it's own lifetime - it has to
21// either suspend and become continuation of some other task thus transferring
22// ownership or destruct itself once completed.
23
24namespace mrs_lib::coro
25{
26
27 template <typename T = void>
28 requires(std::same_as<T, std::remove_cvref_t<T>>)
29 class Task;
30
31 namespace internal
32 {
33
37 template <typename T>
39 {
40 void operator()(std::coroutine_handle<T> handle)
41 {
42 handle.destroy();
43 }
44 using pointer = std::coroutine_handle<T>;
45 };
46
47 template <typename T = void>
48 using OwningCoroutineHandle = std::unique_ptr<std::coroutine_handle<T>, CoroutineDestroyer<T>>;
49
53 template <typename T>
55 {
56 public:
57 DeferredCoroutineDestroyer(std::coroutine_handle<T> handle) : handle_(handle)
58 {
59 }
61 {
62 std::invoke(CoroutineDestroyer<T>{}, handle_);
63 }
68
69 private:
70 std::coroutine_handle<T> handle_;
71 };
72
79 template <typename Derived>
81 {
87 class FinalAwaitable
88 {
89 public:
90 FinalAwaitable() = default;
91 ~FinalAwaitable() = default;
92 FinalAwaitable(const FinalAwaitable&) = delete;
93 FinalAwaitable& operator=(const FinalAwaitable&) = delete;
94 FinalAwaitable(FinalAwaitable&&) = delete;
95 FinalAwaitable& operator=(FinalAwaitable&&) = delete;
96
97 // Always suspend the ending task
98 bool await_ready() noexcept;
99
100 // SYMMETRIC TRANSFER IS BROKEN IN GCC and can result in stack
101 // overflow when many tasks complete synchronously.
102 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100897
103 // Because of this problem, the `await_suspend` uses the void signature
104 // and resumes the continuation on a thread-local scheduler as a workaround.
105 void await_suspend(std::coroutine_handle<Derived> task_handle) noexcept;
106
107 // This should be unreachable - ended task should not be resumed
108 void await_resume() noexcept;
109 };
110
111 public:
112 // The task is lazy and will only start when awaited
113 std::suspend_always initial_suspend();
114 // The coroutine will be suspended and the continuation will be resumed
115 FinalAwaitable final_suspend() noexcept;
116
117 void set_continuation(CancellableContinuation continuation);
118
119 CancellableContinuation release_continuation()
120 {
121 return std::exchange(continuation_, {});
122 }
123
124 std::stop_token get_token() const
125 {
126 return continuation_.get_token();
127 }
128
129 private:
130 CancellableContinuation continuation_;
131 };
132
138 template <typename T>
139 class PromiseType : public BasePromiseType<PromiseType<T>>
140 {
141 public:
142 Task<T> get_return_object();
143
144 void return_value(T&& ret_val);
145
146 void unhandled_exception();
147
148 T get_value()
149 {
150 return std::move(result_).get_value();
151 }
152
153 private:
154 ResultStorage<T> result_;
155 };
156
162 template <>
163 class PromiseType<void> : public BasePromiseType<PromiseType<void>>
164 {
165 public:
166 Task<void> get_return_object();
167
168 void return_void();
169
170 void unhandled_exception();
171
172 void get_value()
173 {
174 if (exception_)
175 {
176 std::rethrow_exception(exception_);
177 }
178 }
179
180 private:
181 std::exception_ptr exception_;
182 };
183
184 template <typename T>
186 {
187 static CancellableContinuation release_continuation(std::coroutine_handle<PromiseType<T>> handle)
188 {
189 PromiseType<T>& promise = handle.promise();
190 return promise.release_continuation();
191 };
192
193 static std::stop_token get_token(std::coroutine_handle<PromiseType<T>> handle)
194 {
195 return handle.promise().get_token();
196 }
197 };
198
205 template <typename T>
207 {
209
210 public:
211 bool await_ready()
212 {
213 return false;
214 }
215
216 // SYMMETRIC TRANSFER IS BROKEN IN GCC and can result in stack
217 // overflow when many tasks complete synchronously.
218 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100897
219 // Because of this problem, the `await_suspend` uses the void signature
220 // and resumes the continuation on a thread-local scheduler as a workaround.
221 template <typename CallerPromise>
222 void await_suspend(std::coroutine_handle<CallerPromise> continuation)
223 {
224 task_handle_.promise().set_continuation(CancellableContinuation(continuation));
225 schedule_coroutine_continuation(task_handle_);
226 }
227
228 T await_resume()
229 {
230 DeferredCoroutineDestroyer destroyer{this->task_handle_};
231 return this->task_handle_.promise().get_value();
232 }
233
234 ~TaskAwaitable() = default;
235 TaskAwaitable(const TaskAwaitable&) = delete;
236 TaskAwaitable& operator=(const TaskAwaitable&) = delete;
237 TaskAwaitable(TaskAwaitable&&) = delete;
238 TaskAwaitable& operator=(TaskAwaitable&&) = delete;
239
240 // This type should only be awaitable when directly returned from the
241 // co_await operator of Task. This prevents it from being awaitable when
242 // not returned from co_await operator.
243 void operator co_await() const = delete;
244
245 private:
246 TaskAwaitable(std::coroutine_handle<Promise> task_handle) : task_handle_(task_handle)
247 {
248 }
249
250 std::coroutine_handle<Promise> task_handle_;
251
252 friend class Task<T>;
253 };
254
255 } // namespace internal
256
265 template <typename T>
266 requires(std::same_as<T, std::remove_cvref_t<T>>)
267 class [[nodiscard("Task is lazy and does not run until `co_await`ed.")]] Task
268 {
269 public:
271
272 ~Task() = default;
273 Task(const Task&) = delete;
274 Task& operator=(const Task&) = delete;
275 Task(Task&&) = delete;
276 Task& operator=(Task&&) = delete;
277
278 friend internal::TaskAwaitable<T> operator co_await(Task task)
279 {
280 return internal::TaskAwaitable<T>(task.coroutine_.release());
281 }
282
283 private:
284 explicit Task(internal::OwningCoroutineHandle<promise_type> coroutine) : coroutine_(std::move(coroutine))
285 {
286 }
287
288 internal::OwningCoroutineHandle<promise_type> coroutine_;
289
290 friend class internal::PromiseType<T>;
291 };
292
293} // namespace mrs_lib::coro
294
295namespace mrs_lib
296{
297 // Export mrs_lib::coro::Task directly into mrs_lib namespace since it is
298 // likely to be used often.
299 using coro::Task;
300
301} // namespace mrs_lib
302
303#ifndef MRS_LIB_CORO_TASK_IMPL_HPP_
304#include "mrs_lib/coro/task.impl.hpp" // IWYU pragma: export
305#endif
306
307#endif // MRS_LIB_CORO_TASK_HPP_
Task type for creating coroutines.
Definition task.hpp:268
Base class for the task's promise type.
Definition task.hpp:81
Owning coroutine handle supporting cancellation.
Definition continuation.hpp:54
std::stop_token get_token() const
Get stop token associated with the continuation.
Definition continuation.hpp:142
RAII class to destroy a coroutine at the end of a scope.
Definition task.hpp:55
Promise type for non-void task.
Definition task.hpp:140
A variant-like class for storing the result of non-void task.
Definition result_storage.hpp:18
Awaitable used to co_await other tasks.
Definition task.hpp:207
All mrs_lib functions, classes, variables and definitions are contained in this namespace.
Definition attitude_converter.h:24
Type trait to obtain data necessary for creating CancellableContinuation.
Definition continuation.hpp:37
Deleter for std::unique_ptr that stores a coroutine handle.
Definition task.hpp:39