20template <
typename...>
struct always_false {
21 static constexpr bool value =
false;
24template <
typename Allocator,
typename... Args>
25struct first_is_not_allocator :
public std::true_type {};
27template <
typename Allocator,
typename Arg,
typename... Args>
28struct first_is_not_allocator<Allocator, Arg, Args...> {
29 static constexpr bool value =
30 !std::is_same_v<std::remove_cvref_t<Arg>, Allocator>;
33template <
typename Promise,
typename Allocator>
34class BindAllocator :
public Promise {
37 template <
typename... Args>
38 requires(first_is_not_allocator<Allocator, Args...>::value)
39 static void *
operator new(
size_t, Args &&...) {
43 static_assert(always_false<Args...>::value,
44 "Invalid arguments for allocator-bound coroutine");
48 template <
typename... Args>
49 static void *
operator new(
size_t size, Allocator &alloc,
const Args &...) {
50 size_t allocator_offset = align_up(size,
alignof(Allocator));
51 size_t total_size = allocator_offset +
sizeof(Allocator);
53 Pointer mem = alloc.allocate(total_size);
55 new (mem + allocator_offset) Allocator(alloc);
57 alloc.deallocate(mem, total_size);
63 void operator delete(
void *ptr,
size_t size)
noexcept {
64 size_t allocator_offset = align_up(size,
alignof(Allocator));
65 size_t total_size = allocator_offset +
sizeof(Allocator);
66 Pointer mem =
static_cast<Pointer
>(ptr);
67 Allocator &alloc = *std::launder(
68 reinterpret_cast<Allocator *
>(mem + allocator_offset));
69 Allocator alloc_copy = std::move(alloc);
71 alloc_copy.deallocate(mem, total_size);
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");
80template <
typename Promise>
81class BindAllocator<Promise, void> :
public Promise {};
83template <
typename Coro>
84class PromiseBase :
public InvokerAdapter<PromiseBase<Coro>, WorkInvoker> {
86 using PromiseType =
typename Coro::promise_type;
89 if (exception_) [[unlikely]] {
91 std::rethrow_exception(exception_);
92 }
catch (
const std::exception &e) {
94 "Unhandled exception in detached coroutine: {}", e.what()));
96 panic_on(
"Unhandled unknown exception in detached coroutine");
101 Coro get_return_object() noexcept {
102 return Coro{std::coroutine_handle<PromiseType>::from_promise(
103 static_cast<PromiseType &
>(*
this))};
106 std::suspend_always initial_suspend() const noexcept {
return {}; }
108 void unhandled_exception() noexcept {
109 exception_ = std::current_exception();
112 struct FinalAwaiter {
113 bool await_ready() const noexcept {
return false; }
115 std::coroutine_handle<>
116 await_suspend(std::coroutine_handle<PromiseType> handle)
noexcept {
117 auto &self = handle.promise();
119 State expected = self.state_.load(std::memory_order_acquire);
122 if (expected == State::Idle) {
123 return self.caller_handle_;
124 }
else if (expected == State::RunningJoinable) {
125 desired = State::Zombie;
126 }
else if (expected == State::RunningDetached ||
127 expected == State::RunningJoining) {
128 desired = State::Finished;
129 }
else [[unlikely]] {
130 panic_on(std::format(
131 "Invalid coroutine state in final_suspend: {}",
132 static_cast<int>(expected)));
134 }
while (!self.state_.compare_exchange_weak(
135 expected, desired, std::memory_order_acq_rel,
136 std::memory_order_acquire));
138 State prev = expected;
139 if (prev == State::RunningDetached) {
141 return std::noop_coroutine();
142 }
else if (prev == State::RunningJoining) {
143 auto *callback = self.callback_;
145 return std::noop_coroutine();
147 assert(prev == State::RunningJoinable);
148 return std::noop_coroutine();
152 void await_resume() const noexcept {}
155 FinalAwaiter final_suspend() const noexcept {
return {}; }
157 template <SenderLike T>
auto await_transform(T &&value) {
158 return detail::as_awaiter(std::forward<T>(value));
161 template <
typename T> T &&await_transform(T &&value) {
162 return std::forward<T>(value);
167 void mark_running() noexcept {
168 state_.store(State::RunningJoinable, std::memory_order_relaxed);
171 void set_caller_handle(std::coroutine_handle<> handle)
noexcept {
172 caller_handle_ = handle;
175 void request_detach() noexcept {
176 State expected = state_.load(std::memory_order_acquire);
179 if (expected == State::RunningJoinable) {
180 desired = State::RunningDetached;
181 }
else if (expected == State::Zombie) {
182 desired = State::Finished;
183 }
else [[unlikely]] {
185 std::format(
"Invalid coroutine state in request_detach: {}",
186 static_cast<int>(expected)));
188 }
while (!state_.compare_exchange_weak(expected, desired,
189 std::memory_order_acq_rel,
190 std::memory_order_acquire));
192 State prev = expected;
193 if (prev == State::Zombie) {
194 auto h = std::coroutine_handle<PromiseType>::from_promise(
195 static_cast<PromiseType &
>(*
this));
200 bool request_join(Invoker *remote_callback)
noexcept {
201 State expected = state_.load(std::memory_order_acquire);
204 if (expected == State::RunningJoinable) {
205 desired = State::RunningJoining;
206 callback_ = remote_callback;
207 }
else if (expected == State::Zombie) {
208 desired = State::Finished;
209 }
else [[unlikely]] {
211 std::format(
"Invalid coroutine state in request_join: {}",
212 static_cast<int>(expected)));
214 }
while (!state_.compare_exchange_weak(expected, desired,
215 std::memory_order_acq_rel,
216 std::memory_order_acquire));
218 State prev = expected;
219 if (prev == State::Zombie) {
222 assert(prev == State::RunningJoinable);
227 std::exception_ptr exception() noexcept {
return std::move(exception_); }
229 void invoke() noexcept {
230 auto h = std::coroutine_handle<PromiseType>::from_promise(
231 static_cast<PromiseType &
>(*
this));
267 enum class State : uint8_t {
275 static_assert(std::atomic<State>::is_always_lock_free);
277 std::atomic<State> state_ = State::Idle;
279 std::coroutine_handle<> caller_handle_ = std::noop_coroutine();
282 std::exception_ptr exception_;
285template <
typename Allocator>
286class Promise<void, Allocator>
287 :
public BindAllocator<PromiseBase<Coro<void, Allocator>>, Allocator> {
289 void return_void() const noexcept {}
292template <
typename T,
typename Allocator>
294 :
public BindAllocator<PromiseBase<Coro<T, Allocator>>, Allocator> {
296 void return_value(T value) { value_ = std::move(value); }
298 T value() {
return std::move(value_.value()); }
301 std::optional<T> value_;
304template <
typename PromiseType>
struct CoroAwaiterBase {
305 bool await_ready() const noexcept {
return false; }
307 std::coroutine_handle<PromiseType>
308 await_suspend(std::coroutine_handle<> caller_handle)
noexcept {
309 handle_.promise().set_caller_handle(caller_handle);
313 std::coroutine_handle<PromiseType> handle_;
316template <
typename T,
typename Allocator>
318 :
public CoroAwaiterBase<typename Coro<T, Allocator>::promise_type> {
319 using Base = CoroAwaiterBase<typename Coro<T, Allocator>::promise_type>;
321 auto exception = Base::handle_.promise().exception();
322 if (exception) [[unlikely]] {
323 Base::handle_.destroy();
324 std::rethrow_exception(exception);
326 T value = Base::handle_.promise().value();
327 Base::handle_.destroy();
332template <
typename Allocator>
333struct CoroAwaiter<void, Allocator>
334 :
public CoroAwaiterBase<typename Coro<void, Allocator>::promise_type> {
335 using Base = CoroAwaiterBase<typename Coro<void, Allocator>::promise_type>;
336 void await_resume() {
337 auto exception = Base::handle_.promise().exception();
338 Base::handle_.destroy();
339 if (exception) [[unlikely]] {
340 std::rethrow_exception(exception);
345template <
typename T,
typename Allocator>
346inline auto Coro<T, Allocator>::operator
co_await()
noexcept {
347 return CoroAwaiter<T, Allocator>{release()};
Polymorphic invocation utilities.
condy::Coro< T, std::pmr::polymorphic_allocator< std::byte > > Coro
Coroutine type using polymorphic allocator.
The main namespace for the Condy library.
Helper functions for composing asynchronous operations.
Internal utility classes and functions used by Condy.