diff --git a/include/spinscale/co/invokerBase.h b/include/spinscale/co/invokerBase.h new file mode 100644 index 0000000..3c89be2 --- /dev/null +++ b/include/spinscale/co/invokerBase.h @@ -0,0 +1,196 @@ +#ifndef INVOKER_BASE_H +#define INVOKER_BASE_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace sscl::co { + +template +class PostingInvoker +{ +public: + explicit PostingInvoker(PromiseType &_calleePromise) noexcept + : calleePromise(_calleePromise) + {} + + PostingInvoker(const PostingInvoker &) = delete; + PostingInvoker &operator=(const PostingInvoker &) = delete; + + PostingInvoker(PostingInvoker &&other) noexcept + : calleePromise(other.calleePromise), + ownsFrameDestroy_(std::exchange(other.ownsFrameDestroy_, false)) + {} + + PostingInvoker &operator=(PostingInvoker &&other) = delete; + + ~PostingInvoker() noexcept + { + if (!ownsFrameDestroy_) { return; } + + std::coroutine_handle<> handle = calleePromise.selfSchedHandle; + if (handle) { + handle.destroy(); + } + } + + template + bool setCallerSchedHandle( + std::coroutine_handle callerSchedHandle) noexcept + { + static_assert( + std::is_base_of_v, + "PostingInvoker caller promise must derive from PromiseChainLink"); + + calleePromise.callerSchedHandle = callerSchedHandle; + calleePromise.setCallerPromiseChainLink(&callerSchedHandle.promise()); +#ifdef CONFIG_LIBSSCL_DEBUG_CO + std::cout << __func__ << ": " << std::this_thread::get_id() + << " Done setting callerSchedHandle; running CallerFlowExecutor.\n"; +#endif + return calleePromise.postBackStatus.getCallerFlowExecutor()(); + } + + ReturnValues &completedReturnValues() noexcept + { return calleePromise.returnValues; } + + const ReturnValues &completedReturnValues() const noexcept + { return calleePromise.returnValues; } + + auto await_resume() + { + calleePromise.postBackStatus.reset(); + + ReturnValues &returnValues = calleePromise.returnValues; +#ifdef CONFIG_LIBSSCL_DEBUG_CO + std::cout << __func__ << ": " << std::this_thread::get_id() + << " About to check for and rethrow any exception.\n"; +#endif + + if (returnValues.myExceptionPtr) + { + std::exception_ptr const captured = returnValues.myExceptionPtr; + std::rethrow_exception(captured); + } + if constexpr (!std::is_void_v) + { + T result = std::move(returnValues.myReturnValue); + return result; + } + } + +private: + PromiseType &calleePromise; + + /** EXPLANATION: + * Every live invoker owns destruction of its callee coroutine frame in + * ~PostingInvoker (via calleePromise.selfSchedHandle). + * + * The only time frame destruction is skipped is for a moved-from invoker + * after move construction or move assignment, so we do not double-destroy + * the same handle when get_return_object() returns the invoker by value. + * + * This is not an opt-out for viral vs non-viral callers or for "callee + * still running"; callers must keep the invoker alive until the callee + * frame is no longer needed. + */ + bool ownsFrameDestroy_ = true; +}; + +template +class NonPostingInvoker +{ +public: + explicit NonPostingInvoker(PromiseType &_calleePromise) noexcept + : calleePromise(_calleePromise) + {} + + NonPostingInvoker(const NonPostingInvoker &) = delete; + NonPostingInvoker &operator=(const NonPostingInvoker &) = delete; + + NonPostingInvoker(NonPostingInvoker &&other) noexcept + : calleePromise(other.calleePromise), + ownsFrameDestroy_(std::exchange(other.ownsFrameDestroy_, false)) + {} + + NonPostingInvoker &operator=(NonPostingInvoker &&other) = delete; + + ~NonPostingInvoker() noexcept + { + if (!ownsFrameDestroy_) { return; } + + std::coroutine_handle<> handle = calleePromise.selfSchedHandle; + if (handle) { + handle.destroy(); + } + } + + template + bool setCallerSchedHandle( + std::coroutine_handle callerSchedHandle) noexcept + { + static_assert( + std::is_base_of_v, + "NonPostingInvoker caller promise must derive from PromiseChainLink"); + + calleePromise.callerSchedHandle = callerSchedHandle; + calleePromise.setCallerPromiseChainLink( + &callerSchedHandle.promise()); +#ifdef CONFIG_LIBSSCL_DEBUG_CO + std::cout << __func__ << ": " << std::this_thread::get_id() + << " Done setting callerSchedHandle; running CallerFlowExecutor.\n"; +#endif + return calleePromise.postBackStatus.getCallerFlowExecutor()(); + } + + ReturnValues &completedReturnValues() noexcept + { return calleePromise.returnValues; } + + const ReturnValues &completedReturnValues() const noexcept + { return calleePromise.returnValues; } + + auto await_resume() + { + calleePromise.postBackStatus.reset(); + + ReturnValues &returnValues = calleePromise.returnValues; +#ifdef CONFIG_LIBSSCL_DEBUG_CO + std::cout << __func__ << ": " << std::this_thread::get_id() + << " About to check for and rethrow any exception.\n"; +#endif + + if (returnValues.myExceptionPtr) { + std::exception_ptr const captured = returnValues.myExceptionPtr; + std::rethrow_exception(captured); + } + if constexpr (!std::is_void_v) + { + T result = std::move(returnValues.myReturnValue); + return result; + } + } + +protected: + PromiseType &calleePromise; + +private: + /** Every live invoker owns destruction of its callee coroutine frame in + * ~NonPostingInvoker (via calleePromise.selfSchedHandle). + * + * The only time frame destruction is skipped is for a moved-from invoker + * after move construction, so we do not double-destroy the same handle + * when get_return_object() returns the invoker by value. + */ + bool ownsFrameDestroy_ = true; +}; + +} // namespace sscl::co + +#endif // INVOKER_BASE_H diff --git a/include/spinscale/co/invokers.h b/include/spinscale/co/invokers.h index 5ef65a3..4bf5e03 100644 --- a/include/spinscale/co/invokers.h +++ b/include/spinscale/co/invokers.h @@ -10,8 +10,8 @@ #include #include -#include -#include +#include +#include namespace sscl::co { diff --git a/include/spinscale/co/nonPostingInvoker.h b/include/spinscale/co/nonPostingInvoker.h deleted file mode 100644 index 8f37f20..0000000 --- a/include/spinscale/co/nonPostingInvoker.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef NON_POSTING_INVOKER_H -#define NON_POSTING_INVOKER_H - -#include -#include -#include -#include -#include -#include - -#include - -namespace sscl::co { - -template -class NonPostingInvoker -{ -public: - explicit NonPostingInvoker(PromiseType &_calleePromise) noexcept - : calleePromise(_calleePromise) - {} - - NonPostingInvoker(const NonPostingInvoker &) = delete; - NonPostingInvoker &operator=(const NonPostingInvoker &) = delete; - - NonPostingInvoker(NonPostingInvoker &&other) noexcept - : calleePromise(other.calleePromise), - ownsFrameDestroy_(std::exchange(other.ownsFrameDestroy_, false)) - {} - - NonPostingInvoker &operator=(NonPostingInvoker &&other) = delete; - - ~NonPostingInvoker() noexcept - { - if (!ownsFrameDestroy_) { return; } - - std::coroutine_handle<> handle = calleePromise.selfSchedHandle; - if (handle) { - handle.destroy(); - } - } - - template - bool setCallerSchedHandle( - std::coroutine_handle callerSchedHandle) noexcept - { - static_assert( - std::is_base_of_v, - "NonPostingInvoker caller promise must derive from PromiseChainLink"); - - calleePromise.callerSchedHandle = callerSchedHandle; - calleePromise.setCallerPromiseChainLink( - &callerSchedHandle.promise()); -#ifdef CONFIG_LIBSSCL_DEBUG_CO - std::cout << __func__ << ": " << std::this_thread::get_id() - << " Done setting callerSchedHandle; running CallerFlowExecutor.\n"; -#endif - return calleePromise.postBackStatus.getCallerFlowExecutor()(); - } - - ReturnValues &completedReturnValues() noexcept - { return calleePromise.returnValues; } - - const ReturnValues &completedReturnValues() const noexcept - { return calleePromise.returnValues; } - - auto await_resume() - { - calleePromise.postBackStatus.reset(); - - ReturnValues &returnValues = calleePromise.returnValues; -#ifdef CONFIG_LIBSSCL_DEBUG_CO - std::cout << __func__ << ": " << std::this_thread::get_id() - << " About to check for and rethrow any exception.\n"; -#endif - - if (returnValues.myExceptionPtr) { - std::exception_ptr const captured = returnValues.myExceptionPtr; - std::rethrow_exception(captured); - } - if constexpr (!std::is_void_v) - { - T result = std::move(returnValues.myReturnValue); - return result; - } - } - -protected: - PromiseType &calleePromise; - -private: - /** Every live invoker owns destruction of its callee coroutine frame in - * ~NonPostingInvoker (via calleePromise.selfSchedHandle). - * - * The only time frame destruction is skipped is for a moved-from invoker - * after move construction, so we do not double-destroy the same handle - * when get_return_object() returns the invoker by value. - */ - bool ownsFrameDestroy_ = true; -}; - -} // namespace sscl::co - -#endif // NON_POSTING_INVOKER_H diff --git a/include/spinscale/co/nonPostingPromise.h b/include/spinscale/co/nonPostingPromise.h index c24d818..b255ced 100644 --- a/include/spinscale/co/nonPostingPromise.h +++ b/include/spinscale/co/nonPostingPromise.h @@ -12,14 +12,15 @@ #include #include #include -#include +#include +#include namespace sscl::co { template struct NonPostingPromise : public PromiseChainLink, - public PostingPromiseReturnOps, T> + public PromiseReturnOps, T> { struct PostBackStatus { diff --git a/include/spinscale/co/postingInvoker.h b/include/spinscale/co/postingInvoker.h deleted file mode 100644 index c41a630..0000000 --- a/include/spinscale/co/postingInvoker.h +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef POSTING_INVOKER_H -#define POSTING_INVOKER_H - -#include -#include -#include -#include -#include -#include - -#include - -namespace sscl::co { - -template -class PostingInvoker -{ -public: - explicit PostingInvoker(PromiseType &_calleePromise) noexcept - : calleePromise(_calleePromise) - {} - - PostingInvoker(const PostingInvoker &) = delete; - PostingInvoker &operator=(const PostingInvoker &) = delete; - - PostingInvoker(PostingInvoker &&other) noexcept - : calleePromise(other.calleePromise), - ownsFrameDestroy_(std::exchange(other.ownsFrameDestroy_, false)) - {} - - PostingInvoker &operator=(PostingInvoker &&other) = delete; - - ~PostingInvoker() noexcept - { - if (!ownsFrameDestroy_) { return; } - - std::coroutine_handle<> handle = calleePromise.selfSchedHandle; - if (handle) { - handle.destroy(); - } - } - - template - bool setCallerSchedHandle(std::coroutine_handle callerSchedHandle) noexcept - { - static_assert( - std::is_base_of_v, - "PostingInvoker caller promise must derive from PromiseChainLink"); - - calleePromise.callerSchedHandle = callerSchedHandle; - calleePromise.setCallerPromiseChainLink(&callerSchedHandle.promise()); -#ifdef CONFIG_LIBSSCL_DEBUG_CO - std::cout << __func__ << ": " << std::this_thread::get_id() - << " Done setting callerSchedHandle; running CallerFlowExecutor.\n"; -#endif - return calleePromise.postBackStatus.getCallerFlowExecutor()(); - } - - ReturnValues &completedReturnValues() noexcept - { return calleePromise.returnValues; } - - const ReturnValues &completedReturnValues() const noexcept - { return calleePromise.returnValues; } - - auto await_resume() - { - calleePromise.postBackStatus.reset(); - - ReturnValues &returnValues = calleePromise.returnValues; -#ifdef CONFIG_LIBSSCL_DEBUG_CO - std::cout << __func__ << ": " << std::this_thread::get_id() - << " About to check for and rethrow any exception.\n"; -#endif - - if (returnValues.myExceptionPtr) - { - std::exception_ptr const captured = returnValues.myExceptionPtr; - std::rethrow_exception(captured); - } - if constexpr (!std::is_void_v) - { - T result = std::move(returnValues.myReturnValue); - return result; - } - } - -private: - PromiseType &calleePromise; - - /** EXPLANATION: - * Every live invoker owns destruction of its callee coroutine frame in - * ~PostingInvoker (via calleePromise.selfSchedHandle). - * - * The only time frame destruction is skipped is for a moved-from invoker - * after move construction or move assignment, so we do not double-destroy - * the same handle when get_return_object() returns the invoker by value. - * - * This is not an opt-out for viral vs non-viral callers or for "callee - * still running"; callers must keep the invoker alive until the callee - * frame is no longer needed. - */ - bool ownsFrameDestroy_ = true; -}; - -} // namespace sscl::co - -#endif // POSTING_INVOKER_H diff --git a/include/spinscale/co/promises.h b/include/spinscale/co/postingPromise.h similarity index 81% rename from include/spinscale/co/promises.h rename to include/spinscale/co/postingPromise.h index 3013ca4..3e3f11e 100644 --- a/include/spinscale/co/promises.h +++ b/include/spinscale/co/postingPromise.h @@ -1,5 +1,5 @@ -#ifndef PROMISES_H -#define PROMISES_H +#ifndef POSTING_PROMISE_H +#define POSTING_PROMISE_H #include #include @@ -16,6 +16,9 @@ #include #include +#include +#include +#include #include namespace sscl::co { @@ -23,76 +26,6 @@ namespace sscl::co { template class PostingInvoker; -template > -struct ReturnValueStorage; - -template -struct ReturnValueStorage -{ - T myReturnValue{}; -}; - -template -struct ReturnValueStorage -{ -}; - -template -struct ReturnValues -: public ReturnValueStorage -{ - ReturnValues() noexcept - : myExceptionPtr(myMemberExceptionPtr) - {} - - explicit ReturnValues(std::exception_ptr &callerExceptionPtr) noexcept - : myExceptionPtr(callerExceptionPtr) - {} - - ~ReturnValues() noexcept - { -#ifdef CONFIG_LIBSSCL_DEBUG_CO - std::cout << __func__ << ": " << std::this_thread::get_id() << " Destructing.\n"; -#endif - } - - /** EXPLANATION: - * The exception_ptr ref here can either point to the exception_ptr - * a non-viral coroutine supplied to us as its storage space for - * where we should store any exception that is thrown; - * - * Or it could point to the member exception_ptr in this very class, - * which is used for viral coroutines that can bubble their exception - * up and automatically via the language runtime. - */ - std::exception_ptr &myExceptionPtr; - std::exception_ptr myMemberExceptionPtr = nullptr; -}; - -/** `return_value` / `return_void` only. ThreadTag is not a template parameter here: - * for tagged promises, PromiseType is `TaggedPostingPromise`. - */ -template > -struct PostingPromiseReturnOps; - -template -struct PostingPromiseReturnOps -{ - void return_value(T returnValue) noexcept - { - static_cast(this)->returnValues.myReturnValue = std::move(returnValue); - } -}; - -template -struct PostingPromiseReturnOps -{ - void return_void() noexcept - { - return; - } -}; - template struct PostingPromise : public PromiseChainLink @@ -345,7 +278,7 @@ protected: template struct TaggedPostingPromise : public PostingPromise, - public PostingPromiseReturnOps, T> + public PromiseReturnOps, T> { TaggedPostingPromise() noexcept : PostingPromise() @@ -389,4 +322,4 @@ struct TaggedPostingPromise } // namespace sscl::co -#endif // PROMISES_H +#endif // POSTING_PROMISE_H diff --git a/include/spinscale/co/promiseReturnOps.h b/include/spinscale/co/promiseReturnOps.h new file mode 100644 index 0000000..9fe7617 --- /dev/null +++ b/include/spinscale/co/promiseReturnOps.h @@ -0,0 +1,38 @@ +#ifndef PROMISE_RETURN_OPS_H +#define PROMISE_RETURN_OPS_H + +#include +#include + +#include + +namespace sscl::co { + +/** `return_value` / `return_void` only. ThreadTag is not a template parameter here: + * for tagged promises, PromiseType is `TaggedPostingPromise`. + */ +template > +struct PromiseReturnOps; + +template +struct PromiseReturnOps +{ + void return_value(T returnValue) noexcept + { + static_cast(this)->returnValues.myReturnValue = + std::move(returnValue); + } +}; + +template +struct PromiseReturnOps +{ + void return_void() noexcept + { + return; + } +}; + +} // namespace sscl::co + +#endif // PROMISE_RETURN_OPS_H diff --git a/include/spinscale/co/returnValues.h b/include/spinscale/co/returnValues.h new file mode 100644 index 0000000..511311f --- /dev/null +++ b/include/spinscale/co/returnValues.h @@ -0,0 +1,61 @@ +#ifndef RETURN_VALUES_H +#define RETURN_VALUES_H + +#include +#include +#include +#include +#include + +namespace sscl::co { + +template > +struct ReturnValueStorage; + +template +struct ReturnValueStorage +{ + T myReturnValue{}; +}; + +template +struct ReturnValueStorage +{ +}; + +template +struct ReturnValues +: public ReturnValueStorage +{ + ReturnValues() noexcept + : myExceptionPtr(myMemberExceptionPtr) + {} + + explicit ReturnValues(std::exception_ptr &callerExceptionPtr) noexcept + : myExceptionPtr(callerExceptionPtr) + {} + + ~ReturnValues() noexcept + { +#ifdef CONFIG_LIBSSCL_DEBUG_CO + std::cout << __func__ << ": " << std::this_thread::get_id() + << " Destructing.\n"; +#endif + } + + /** EXPLANATION: + * The exception_ptr ref here can either point to the exception_ptr + * a non-viral coroutine supplied to us as its storage space for + * where we should store any exception that is thrown; + * + * Or it could point to the member exception_ptr in this very class, + * which is used for viral coroutines that can bubble their exception + * up and automatically via the language runtime. + */ + std::exception_ptr &myExceptionPtr; + std::exception_ptr myMemberExceptionPtr = nullptr; +}; + +} // namespace sscl::co + +#endif // RETURN_VALUES_H