5#include <core/async/Awaitable.hpp>
6#include <core/async/Cancellation.hpp>
7#include <core/async/ExecutorContext.hpp>
8#include <core/async/ParkedWork.hpp>
9#include <core/async/StopToken.hpp>
18#include "../callback_scope.hpp"
19#include "../executor.hpp"
28namespace morph::async::detail {
31struct CompletionState;
53class CompletionAwaiter {
54 enum class Outcome : std::uint8_t { Pending, Settled, Cancelled };
61 void operator()() const noexcept { shared->onStop(); }
65 std::atomic<Outcome> outcome{Outcome::Pending};
68 std::atomic<bool> armed{
false};
70 std::atomic<bool> claimed{
false};
71 std::optional<T> value;
72 std::exception_ptr error;
73 std::coroutine_handle<> continuation;
76 ::core::async::ResumeTarget context;
77 ::morph::exec::IExecutor* fallback =
nullptr;
79 std::optional<::core::async::StopCallback<OnStop>> stopCallback;
83 bool decide(Outcome outcome_)
noexcept {
84 auto expected = Outcome::Pending;
85 return outcome.compare_exchange_strong(expected, outcome_, std::memory_order_seq_cst);
90 void resumeSettled() {
92 context.submit(::core::async::ParkedWork{.resume = continuation});
94 continuation.resume();
103 void resumeCancelled() {
105 context.submit(::core::async::ParkedWork{.resume = continuation});
107 fallback->post([handle = continuation] { handle.resume(); });
111 void onStop() noexcept {
112 if (!decide(Outcome::Cancelled)) {
116 if (armed.load(std::memory_order_seq_cst) && !claimed.exchange(
true, std::memory_order_acq_rel)) {
130 explicit CompletionAwaiter(std::shared_ptr<CompletionState<T>> state)
131 : _state{std::move(state)}, _shared{std::make_shared<Shared>()} {}
135 ~CompletionAwaiter() {
136 _shared->stopCallback.reset();
137 _shared->scope.requestStop();
140 CompletionAwaiter(
const CompletionAwaiter&) =
delete;
141 CompletionAwaiter& operator=(
const CompletionAwaiter&) =
delete;
142 CompletionAwaiter(CompletionAwaiter&&) =
delete;
143 CompletionAwaiter& operator=(CompletionAwaiter&&) =
delete;
147 [[nodiscard]]
constexpr bool await_ready() const noexcept {
return false; }
153 template <
typename Promise>
155 bool await_suspend(std::coroutine_handle<Promise> awaiting) {
156 auto shared = _shared;
158 if (state ==
nullptr || state->cbExec ==
nullptr) {
159 shared->error = std::make_exception_ptr(std::logic_error{
160 state ==
nullptr ?
"co_await on an empty morph::async::Completion (default-constructed or moved-from)"
161 :
"co_await on a morph::async::Completion with no callback executor to resume on"});
162 shared->outcome.store(Outcome::Settled);
165 shared->continuation = awaiting;
166 shared->context = ::core::async::ResumeTarget::current();
167 shared->fallback = state->cbExec;
169 if constexpr (::core::async::HasStopToken<Promise>) {
170 ::core::async::StopToken
const token = awaiting.promise().stopToken();
171 if (token.stop_requested()) {
172 shared->outcome.store(Outcome::Cancelled);
173 stopUnattached(state, token);
176 if (token.stop_possible()) {
177 shared->stopCallback.emplace(token, OnStop{shared.get()});
178 shared->armed.store(
true, std::memory_order_seq_cst);
179 if (shared->outcome.load(std::memory_order_seq_cst) == Outcome::Cancelled) {
182 stopUnattached(state, token);
183 return shared->claimed.exchange(
true, std::memory_order_acq_rel);
189 if (::morph::exec::runningOn(*state->cbExec)) {
190 attach(*state, shared);
195 state->cbExec->post([state, shared] {
197 attach(*state, shared);
199 if (shared->decide(Outcome::Settled)) {
200 shared->error = std::current_exception();
201 shared->resumeSettled();
211 if (shared->claimed.exchange(
true, std::memory_order_acq_rel)) {
212 shared->scope.requestStop();
217 if (!shared->decide(Outcome::Settled) && shared->outcome.load() == Outcome::Settled) {
218 shared->scope.requestStop();
223 shared->stopCallback.reset();
224 shared->scope.requestStop();
235 _shared->stopCallback.reset();
236 if (_shared->outcome.load() == Outcome::Cancelled) {
237 throw ::core::async::OperationCancelled{};
239 if (_shared->error) {
240 std::rethrow_exception(_shared->error);
242 return std::move(*_shared->value);
253 static void attach(CompletionState<T>& state,
const std::shared_ptr<Shared>& shared) {
254 auto const token = shared->scope.token();
255 state.linkStop(token.stopToken());
256 state.attachThen([token, shared](
const T& settled) {
257 if (token.active() && shared->decide(Outcome::Settled)) {
258 shared->value.emplace(settled);
259 shared->resumeSettled();
262 state.attachOnError([token, shared](std::exception_ptr error) {
263 if (token.active() && shared->decide(Outcome::Settled)) {
264 shared->error = std::move(error);
265 shared->resumeSettled();
275 static void stopUnattached(
const std::shared_ptr<CompletionState<T>>& state,
276 const ::core::async::StopToken& stopped)
noexcept {
278 if (::morph::exec::runningOn(*state->cbExec)) {
279 state->linkStop(stopped);
282 state->cbExec->post([state, stopped] { state->linkStop(stopped); });
287 std::shared_ptr<CompletionState<T>> _state;
288 std::shared_ptr<Shared> _shared;