#ifndef POSTING_PROMISE_H #define POSTING_PROMISE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sscl::co { template class Invoker; template struct PostingPromise : public PromiseChainLink { struct PostBackStatus { struct CalleeFlowExecutor; struct CallerFlowExecutor; friend struct CalleeFlowExecutor; friend struct CallerFlowExecutor; explicit PostBackStatus(PostingPromise &calleePromiseIn) noexcept : calleePromise(calleePromiseIn) {} void reset() noexcept { sscl::SpinLock::Guard guard(lock); callerHasSetCallerSchedHandle = false; calleeIsReadyToPostBack = false; } struct FlowExecutor { explicit FlowExecutor(PostBackStatus &parentIn) noexcept : parent(parentIn) {} PostBackStatus &parent; }; struct CalleeFlowExecutor : public FlowExecutor { explicit CalleeFlowExecutor(PostBackStatus &parentIn) noexcept : FlowExecutor(parentIn) {} void operator()() noexcept { sscl::SpinLock::Guard guard(this->parent.lock); this->parent.calleeIsReadyToPostBack = true; if (this->parent.callerHasSetCallerSchedHandle) { boost::asio::post( this->parent.calleePromise.callerIoContext, this->parent.calleePromise.callerSchedHandle); } } }; struct CallerFlowExecutor : public FlowExecutor { explicit CallerFlowExecutor(PostBackStatus &parentIn) noexcept : FlowExecutor(parentIn) {} bool operator()() noexcept { sscl::SpinLock::Guard guard(this->parent.lock); this->parent.callerHasSetCallerSchedHandle = true; if (this->parent.calleeIsReadyToPostBack) { return false; } return true; } }; CalleeFlowExecutor getCalleeFlowExecutor() noexcept { return CalleeFlowExecutor(*this); } CallerFlowExecutor getCallerFlowExecutor() noexcept { return CallerFlowExecutor(*this); } PostingPromise &calleePromise; private: sscl::SpinLock lock; bool callerHasSetCallerSchedHandle = false; bool calleeIsReadyToPostBack = false; }; /** Post-to must run from this awaiter's await_suspend, not synchronously inside * promise.initial_suspend() before it returns: the implementation's hidden coroutine * state (async segment / suspend index used on the next resume()) is only updated * after initial_suspend has finished returning its awaiter. Posting the handle too * early lets the callee resume before that update and re-enter initial_suspend from * the start, duplicating the post. See docs/prompts/post-to-and-back-in-invokables.md. */ struct InitialSuspendPostingInvoker : public std::suspend_always { InitialSuspendPostingInvoker( boost::asio::io_context &targetIoContextIn, std::coroutine_handle<> targetSchedHandleIn) noexcept : targetIoContext(targetIoContextIn), targetSchedHandle(targetSchedHandleIn) {} bool await_suspend(std::coroutine_handle<> const) noexcept { boost::asio::post(targetIoContext, targetSchedHandle); return true; } boost::asio::io_context &targetIoContext; std::coroutine_handle<> targetSchedHandle; }; /** Post-back (non-viral completion post; viral CalleeFlowExecutor) must run from this * awaiter's await_suspend, not synchronously inside promise.final_suspend() before it * returns: the hidden coroutine segment index in the coroutine state is only advanced * after final_suspend exits. Doing that work inside final_suspend's body risks the same * kind of ordering bug as initial_suspend—resume observing the wrong segment. See * docs/prompts/post-to-and-back-in-invokables.md. */ struct FinalSuspendPostingInvoker : public std::suspend_always { explicit FinalSuspendPostingInvoker(PostingPromise &calleePromiseIn) noexcept : calleePromise(calleePromiseIn) {} bool await_suspend(std::coroutine_handle<> const) noexcept { if (calleePromise.callerLambda) { #ifdef CONFIG_LIBSSCL_DEBUG_CO std::cout << "final_suspend" << ": " << std::this_thread::get_id() << " Non-viral: posting callerLambda completion to callerIoContext.\n"; #endif auto callerLambda = std::move(calleePromise.callerLambda); boost::asio::post( calleePromise.callerIoContext, [callerLambda = std::move(callerLambda)]() mutable { callerLambda(); }); } else { #ifdef CONFIG_LIBSSCL_DEBUG_CO std::cout << "final_suspend" << ": " << std::this_thread::get_id() << " Viral: running CalleeFlowExecutor.\n"; #endif calleePromise.postBackStatus.getCalleeFlowExecutor()(); } return true; } PostingPromise &calleePromise; }; PostingPromise() noexcept : returnValues(), postBackStatus(*this) {} /** Non-viral entry: post-TO uses ThreadTag default (via TaggedPostingPromise). */ template PostingPromise( std::exception_ptr &_callerExceptionPtr, std::function _callerLambda, TailArgs &&...) noexcept : returnValues(_callerExceptionPtr), callerLambda(std::move(_callerLambda)), postBackStatus(*this) {} /** Non-viral entry with explicit post-TO target. */ template PostingPromise( ExplicitPostTarget _calleePostTarget, std::exception_ptr &_callerExceptionPtr, std::function _callerLambda, TailArgs &&...) noexcept : returnValues(_callerExceptionPtr), callerLambda(std::move(_callerLambda)), calleePostTarget(std::move(_calleePostTarget)), postBackStatus(*this) {} /** Viral / free-function entry with explicit post-TO target. */ template PostingPromise( ExplicitPostTarget _calleePostTarget, TailArgs &&...) noexcept : returnValues(), calleePostTarget(std::move(_calleePostTarget)), postBackStatus(*this) {} /** Viral / free-function entry: post-TO uses ThreadTag default. */ template requires (!is_explicit_post_target_v>) PostingPromise(FirstArg &&, TailArgs &&...) noexcept : PostingPromise() {} /** Member non-viral: peel implicit object parameter. */ template requires ( !std::same_as, std::exception_ptr> && !is_explicit_post_target_v>) PostingPromise( ObjectArg &&, std::exception_ptr &_callerExceptionPtr, std::function _callerLambda, TailArgs &&...) noexcept : PostingPromise( _callerExceptionPtr, std::move(_callerLambda)) {} /** Member non-viral with explicit post-TO target. */ template requires ( !std::same_as, std::exception_ptr> && !is_explicit_post_target_v>) PostingPromise( ObjectArg &&, ExplicitPostTarget _calleePostTarget, std::exception_ptr &_callerExceptionPtr, std::function _callerLambda, TailArgs &&...) noexcept : PostingPromise( std::move(_calleePostTarget), _callerExceptionPtr, std::move(_callerLambda)) {} /** Member viral with explicit post-TO target. */ template requires ( !std::same_as, std::exception_ptr> && !is_explicit_post_target_v>) PostingPromise( ObjectArg &&, ExplicitPostTarget _calleePostTarget, TailArgs &&...) noexcept : PostingPromise(std::move(_calleePostTarget)) {} /** Member viral: peel implicit object parameter. */ template requires ( !std::same_as, std::exception_ptr> && !is_explicit_post_target_v> && !is_explicit_post_target_v>) PostingPromise( ObjectArg &&, FirstArg &&, TailArgs &&...) noexcept : PostingPromise() {} ~PostingPromise() noexcept { #ifdef CONFIG_LIBSSCL_DEBUG_CO std::cout << __func__ << ": " << std::this_thread::get_id() << " Destructing.\n"; #endif } void unhandled_exception() noexcept { returnValues.myExceptionPtr = std::current_exception(); } void removeAcquiredLock(CoQutex &coQutex) noexcept override { eraseFirstMatchingAcquiredLock(coQutex); } const PromiseChainLink *callerPromiseChainLink() const noexcept override { return callerChainLink; } PromiseChainLink *callerPromiseChainLink() noexcept override { return callerChainLink; } /** Non-viral: post completion lambda to callerIoContext from this thread. * Viral: run CalleeFlowExecutor (handshake flags); caller may post caller resume * later via CallerFlowExecutor. See docs/caller-posts-to-own-io-context.md. * Work runs in FinalSuspendPostingInvoker::await_suspend after the suspend point * advances (see docs/prompts/post-to-and-back-in-invokables.md). */ auto final_suspend() noexcept { return FinalSuspendPostingInvoker(*this); } ReturnValues returnValues; std::function callerLambda; boost::asio::io_context &callerIoContext = sscl::ComponentThread::getSelf()->getIoContext(); std::optional calleePostTarget; std::coroutine_handle<> selfSchedHandle; std::coroutine_handle callerSchedHandle; PromiseChainLink *callerChainLink = nullptr; PostBackStatus postBackStatus; protected: void setSelfSchedHandle(std::coroutine_handle<> schedHandle) noexcept { selfSchedHandle = schedHandle; } void setCallerPromiseChainLink(PromiseChainLink *chainLink) noexcept { callerChainLink = chainLink; } template friend class Invoker; }; template struct TaggedPostingPromise : public PostingPromise, public PromiseReturnOps, T> { using PostingPromise::PostingPromise; auto initial_suspend() noexcept { #ifdef CONFIG_LIBSSCL_DEBUG_CO std::cout << __func__ << ": " << std::this_thread::get_id() << " About to post selfSchedHandle to " << typeid(ThreadTag).name() << ".\n"; std::cout << __func__ << ": " << std::this_thread::get_id() << " Returning InitialSuspendPostingInvoker.\n"; #endif boost::asio::io_context &postToIoContext = this->calleePostTarget ? this->calleePostTarget->ioContext : ThreadTag::io_context(); return typename PostingPromise::InitialSuspendPostingInvoker( postToIoContext, this->selfSchedHandle); } }; } // namespace sscl::co #endif // POSTING_PROMISE_H