Condy v1.1.0
C++ Asynchronous System Call Layer for Linux
Loading...
Searching...
No Matches
task.hpp
Go to the documentation of this file.
1
7
8#pragma once
9
10#include "condy/context.hpp"
11#include "condy/coro.hpp"
12#include "condy/invoker.hpp"
13#include "condy/runtime.hpp"
14#include <coroutine>
15#include <exception>
16#include <future>
17#include <utility>
18
19namespace condy {
20
21template <typename T = void, typename Allocator = void> class TaskBase {
22public:
23 using PromiseType = typename Coro<T, Allocator>::promise_type;
24
25 TaskBase(std::coroutine_handle<PromiseType> h) : handle_(h) {}
26 TaskBase(TaskBase &&other) noexcept
27 : handle_(std::exchange(other.handle_, nullptr)) {}
28
29 TaskBase(const TaskBase &) = delete;
30 TaskBase &operator=(const TaskBase &) = delete;
31 TaskBase &operator=(TaskBase &&other) = delete;
32
33 ~TaskBase() {
34 if (handle_) {
35 panic_on("Task destroyed without being awaited");
36 }
37 }
38
39public:
47 void detach() noexcept {
48 handle_.promise().request_detach();
49 handle_ = nullptr;
50 }
51
61 auto operator co_await() noexcept;
62
63protected:
64 static void wait_inner_(std::coroutine_handle<PromiseType> handle);
65
66protected:
67 std::coroutine_handle<PromiseType> handle_;
68};
69
70template <typename T, typename Allocator>
71void TaskBase<T, Allocator>::wait_inner_(
72 std::coroutine_handle<PromiseType> handle) {
73 if (Context::current().runtime() != nullptr) [[unlikely]] {
74 throw std::logic_error("Sync wait inside runtime");
75 }
76 std::promise<void> prom;
77 auto fut = prom.get_future();
78 struct TaskWaiter : public InvokerAdapter<TaskWaiter> {
79 TaskWaiter(std::promise<void> &p) : prom_(p) {}
80
81 void invoke() { prom_.set_value(); }
82
83 std::promise<void> &prom_;
84 };
85
86 TaskWaiter waiter(prom);
87 if (handle.promise().register_task_await(&waiter)) {
88 // Still not finished, wait
89 fut.get();
90 }
91}
92
106template <typename T = void, typename Allocator = void>
107class [[nodiscard]] Task : public TaskBase<T, Allocator> {
108public:
109 using Base = TaskBase<T, Allocator>;
110 using Base::Base;
111
121 T wait() {
122 auto handle = std::exchange(Base::handle_, nullptr);
123 Base::wait_inner_(handle);
124 auto exception = std::move(handle.promise()).exception();
125 if (exception) [[unlikely]] {
126 handle.destroy();
127 std::rethrow_exception(exception);
128 }
129 T value = std::move(handle.promise()).value();
130 handle.destroy();
131 return std::move(value);
132 }
133};
134
135template <typename Allocator>
136class [[nodiscard]] Task<void, Allocator> : public TaskBase<void, Allocator> {
137public:
138 using Base = TaskBase<void, Allocator>;
139 using Base::Base;
140
148 void wait() {
149 auto handle = std::exchange(Base::handle_, nullptr);
150 Base::wait_inner_(handle);
151 auto exception = std::move(handle.promise()).exception();
152 handle.destroy();
153 if (exception) [[unlikely]] {
154 std::rethrow_exception(exception);
155 }
156 }
157};
158
159template <typename T, typename Allocator>
160struct TaskAwaiterBase : public InvokerAdapter<TaskAwaiterBase<T, Allocator>> {
161 TaskAwaiterBase(
162 std::coroutine_handle<typename Coro<T, Allocator>::promise_type>
163 task_handle,
164 Runtime *runtime)
165 : task_handle_(task_handle), runtime_(runtime) {}
166
167 bool await_ready() const noexcept { return false; }
168
169 template <typename PromiseType>
170 bool
171 await_suspend(std::coroutine_handle<PromiseType> caller_handle) noexcept {
172 Context::current().runtime()->pend_work();
173 assert(runtime_ != nullptr);
174 caller_promise_ = &caller_handle.promise();
175 return task_handle_.promise().register_task_await(this);
176 }
177
178 void invoke() {
179 assert(caller_promise_ != nullptr);
180 runtime_->schedule(caller_promise_);
181 }
182
183 std::coroutine_handle<typename Coro<T, Allocator>::promise_type>
184 task_handle_;
185 Runtime *runtime_ = nullptr;
186 WorkInvoker *caller_promise_ = nullptr;
187};
188
189template <typename T, typename Allocator>
190struct TaskAwaiter : public TaskAwaiterBase<T, Allocator> {
191 using Base = TaskAwaiterBase<T, Allocator>;
192 using Base::Base;
193
194 T await_resume() {
195 Context::current().runtime()->resume_work();
196 auto exception = std::move(Base::task_handle_.promise()).exception();
197 if (exception) [[unlikely]] {
198 Base::task_handle_.destroy();
199 std::rethrow_exception(exception);
200 }
201 T value = std::move(Base::task_handle_.promise()).value();
202 Base::task_handle_.destroy();
203 return value;
204 }
205};
206
207template <typename Allocator>
208struct TaskAwaiter<void, Allocator> : public TaskAwaiterBase<void, Allocator> {
209 using Base = TaskAwaiterBase<void, Allocator>;
210 using Base::Base;
211
212 void await_resume() {
213 Context::current().runtime()->resume_work();
214 auto exception = std::move(Base::task_handle_.promise()).exception();
215 Base::task_handle_.destroy();
216 if (exception) [[unlikely]] {
217 std::rethrow_exception(exception);
218 }
219 }
220};
221
222template <typename T, typename Allocator>
223inline auto TaskBase<T, Allocator>::operator co_await() noexcept {
224 return TaskAwaiter<T, Allocator>(std::exchange(handle_, nullptr),
225 Context::current().runtime());
226}
227
239template <typename T, typename Allocator>
241 auto handle = coro.release();
242 auto &promise = handle.promise();
243 promise.set_auto_destroy(false);
244
245 runtime.schedule(&promise);
246 return {handle};
247}
248
257template <typename T, typename Allocator>
259 auto *runtime = Context::current().runtime();
260 if (runtime == nullptr) [[unlikely]] {
261 throw std::logic_error("No runtime to spawn coroutine task");
262 }
263 return co_spawn(*runtime, std::move(coro));
264}
265
266namespace detail {
267
268struct [[nodiscard]] SwitchAwaiter {
269 bool await_ready() const noexcept { return false; }
270
271 template <typename PromiseType>
272 void await_suspend(std::coroutine_handle<PromiseType> handle) noexcept {
273 runtime_->schedule(&handle.promise());
274 }
275
276 void await_resume() const noexcept {}
277
278 Runtime *runtime_;
279};
280
281} // namespace detail
282
291inline detail::SwitchAwaiter co_switch(Runtime &runtime) { return {&runtime}; }
292
293} // namespace condy
Coroutine type used to define a coroutine function.
Definition coro.hpp:26
The event loop runtime for executing asynchronous.
Definition runtime.hpp:76
Coroutine task that runs concurrently in the runtime.
Definition task.hpp:107
T wait()
Wait synchronously for the task to complete and get the result.
Definition task.hpp:121
Coroutine definitions.
Polymorphic invocation utilities.
The main namespace for the Condy library.
Definition condy.hpp:28
detail::SwitchAwaiter co_switch(Runtime &runtime)
Switch current coroutine task to the given runtime.
Definition task.hpp:291
Task< T, Allocator > co_spawn(Runtime &runtime, Coro< T, Allocator > coro)
Spawn a coroutine as a task in the given runtime.
Definition task.hpp:240
Runtime type for running the io_uring event loop.