Condy v1.6.0
C++ Asynchronous System Call Layer for Linux
Loading...
Searching...
No Matches
coro.inl
Go to the documentation of this file.
1
5
6#pragma once
7
8#include "condy/coro.hpp"
9#include "condy/invoker.hpp"
10#include "condy/utils.hpp"
11#include <coroutine>
12#include <exception>
13#include <mutex>
14#include <optional>
15
16namespace condy {
17
18template <typename...> struct always_false {
19 static constexpr bool value = false;
20};
21
22template <typename Allocator, typename... Args>
23struct first_is_not_allocator : public std::true_type {};
24
25template <typename Allocator, typename Arg, typename... Args>
26struct first_is_not_allocator<Allocator, Arg, Args...> {
27 static constexpr bool value =
28 !std::is_same_v<std::remove_cvref_t<Arg>, Allocator>;
29};
30
31template <typename Promise, typename Allocator>
32class BindAllocator : public Promise {
33public:
34#ifdef __clang__
35 template <typename... Args>
36 requires(first_is_not_allocator<Allocator, Args...>::value)
37 static void *operator new(size_t, Args &&...) {
38 // If user didn't provide a signature like (Allocator&, ...), clang will
39 // fall back to ::new, we don't want that.
40 // https://github.com/llvm/llvm-project/issues/54881
41 static_assert(always_false<Args...>::value,
42 "Invalid arguments for allocator-bound coroutine");
43 }
44#endif
45
46 template <typename... Args>
47 static void *operator new(size_t size, Allocator &alloc, const Args &...) {
48 size_t allocator_offset =
49 (size + alignof(Allocator) - 1) & ~(alignof(Allocator) - 1);
50 size_t total_size = allocator_offset + sizeof(Allocator);
51
52 Pointer mem = alloc.allocate(total_size);
53 try {
54 new (mem + allocator_offset) Allocator(alloc);
55 } catch (...) {
56 alloc.deallocate(mem, total_size);
57 throw;
58 }
59 return mem;
60 }
61
62 void operator delete(void *ptr, size_t size) noexcept {
63 size_t allocator_offset =
64 (size + alignof(Allocator) - 1) & ~(alignof(Allocator) - 1);
65 size_t total_size = allocator_offset + sizeof(Allocator);
66 Pointer mem = static_cast<Pointer>(ptr);
67 Allocator &alloc =
68 *reinterpret_cast<Allocator *>(mem + allocator_offset);
69 Allocator alloc_copy = std::move(alloc);
70 alloc.~Allocator();
71 alloc_copy.deallocate(mem, total_size);
72 }
73
74private:
75 using Pointer = typename std::allocator_traits<Allocator>::pointer;
76 using T = std::remove_pointer_t<Pointer>;
77 static_assert(sizeof(T) == 1, "Allocator pointer must point to byte type");
78};
79
80template <typename Promise>
81class BindAllocator<Promise, void> : public Promise {};
82
83template <typename Coro>
84class PromiseBase : public InvokerAdapter<PromiseBase<Coro>, WorkInvoker> {
85public:
86 using PromiseType = typename Coro::promise_type;
87
88 ~PromiseBase() {
89 if (exception_) [[unlikely]] {
90 try {
91 std::rethrow_exception(exception_);
92 } catch (const std::exception &e) {
93 panic_on(std::format(
94 "Unhandled exception in detached coroutine: {}", e.what()));
95 } catch (...) {
96 panic_on("Unhandled unknown exception in detached coroutine");
97 }
98 }
99 }
100
101 Coro get_return_object() noexcept {
102 return Coro{std::coroutine_handle<PromiseType>::from_promise(
103 static_cast<PromiseType &>(*this))};
104 }
105
106 std::suspend_always initial_suspend() const noexcept { return {}; }
107
108 void unhandled_exception() noexcept {
109 exception_ = std::current_exception();
110 }
111
112 struct FinalAwaiter {
113 bool await_ready() const noexcept { return false; }
114
115 std::coroutine_handle<>
116 await_suspend(std::coroutine_handle<PromiseType> handle) noexcept {
117 auto &self = handle.promise();
118 std::unique_lock lock(self.mutex_);
119
120 // 1. Detached task, destroy self
121 if (self.auto_destroy_) {
122 assert(self.caller_handle_ == std::noop_coroutine());
123 lock.unlock();
124 handle.destroy();
125 return std::noop_coroutine();
126 }
127
128 // 2. Task awaited by another coroutine, invoke callback
129 if (self.remote_callback_ != nullptr) {
130 auto *callback = self.remote_callback_;
131 assert(self.caller_handle_ == std::noop_coroutine());
132 lock.unlock();
133 (*callback)();
134 return std::noop_coroutine();
135 }
136
137 // 3. Stacked coroutine, or task that has not been awaited yet
138 self.finished_ = true;
139 return self.caller_handle_;
140 }
141
142 void await_resume() const noexcept {}
143 };
144
145 FinalAwaiter final_suspend() const noexcept { return {}; }
146
147public:
148 void request_detach() noexcept {
149 std::lock_guard lock(mutex_);
150 if (!finished_) {
151 auto_destroy_ = true;
152 } else {
153 // Destroy self immediately
154 auto handle = std::coroutine_handle<PromiseType>::from_promise(
155 static_cast<PromiseType &>(*this));
156 handle.destroy();
157 }
158 }
159
160 bool register_task_await(Invoker *remote_callback) noexcept {
161 std::lock_guard lock(mutex_);
162 if (finished_) {
163 return false; // ready to resume immediately
164 }
165 remote_callback_ = remote_callback;
166 return true;
167 }
168
169 void set_caller_handle(std::coroutine_handle<> handle) noexcept {
170 caller_handle_ = handle;
171 }
172
173 void set_auto_destroy(bool auto_destroy) noexcept {
174 auto_destroy_ = auto_destroy;
175 }
176
177 std::exception_ptr exception() noexcept { return std::move(exception_); }
178
179 void invoke() noexcept {
180 auto h = std::coroutine_handle<PromiseType>::from_promise(
181 static_cast<PromiseType &>(*this));
182 h.resume();
183 }
184
185protected:
186 AtomicMutex mutex_;
187 bool auto_destroy_ = true;
188 bool finished_ = false;
189 std::coroutine_handle<> caller_handle_ = std::noop_coroutine();
190 Invoker *remote_callback_ = nullptr;
191 std::exception_ptr exception_;
192};
193
194template <typename Allocator>
195class Promise<void, Allocator>
196 : public BindAllocator<PromiseBase<Coro<void, Allocator>>, Allocator> {
197public:
198 void return_void() const noexcept {}
199};
200
201template <typename T, typename Allocator>
202class Promise
203 : public BindAllocator<PromiseBase<Coro<T, Allocator>>, Allocator> {
204public:
205 void return_value(T value) { value_ = std::move(value); }
206
207 T value() { return std::move(value_.value()); }
208
209private:
210 std::optional<T> value_;
211};
212
213template <typename PromiseType> struct CoroAwaiterBase {
214 bool await_ready() const noexcept { return false; }
215
216 std::coroutine_handle<PromiseType>
217 await_suspend(std::coroutine_handle<> caller_handle) noexcept {
218 handle_.promise().set_auto_destroy(false);
219 handle_.promise().set_caller_handle(caller_handle);
220 return handle_;
221 }
222
223 std::coroutine_handle<PromiseType> handle_;
224};
225
226template <typename T, typename Allocator>
227struct CoroAwaiter
228 : public CoroAwaiterBase<typename Coro<T, Allocator>::promise_type> {
229 using Base = CoroAwaiterBase<typename Coro<T, Allocator>::promise_type>;
230 T await_resume() {
231 auto exception = Base::handle_.promise().exception();
232 if (exception) [[unlikely]] {
233 Base::handle_.destroy();
234 std::rethrow_exception(exception);
235 }
236 T value = Base::handle_.promise().value();
237 Base::handle_.destroy();
238 return value;
239 }
240};
241
242template <typename Allocator>
243struct CoroAwaiter<void, Allocator>
244 : public CoroAwaiterBase<typename Coro<void, Allocator>::promise_type> {
245 using Base = CoroAwaiterBase<typename Coro<void, Allocator>::promise_type>;
246 void await_resume() {
247 auto exception = Base::handle_.promise().exception();
248 Base::handle_.destroy();
249 if (exception) [[unlikely]] {
250 std::rethrow_exception(exception);
251 }
252 }
253};
254
255template <typename T, typename Allocator>
256inline auto Coro<T, Allocator>::operator co_await() noexcept {
257 return CoroAwaiter<T, Allocator>{release()};
258}
259
260} // namespace condy
Coroutine definitions.
Polymorphic invocation utilities.
condy::Coro< T, std::pmr::polymorphic_allocator< std::byte > > Coro
Coroutine type using polymorphic allocator.
Definition pmr.hpp:26
The main namespace for the Condy library.
Definition condy.hpp:30
Internal utility classes and functions used by Condy.