6#include <core/async/Cancellation.hpp>
7#include <core/async/ExecutorContext.hpp>
8#include <core/async/StopToken.hpp>
19#include "../attributes.hpp"
20#include "callback_scope.hpp"
23#include "executor.hpp"
26namespace morph::async {
52struct CompletionState : std::enable_shared_from_this<CompletionState<T>> {
53 static_assert(std::move_constructible<T>,
54 "morph::async::Completion<T> requires only that T be move-constructible. Copyability is a "
55 "per-handler obligation: a handler taking `const T&` imposes nothing, a handler taking `T` by "
56 "value requires T to be copyable and is diagnosed where that handler is written.");
58 std::optional<T> value;
59 std::exception_ptr error;
61 std::atomic<bool> settled{
false};
64 std::atomic<bool> ready{
false};
77 std::vector<std::function<void(
const T&)>> onOk;
78 std::vector<std::function<void(std::exception_ptr)>> onErr;
82 std::atomic<bool> onErrAttached{
false};
85 std::atomic<bool> delivered{
false};
89 std::atomic<std::thread::id> presumedOwnerThread;
90 std::atomic<std::thread::id> deliveredOn;
91 ::morph::exec::IExecutor* cbExec =
nullptr;
95 std::shared_ptr<::core::async::StopSource> stopSource;
99 std::vector<std::shared_ptr<void>> stopLinks;
104 std::weak_ptr<::core::async::StopSource> source;
105 void operator()() const noexcept {
106 if (
auto const strong = source.lock()) {
107 static_cast<void>(strong->request_stop());
119 void checkOwner(
char const* site)
noexcept {
120 if (::morph::exec::runningOn(*cbExec)) {
123 if (::core::async::ExecutorScope::innermost() !=
nullptr) {
124 ::morph::exec::detail::noteOwner(site, cbExec->coreExecutor(),
false);
127 auto const self = std::this_thread::get_id();
128 presumedOwnerThread.store(self, std::memory_order_relaxed);
129 if (delivered.load(std::memory_order_acquire) && deliveredOn.load(std::memory_order_relaxed) != self) {
130 ::morph::exec::detail::noteOwner(site, cbExec->coreExecutor(),
false);
139 void linkStop(::core::async::StopToken scopeStop) {
140 if (cbExec ==
nullptr || stopSource ==
nullptr || delivered.load(std::memory_order_relaxed) ||
141 !scopeStop.stop_possible()) {
144 checkOwner(
"Completion::linkStop");
145 stopLinks.push_back(std::make_shared<::core::async::StopCallback<StopRelay>>(
146 std::move(scopeStop), StopRelay{std::weak_ptr<::core::async::StopSource>{stopSource}}));
154 std::weak_ptr<CompletionState> state;
155 std::weak_ptr<::core::async::StopSource> source;
156 void operator()() const noexcept {
157 if (
auto const strong = state.lock()) {
159 strong->setException(std::make_exception_ptr(::core::async::OperationCancelled{}));
164 if (
auto const strong = source.lock()) {
165 static_cast<void>(strong->request_stop());
175 void linkCancel(::core::async::StopToken cancel) {
176 if (cbExec ==
nullptr || delivered.load(std::memory_order_relaxed) || !cancel.stop_possible()) {
179 checkOwner(
"Completion::linkCancel");
180 stopLinks.push_back(std::make_shared<::core::async::StopCallback<CancelRelay>>(
181 std::move(cancel), CancelRelay{
182 .state = this->weak_from_this(),
183 .source = std::weak_ptr<::core::async::StopSource>{stopSource},
187 void setValue(T val) {
188 if (settled.exchange(
true, std::memory_order_acq_rel)) {
198 value.emplace(std::move(val));
200 settled.store(
false, std::memory_order_release);
206 void setException(
const std::exception_ptr& exc) {
207 if (settled.exchange(
true, std::memory_order_acq_rel)) {
224 : std::make_exception_ptr(
225 std::runtime_error{
"completion rejected with no exception (null exception_ptr)"});
229 void attachThen(std::function<
void(
const T&)> handler) {
230 if (cbExec ==
nullptr) {
233 checkOwner(
"Completion::attach");
234 if (!delivered.load(std::memory_order_relaxed)) {
235 onOk.push_back(std::move(handler));
244 cbExec->post([self = this->shared_from_this(), handler = std::move(handler)]() {
246 handler(*
self->value);
248 ::morph::log::logError(
"[completion] then handler threw; continuing with next handler");
253 void attachOnError(std::function<
void(std::exception_ptr)> handler) {
254 if (cbExec ==
nullptr) {
259 checkOwner(
"Completion::attach");
260 onErrAttached.store(
true, std::memory_order_relaxed);
261 if (!delivered.load(std::memory_order_relaxed)) {
262 onErr.push_back(std::move(handler));
268 cbExec->post([handler = std::move(handler), savedErr = error]()
mutable {
272 ::morph::log::logError(
"[completion] onError handler threw; continuing with next handler");
278 if (!ready.load(std::memory_order_acquire) || !error || onErrAttached.load(std::memory_order_relaxed)) {
288 std::rethrow_exception(error);
289 }
catch (
const std::exception& exc) {
290 ::morph::log::logError(
"[orphan] unhandled exception: {}", exc.what());
292 ::morph::log::logError(
"[orphan] unhandled unknown exception");
299 ready.store(
true, std::memory_order_release);
300 if (cbExec !=
nullptr) {
301 cbExec->post([self = this->shared_from_this()] {
self->deliver(); });
308 auto const here = std::this_thread::get_id();
309 deliveredOn.store(here, std::memory_order_relaxed);
310 delivered.store(
true, std::memory_order_release);
313 if (
auto const presumed = presumedOwnerThread.load(std::memory_order_relaxed);
314 presumed != std::thread::id{} && presumed != here) {
315 ::morph::exec::detail::noteOwner(
"Completion::deliver", cbExec->coreExecutor(),
false);
321 auto const links = std::move(stopLinks);
322 auto const okHandlers = std::move(onOk);
323 auto const errHandlers = std::move(onErr);
325 for (
const auto& handler : okHandlers) {
329 ::morph::log::logError(
"[completion] then handler threw; continuing with next handler");
334 for (
const auto& handler : errHandlers) {
338 ::morph::log::logError(
"[completion] onError handler threw; continuing with next handler");
375 ISettleSink() =
default;
376 ISettleSink(
const ISettleSink&) =
delete;
377 ISettleSink(ISettleSink&&) =
delete;
378 ISettleSink& operator=(
const ISettleSink&) =
delete;
379 ISettleSink& operator=(ISettleSink&&) =
delete;
380 virtual ~ISettleSink() =
default;
384 virtual void settleValue(std::shared_ptr<void> value) = 0;
388 virtual void settleException(
const std::exception_ptr& exc) = 0;
398class CompletionSettleSink final :
public ISettleSink {
402 explicit CompletionSettleSink(std::shared_ptr<CompletionState<std::shared_ptr<void>>> state)
403 : _state{std::move(state)} {}
407 void settleValue(std::shared_ptr<void> value)
override { _state->setValue(std::move(value)); }
411 void settleException(
const std::exception_ptr& exc)
override { _state->setException(exc); }
414 std::shared_ptr<CompletionState<std::shared_ptr<void>>> _state;
479 : _state{std::move(statePtr)} {
480 if (execPtr !=
nullptr && !execPtr->
isSerial()) {
485 ::morph::exec::detail::noteOwner(
"Completion: an owner that is not serial", execPtr->coreExecutor(),
488 if (_state !=
nullptr) {
489 _state->cbExec = execPtr;
513 if (_state !=
nullptr) {
514 _state->attachThen(std::move(handler));
530 if (_state !=
nullptr) {
531 _state->attachOnError(std::move(handler));
560 return then(scope.
token(), std::move(handler));
576 if (_state !=
nullptr) {
579 return then(std::function<
void(
const T&)>{token.
guard(std::move(handler))});
597 std::function<
void(std::exception_ptr)> handler) MORPH_LIFETIMEBOUND {
611 if (_state !=
nullptr) {
614 return onError(std::function<
void(std::exception_ptr)>{token.
guard(std::move(handler))});
629 return then(std::move(handler));
640 return onError(std::move(handler));
655 [[nodiscard]] detail::CompletionAwaiter<T>
operator co_await() && {
656 static_assert(std::copy_constructible<T>,
657 "co_await on a morph::async::Completion<T> copies the settled value out of the shared state, "
658 "which other handlers may still read, so T must be copy-constructible.");
659 return detail::CompletionAwaiter<T>{std::move(_state)};
664 [[nodiscard]] std::shared_ptr<detail::CompletionState<T>>
state()
const {
return _state; }
699 if (_state !=
nullptr) {
700 _state->setValue(std::move(val));
714 if (_state !=
nullptr) {
715 _state->setException(exc);
721 explicit Promise(std::shared_ptr<detail::CompletionState<T>>
state) : _state{std::move(
state)} {}
723 std::shared_ptr<detail::CompletionState<T>> _state;
739 auto state = std::make_shared<detail::CompletionState<T>>();
742 return {std::move(completion), std::move(promise)};
746 std::shared_ptr<detail::CompletionState<T>> _state;
constexpr Self self
The dialog or drawer a command list runs in.
Owned lifetime-and-stop gate for asynchronous callbacks — a data member, not a base class.
CallbackToken token() const noexcept
Issues a weak token for the current generation.
Weak, copyable observer of a CallbackScope — the half a callback captures.
auto guard(F &&fn) const
Wraps fn so it runs only while this token is Active.
::core::async::StopToken stopToken() const noexcept
The stop token of the issuing scope's generation: stop is requested on it when the scope is stopped,...
Producer-side handle paired with a Completion<T> by makeSettleable().
void reject(std::exception_ptr exc)
Rejects the paired Completion<T> with exc.
Promise(Promise &&) noexcept=default
Move constructor — transfers ownership of the shared state.
void resolve(T val)
Resolves the paired Completion<T> with val.
Completion & then(CallbackToken token, std::function< void(const T &)> handler)
Registers a success callback gated on an already-issued token.
Completion & then(std::function< void(const T &)> handler)
Registers a success callback.
Completion & thenDetached(std::function< void(const T &)> handler)
Registers a success callback whose lifetime is deliberately unmanaged.
Completion & onError(std::function< void(std::exception_ptr)> handler)
Registers an error callback.
std::shared_ptr< detail::CompletionState< T > > state() const
Returns the underlying shared state (for advanced / internal use).
Completion & then(const CallbackScope &scope, std::function< void(const T &)> handler)
Registers a success callback gated on scope's lifetime and stop state.
Completion & onError(const CallbackScope &scope, std::function< void(std::exception_ptr)> handler)
Registers an error callback gated on scope's lifetime and stop state.
static std::pair< Completion< T >, Promise > makeSettleable(::morph::exec::IExecutor *execPtr)
Constructs a Completion<T>/Promise<T> pair sharing one settleable state.
Completion()=default
Constructs an empty (no-op) completion.
Completion(std::shared_ptr< detail::CompletionState< T > > statePtr, ::morph::exec::IExecutor *execPtr)
Constructs a completion backed by statePtr, delivering callbacks via execPtr.
Completion(Completion &&) noexcept=default
Move constructor — transfers ownership of the underlying state.
Completion & onErrorDetached(std::function< void(std::exception_ptr)> handler)
Registers an error callback whose lifetime is deliberately unmanaged.
Completion & onError(CallbackToken token, std::function< void(std::exception_ptr)> handler)
Registers an error callback gated on an already-issued token.
The awaiter behind Completion<T>::operator co_await.
The check every loop-owned body makes on entry: "am I on my owner?".
Abstract executor interface.
virtual bool isSerial() const noexcept
Whether this executor runs one task at a time, each after the tasks posted before it: a strand,...