Condy v1.1.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_) {
90 panic_on("Unhandled exception in detached coroutine!!!");
91 }
92 }
93
94 Coro get_return_object() {
95 return Coro{std::coroutine_handle<PromiseType>::from_promise(
96 static_cast<PromiseType &>(*this))};
97 }
98
99 std::suspend_always initial_suspend() noexcept { return {}; }
100
101 void unhandled_exception() { exception_ = std::current_exception(); }
102
103 struct FinalAwaiter {
104 bool await_ready() noexcept { return false; }
105
106 std::coroutine_handle<>
107 await_suspend(std::coroutine_handle<PromiseType> handle) noexcept {
108 auto &self = handle.promise();
109 std::unique_lock lock(self.mutex_);
110
111 // 1. Detached task, destroy self
112 if (self.auto_destroy_) {
113 assert(self.caller_handle_ == std::noop_coroutine());
114 lock.unlock();
115 handle.destroy();
116 return std::noop_coroutine();
117 }
118
119 // 2. Task awaited by another coroutine, invoke callback
120 if (self.remote_callback_ != nullptr) {
121 auto *callback = self.remote_callback_;
122 assert(self.caller_handle_ == std::noop_coroutine());
123 lock.unlock();
124 (*callback)();
125 return std::noop_coroutine();
126 }
127
128 // 3. Stacked coroutine, or task that has not been awaited yet
129 self.finished_ = true;
130 return self.caller_handle_;
131 }
132
133 void await_resume() noexcept {}
134 };
135
136 FinalAwaiter final_suspend() noexcept { return {}; }
137
138public:
139 void request_detach() noexcept {
140 std::lock_guard lock(mutex_);
141 if (!finished_) {
142 auto_destroy_ = true;
143 } else {
144 // Destroy self immediately
145 auto handle = std::coroutine_handle<PromiseType>::from_promise(
146 static_cast<PromiseType &>(*this));
147 handle.destroy();
148 }
149 }
150
151 bool register_task_await(Invoker *remote_callback) noexcept {
152 std::lock_guard lock(mutex_);
153 if (finished_) {
154 return false; // ready to resume immediately
155 }
156 remote_callback_ = remote_callback;
157 return true;
158 }
159
160 void set_caller_handle(std::coroutine_handle<> handle) noexcept {
161 caller_handle_ = handle;
162 }
163
164 void set_auto_destroy(bool auto_destroy) noexcept {
165 auto_destroy_ = auto_destroy;
166 }
167
168 std::exception_ptr &exception() & noexcept { return exception_; }
169 std::exception_ptr &&exception() && noexcept {
170 return std::move(exception_);
171 }
172
173 void invoke() {
174 auto h = std::coroutine_handle<PromiseType>::from_promise(
175 static_cast<PromiseType &>(*this));
176 h.resume();
177 }
178
179protected:
180 std::mutex mutex_;
181 std::coroutine_handle<> caller_handle_ = std::noop_coroutine();
182 bool auto_destroy_ = true;
183 bool finished_ = false;
184 Invoker *remote_callback_ = nullptr;
185
186 std::exception_ptr exception_;
187};
188
189template <typename Allocator>
190class Promise<void, Allocator>
191 : public BindAllocator<PromiseBase<Coro<void, Allocator>>, Allocator> {
192public:
193 void return_void() noexcept {}
194};
195
196template <typename T, typename Allocator>
197class Promise
198 : public BindAllocator<PromiseBase<Coro<T, Allocator>>, Allocator> {
199public:
200 void return_value(T value) { value_ = std::move(value); }
201
202 T &value() & noexcept { return value_.value(); }
203 T &&value() && noexcept { return std::move(value_).value(); }
204
205private:
206 std::optional<T> value_;
207};
208
209template <typename PromiseType> struct CoroAwaiterBase {
210 bool await_ready() const noexcept { return false; }
211
212 std::coroutine_handle<PromiseType>
213 await_suspend(std::coroutine_handle<> caller_handle) noexcept {
214 handle_.promise().set_auto_destroy(false);
215 handle_.promise().set_caller_handle(caller_handle);
216 return handle_;
217 }
218
219 std::coroutine_handle<PromiseType> handle_;
220};
221
222template <typename T, typename Allocator>
223struct CoroAwaiter
224 : public CoroAwaiterBase<typename Coro<T, Allocator>::promise_type> {
225 using Base = CoroAwaiterBase<typename Coro<T, Allocator>::promise_type>;
226 T await_resume() {
227 auto exception = std::move(Base::handle_.promise()).exception();
228 if (exception) [[unlikely]] {
229 Base::handle_.destroy();
230 std::rethrow_exception(exception);
231 }
232 T value = std::move(Base::handle_.promise()).value();
233 Base::handle_.destroy();
234 return value;
235 }
236};
237
238template <typename Allocator>
239struct CoroAwaiter<void, Allocator>
240 : public CoroAwaiterBase<typename Coro<void, Allocator>::promise_type> {
241 using Base = CoroAwaiterBase<typename Coro<void, Allocator>::promise_type>;
242 void await_resume() {
243 auto exception = std::move(Base::handle_.promise()).exception();
244 Base::handle_.destroy();
245 if (exception) [[unlikely]] {
246 std::rethrow_exception(exception);
247 }
248 }
249};
250
251template <typename T, typename Allocator>
252inline auto Coro<T, Allocator>::operator co_await() noexcept {
253 return CoroAwaiter<T, Allocator>{release()};
254}
255
256} // 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:28
Internal utility classes and functions used by Condy.