2026-05-17 16:52:04 -04:00
|
|
|
#ifndef INVOKERS_H
|
|
|
|
|
#define INVOKERS_H
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#include <coroutine>
|
2026-05-17 17:11:43 -04:00
|
|
|
#include <exception>
|
2026-05-17 16:52:04 -04:00
|
|
|
#include <iostream>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
#include <stdexcept>
|
|
|
|
|
#include <thread>
|
|
|
|
|
#include <type_traits>
|
|
|
|
|
|
2026-05-24 04:28:30 -04:00
|
|
|
#include <spinscale/co/invokerBase.h>
|
|
|
|
|
#include <spinscale/co/nonPostingPromise.h>
|
2026-05-17 16:52:04 -04:00
|
|
|
|
|
|
|
|
namespace sscl::co {
|
|
|
|
|
|
|
|
|
|
/** Non-viral coroutine entry that must not be co_awaited: promise is always
|
|
|
|
|
* PostingPromiseTemplate<void> (no return-value path to a caller).
|
|
|
|
|
*
|
|
|
|
|
* The invoker must outlive the callee frame: do not discard the return object
|
2026-05-24 04:32:44 -04:00
|
|
|
* from get_return_object(). ~Invoker destroys the callee frame.
|
2026-05-17 16:52:04 -04:00
|
|
|
*/
|
|
|
|
|
template <template <typename> class PostingPromiseTemplate>
|
2026-05-19 09:57:24 -04:00
|
|
|
struct NonViralPostingInvoker
|
2026-05-17 16:52:04 -04:00
|
|
|
: public PostingInvoker<PostingPromiseTemplate<void>, void>
|
|
|
|
|
{
|
|
|
|
|
struct promise_type
|
|
|
|
|
: public PostingPromiseTemplate<void>
|
|
|
|
|
{
|
|
|
|
|
using PostingPromiseTemplate<void>::PostingPromiseTemplate;
|
|
|
|
|
|
2026-05-19 09:57:24 -04:00
|
|
|
NonViralPostingInvoker<PostingPromiseTemplate> get_return_object()
|
2026-05-17 16:52:04 -04:00
|
|
|
{
|
|
|
|
|
#ifdef CONFIG_LIBSSCL_DEBUG_CO
|
2026-05-19 09:57:24 -04:00
|
|
|
std::cout << __func__ << ": " << std::this_thread::get_id() << " Returning NonViralPostingInvoker.\n";
|
2026-05-17 16:52:04 -04:00
|
|
|
#endif
|
|
|
|
|
if (!this->callerLambda)
|
|
|
|
|
{
|
|
|
|
|
/** EXPLANATION:
|
|
|
|
|
* We require a completion lambda to be provided to the
|
|
|
|
|
* non-viral coroutines, because that's how we internally
|
|
|
|
|
* distinguish between non-viral and viral coroutines.
|
|
|
|
|
*
|
|
|
|
|
* Additionally, non-viral coroutines almost never have a
|
|
|
|
|
* good reason to not have a completion lambda.
|
|
|
|
|
*/
|
|
|
|
|
std::ostringstream oss;
|
|
|
|
|
oss << std::this_thread::get_id()
|
2026-05-24 02:25:04 -04:00
|
|
|
<< ": Missing completion lambda: non-viral coroutines require a completion lambda."
|
|
|
|
|
<< " Promise type=" << typeid(*this).name()
|
|
|
|
|
<< ". This usually means promise construction did not bind the"
|
|
|
|
|
<< " (exception_ptr&, function<void()>, ...) constructor.";
|
2026-05-17 16:52:04 -04:00
|
|
|
throw std::runtime_error(oss.str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->setSelfSchedHandle(
|
|
|
|
|
std::coroutine_handle<promise_type>::from_promise(*this));
|
|
|
|
|
|
2026-05-19 09:57:24 -04:00
|
|
|
return NonViralPostingInvoker<PostingPromiseTemplate>(*this);
|
2026-05-17 16:52:04 -04:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
using PostingInvoker<PostingPromiseTemplate<void>, void>::PostingInvoker;
|
|
|
|
|
|
|
|
|
|
bool await_ready() const noexcept
|
2026-05-17 17:11:43 -04:00
|
|
|
{ std::terminate(); }
|
2026-05-17 16:52:04 -04:00
|
|
|
|
2026-05-19 09:57:24 -04:00
|
|
|
void await_suspend(std::coroutine_handle<NonViralPostingInvoker<PostingPromiseTemplate>>) noexcept
|
2026-05-17 17:11:43 -04:00
|
|
|
{ std::terminate(); }
|
2026-05-17 16:52:04 -04:00
|
|
|
|
|
|
|
|
void await_resume() noexcept
|
2026-05-17 17:11:43 -04:00
|
|
|
{ std::terminate(); }
|
2026-05-17 16:52:04 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** Viral awaitable: promise_type inherits PostingPromiseTemplate<T> (posting
|
|
|
|
|
* target chosen by the posting-promise alias, e.g. BodyPostingPromise<int>).
|
|
|
|
|
*
|
|
|
|
|
* The invoker must outlive the callee frame until results are read.
|
2026-05-24 04:32:44 -04:00
|
|
|
* ~Invoker destroys the callee frame (not await_resume).
|
2026-05-17 16:52:04 -04:00
|
|
|
*/
|
|
|
|
|
template <template <typename> class PostingPromiseTemplate, typename T>
|
2026-05-19 09:57:24 -04:00
|
|
|
struct ViralPostingInvoker
|
2026-05-17 16:52:04 -04:00
|
|
|
: public PostingInvoker<PostingPromiseTemplate<T>, T>
|
|
|
|
|
{
|
|
|
|
|
struct promise_type
|
|
|
|
|
: public PostingPromiseTemplate<T>
|
|
|
|
|
{
|
|
|
|
|
using PostingPromiseTemplate<T>::PostingPromiseTemplate;
|
|
|
|
|
|
2026-05-19 09:57:24 -04:00
|
|
|
ViralPostingInvoker<PostingPromiseTemplate, T> get_return_object() noexcept
|
2026-05-17 16:52:04 -04:00
|
|
|
{
|
|
|
|
|
#ifdef CONFIG_LIBSSCL_DEBUG_CO
|
2026-05-19 09:57:24 -04:00
|
|
|
std::cout << __func__ << ": " << std::this_thread::get_id() << " Returning ViralPostingInvoker.\n";
|
2026-05-17 16:52:04 -04:00
|
|
|
#endif
|
|
|
|
|
this->setSelfSchedHandle(
|
|
|
|
|
std::coroutine_handle<promise_type>::from_promise(*this));
|
|
|
|
|
|
2026-05-19 09:57:24 -04:00
|
|
|
return ViralPostingInvoker<PostingPromiseTemplate, T>(*this);
|
2026-05-17 16:52:04 -04:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
using PostingInvoker<PostingPromiseTemplate<T>, T>::PostingInvoker;
|
|
|
|
|
|
|
|
|
|
bool await_ready() const noexcept
|
|
|
|
|
{
|
|
|
|
|
#ifdef CONFIG_LIBSSCL_DEBUG_CO
|
|
|
|
|
std::cout << __func__ << ": " << std::this_thread::get_id() << " Returning false.\n";
|
|
|
|
|
#endif
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename CallerPromise>
|
|
|
|
|
bool await_suspend(std::coroutine_handle<CallerPromise> callerSchedHandle) noexcept
|
|
|
|
|
{
|
|
|
|
|
static_assert(
|
|
|
|
|
std::is_base_of_v<PromiseChainLink, CallerPromise>,
|
2026-05-19 09:57:24 -04:00
|
|
|
"ViralPostingInvoker caller promise must derive from PromiseChainLink");
|
2026-05-17 16:52:04 -04:00
|
|
|
#ifdef CONFIG_LIBSSCL_DEBUG_CO
|
|
|
|
|
std::cout << __func__ << ": " << std::this_thread::get_id() << " Setting callerSchedHandle.\n";
|
|
|
|
|
#endif
|
|
|
|
|
const bool suspendCaller = this->setCallerSchedHandle(callerSchedHandle);
|
|
|
|
|
#ifdef CONFIG_LIBSSCL_DEBUG_CO
|
|
|
|
|
std::cout << __func__ << ": " << std::this_thread::get_id()
|
|
|
|
|
<< " CallerFlowExecutor returned suspend=" << suspendCaller << ".\n";
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/** EXPLANATION:
|
|
|
|
|
* If the callee was ready to post-back, then we don't need to
|
|
|
|
|
* suspend the caller -- so return either false or
|
|
|
|
|
* a symmetric transfer handle to the `callerSchedHandle` we were
|
|
|
|
|
* passed as an argument.
|
|
|
|
|
*
|
|
|
|
|
* If the callee is not ready to post-back, then we need to suspend
|
|
|
|
|
* the caller so that the caller can suspend until the callee posts
|
|
|
|
|
* the callerSchedHandle to the callerIoContext -- so return true
|
|
|
|
|
* or std::noop_coroutine().
|
|
|
|
|
*/
|
|
|
|
|
return suspendCaller;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
T await_resume()
|
|
|
|
|
{
|
|
|
|
|
#ifdef CONFIG_LIBSSCL_DEBUG_CO
|
|
|
|
|
std::cout << __func__ << ": " << std::this_thread::get_id() << " Resumed on caller thread, hopefully.\n";
|
|
|
|
|
#endif
|
|
|
|
|
return PostingInvoker<PostingPromiseTemplate<T>, T>::await_resume();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-19 09:57:24 -04:00
|
|
|
/** Non-viral coroutine entry that must not be co_awaited: runs on the caller
|
|
|
|
|
* thread (initial_suspend is never) and invokes the completion lambda directly
|
|
|
|
|
* from final_suspend (no cross-thread posting).
|
|
|
|
|
*
|
|
|
|
|
* The invoker must outlive the callee frame: do not discard the return object
|
2026-05-24 04:32:44 -04:00
|
|
|
* from get_return_object(). ~Invoker destroys the callee frame.
|
2026-05-19 09:57:24 -04:00
|
|
|
*/
|
|
|
|
|
struct NonViralNonPostingInvoker
|
2026-05-24 02:25:04 -04:00
|
|
|
: public NonPostingInvoker<NonPostingPromise<void>, void>
|
2026-05-19 09:57:24 -04:00
|
|
|
{
|
|
|
|
|
struct promise_type
|
2026-05-24 02:25:04 -04:00
|
|
|
: public NonPostingPromise<void>
|
2026-05-19 09:57:24 -04:00
|
|
|
{
|
2026-05-24 02:25:04 -04:00
|
|
|
using NonPostingPromise<void>::NonPostingPromise;
|
2026-05-19 09:57:24 -04:00
|
|
|
|
|
|
|
|
NonViralNonPostingInvoker get_return_object()
|
|
|
|
|
{
|
|
|
|
|
#ifdef CONFIG_LIBSSCL_DEBUG_CO
|
|
|
|
|
std::cout << __func__ << ": " << std::this_thread::get_id() << " Returning NonViralNonPostingInvoker.\n";
|
|
|
|
|
#endif
|
|
|
|
|
if (!this->callerLambda)
|
|
|
|
|
{
|
|
|
|
|
std::ostringstream oss;
|
|
|
|
|
oss << std::this_thread::get_id()
|
2026-05-24 02:25:04 -04:00
|
|
|
<< ": Missing completion lambda: non-viral coroutines require a completion lambda."
|
|
|
|
|
<< " Promise type=" << typeid(*this).name()
|
|
|
|
|
<< ". This usually means promise construction did not bind the"
|
|
|
|
|
<< " (exception_ptr&, function<void()>, ...) constructor.";
|
2026-05-19 09:57:24 -04:00
|
|
|
throw std::runtime_error(oss.str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->setSelfSchedHandle(
|
|
|
|
|
std::coroutine_handle<promise_type>::from_promise(*this));
|
|
|
|
|
|
|
|
|
|
return NonViralNonPostingInvoker(*this);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-24 02:25:04 -04:00
|
|
|
using NonPostingInvoker<NonPostingPromise<void>, void>::NonPostingInvoker;
|
2026-05-19 09:57:24 -04:00
|
|
|
|
|
|
|
|
bool await_ready() const noexcept
|
|
|
|
|
{ std::terminate(); }
|
|
|
|
|
|
|
|
|
|
void await_suspend(std::coroutine_handle<NonViralNonPostingInvoker>) noexcept
|
|
|
|
|
{ std::terminate(); }
|
|
|
|
|
|
|
|
|
|
void await_resume() noexcept
|
|
|
|
|
{ std::terminate(); }
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-24 02:25:04 -04:00
|
|
|
/** Viral awaitable non-posting coroutine: runs eagerly on the caller thread
|
|
|
|
|
* (initial_suspend is never). Caller resume uses symmetric transfer when the
|
|
|
|
|
* caller has registered before callee completion; otherwise PostBackStatus
|
|
|
|
|
* fast-paths await_resume on co_await.
|
|
|
|
|
*/
|
|
|
|
|
template <typename T = void>
|
|
|
|
|
struct ViralNonPostingInvoker
|
|
|
|
|
: public NonPostingInvoker<NonPostingPromise<T>, T>
|
|
|
|
|
{
|
|
|
|
|
struct promise_type
|
|
|
|
|
: public NonPostingPromise<T>
|
|
|
|
|
{
|
|
|
|
|
using NonPostingPromise<T>::NonPostingPromise;
|
|
|
|
|
|
|
|
|
|
ViralNonPostingInvoker<T> get_return_object() noexcept
|
|
|
|
|
{
|
|
|
|
|
#ifdef CONFIG_LIBSSCL_DEBUG_CO
|
|
|
|
|
std::cout << __func__ << ": " << std::this_thread::get_id()
|
|
|
|
|
<< " Returning ViralNonPostingInvoker.\n";
|
|
|
|
|
#endif
|
|
|
|
|
this->setSelfSchedHandle(
|
|
|
|
|
std::coroutine_handle<promise_type>::from_promise(*this));
|
|
|
|
|
|
|
|
|
|
return ViralNonPostingInvoker<T>(*this);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
using NonPostingInvoker<NonPostingPromise<T>, T>::NonPostingInvoker;
|
|
|
|
|
|
|
|
|
|
bool await_ready() const noexcept
|
|
|
|
|
{ return false; }
|
|
|
|
|
|
|
|
|
|
template <typename CallerPromise>
|
|
|
|
|
bool await_suspend(
|
|
|
|
|
std::coroutine_handle<CallerPromise> callerSchedHandle) noexcept
|
|
|
|
|
{
|
|
|
|
|
static_assert(
|
|
|
|
|
std::is_base_of_v<PromiseChainLink, CallerPromise>,
|
|
|
|
|
"ViralNonPostingInvoker caller promise must derive from "
|
|
|
|
|
"PromiseChainLink");
|
|
|
|
|
#ifdef CONFIG_LIBSSCL_DEBUG_CO
|
|
|
|
|
std::cout << __func__ << ": " << std::this_thread::get_id()
|
|
|
|
|
<< " Setting callerSchedHandle.\n";
|
|
|
|
|
#endif
|
|
|
|
|
const bool suspendCaller =
|
|
|
|
|
this->setCallerSchedHandle(callerSchedHandle);
|
|
|
|
|
#ifdef CONFIG_LIBSSCL_DEBUG_CO
|
|
|
|
|
std::cout << __func__ << ": " << std::this_thread::get_id()
|
|
|
|
|
<< " CallerFlowExecutor returned suspend=" << suspendCaller
|
|
|
|
|
<< ".\n";
|
|
|
|
|
#endif
|
|
|
|
|
return suspendCaller;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto await_resume()
|
|
|
|
|
{
|
|
|
|
|
return NonPostingInvoker<NonPostingPromise<T>, T>::await_resume();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-17 16:52:04 -04:00
|
|
|
} // namespace sscl::co
|
|
|
|
|
|
|
|
|
|
#endif // INVOKERS_H
|