From a7521f376037dd9f5aaae2b9dfc98d793a5f1a54 Mon Sep 17 00:00:00 2001 From: Hayodea Hekol Date: Sun, 22 Feb 2026 17:38:53 -0400 Subject: [PATCH] Split: Cleanly split spinscale off from SMO Remaining areas to split off: * The handleLoopException, exceptionInd logic. * getThreadName(). --- CMakeLists.txt | 1 + include/spinscale/component.h | 27 +++++++- include/spinscale/componentThread.h | 100 ++++++++++++++++++++-------- include/spinscale/marionette.h | 59 ---------------- include/spinscale/runtime.h | 23 +++++++ src/component.cpp | 76 +++++++++++++++++++-- src/componentThread.cpp | 39 +++++++---- src/puppeteerComponent.cpp | 99 +++++++++++++++++++++++++++ src/runtime.cpp | 2 +- 9 files changed, 320 insertions(+), 106 deletions(-) delete mode 100644 include/spinscale/marionette.h create mode 100644 include/spinscale/runtime.h create mode 100644 src/puppeteerComponent.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bccff0..cc1b676 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,7 @@ add_library(spinscale SHARED src/lockerAndInvokerBase.cpp src/componentThread.cpp src/component.cpp + src/puppeteerComponent.cpp src/puppetApplication.cpp src/runtime.cpp src/callableTracer.cpp diff --git a/include/spinscale/component.h b/include/spinscale/component.h index 977f3a6..9546117 100644 --- a/include/spinscale/component.h +++ b/include/spinscale/component.h @@ -2,8 +2,8 @@ #define COMPONENT_H #include +#include #include -#include #include #include @@ -40,13 +40,22 @@ class PuppetComponent : public Component { public: + virtual void handleLoopExceptionHook() = 0; + PuppetComponent( PuppetApplication &parent, const std::shared_ptr &thread); ~PuppetComponent() = default; + static void defaultPuppetMain(const PuppetThread::EntryFnArguments &args); + public: PuppetApplication &parent; + +protected: + virtual void postJoltHook() {} + virtual void preLoopHook() {} + virtual void postLoopHook() {} }; namespace pptr { @@ -55,10 +64,26 @@ class PuppeteerComponent : public Component { public: + virtual void handleLoopExceptionHook() = 0; + PuppeteerComponent(const std::shared_ptr &thread); ~PuppeteerComponent() = default; + + static void defaultPuppeteerMain( + const PuppeteerThread::EntryFnArguments &args); + +protected: + virtual void postJoltHook() {} + virtual void tryBlock1Hook() {} + virtual void preLoopHook() {} + virtual void postLoopHook() {} + virtual void postTryBlock1CatchHook() {} + virtual void handleTryBlock1TypedException(const std::exception& e); + virtual void handleTryBlock1UnknownException(); }; +extern std::atomic exitCode; + } // namespace pptr } // namespace sscl diff --git a/include/spinscale/componentThread.h b/include/spinscale/componentThread.h index 930832d..7157c5f 100644 --- a/include/spinscale/componentThread.h +++ b/include/spinscale/componentThread.h @@ -19,9 +19,14 @@ namespace sscl { +class PuppetComponent; class PuppeteerThread; class PuppetThread; +namespace pptr { +class PuppeteerComponent; +} + // ThreadId is a generic type - application-specific enums should be defined elsewhere typedef uint8_t ThreadId; @@ -29,8 +34,7 @@ class ComponentThread { protected: ComponentThread(ThreadId _id) - : id(_id), name(getThreadName(_id)), - work(io_service) + : id(_id), name(getThreadName(_id)), work(io_service) {} public: @@ -45,9 +49,11 @@ public: static const std::shared_ptr getSelf(void); static bool tlsInitialized(void); - static std::shared_ptr getMrntt(); - - typedef void (mainFn)(ComponentThread &self); + static void setPuppeteerThread(const std::shared_ptr &t); + static void setPuppeteerThreadId(ThreadId id); + static std::shared_ptr getPptr(); + static std::shared_ptr getPuppeteer() + { return getPptr(); } // CPU management methods static int getAvailableCpuCount(); @@ -71,16 +77,44 @@ class PuppeteerThread public ComponentThread { public: - PuppeteerThread(ThreadId id = 0) - : ComponentThread(id), - thread(main, std::ref(*this)) - { - } + typedef void (*preJoltHookFn)(PuppeteerThread &); + + struct EntryFnArguments + { + PuppeteerThread &usableBeforeJolt; + /** EXPLANATION: + * The `Puppet*Component` ref points at the Component object which this + * thread is associated with. However, we have no guarantee that this + * object has been constructed at the point of OS thread entry. + * + * Hence this ref must be dereferenced only after JOLT. + */ + pptr::PuppeteerComponent &useOnlyAfterJolt; + preJoltHookFn preJoltHook; + }; + + using entryPointFn = std::function; + + PuppeteerThread( + ThreadId id, entryPointFn entryPoint, + pptr::PuppeteerComponent &component, + preJoltHookFn preJoltFn) + : ComponentThread(id), + entryFnArguments(*this, component, preJoltFn), + thread(std::move(entryPoint), std::cref(entryFnArguments)) + {} - static void main(PuppeteerThread& self); void initializeTls(void); + void exitLoop(void); public: + EntryFnArguments entryFnArguments; + /** EXPLANATION: + * Must always be memberwise-initialized last. + * This ensures that the ref to this `ComponentThread` object, which is + * passed to the entry point function, is fully constructed when the OS + * thread begins executing. + */ std::thread thread; }; @@ -89,6 +123,20 @@ class PuppetThread public ComponentThread { public: + typedef void (*preJoltHookFn)(PuppetThread &); + + struct EntryFnArguments + { + PuppetThread &usableBeforeJolt; + /** See comment above in: + * PuppeteerThread::EntryFnArguments::useOnlyAfterJolt. + */ + PuppetComponent &useOnlyAfterJolt; + preJoltHookFn preJoltHook; + }; + + using entryPointFn = std::function; + enum class ThreadOp { START, @@ -99,17 +147,18 @@ public: N_ITEMS }; - PuppetThread(ThreadId _id) - : ComponentThread(_id), + PuppetThread( + ThreadId _id, entryPointFn entryPoint, PuppetComponent &component, + preJoltHookFn preJoltFn) + : ComponentThread(_id), pinnedCpuId(-1), pause_work(pause_io_service), - thread(main, std::ref(*this)) - { - } + entryFnArguments(*this, component, preJoltFn), + thread(std::move(entryPoint), std::cref(entryFnArguments)) + {} virtual ~PuppetThread() = default; - static void main(PuppetThread& self); void initializeTls(void); // Thread management methods @@ -137,17 +186,16 @@ public: // CPU management methods void pinToCpu(int cpuId); -protected: - /** - * Handle exception - called from main() when an exception occurs. - * Derived classes can override to provide application-specific handling. - */ - virtual void handleException() {} - public: int pinnedCpuId; boost::asio::io_service pause_io_service; boost::asio::io_service::work pause_work; + +public: + EntryFnArguments entryFnArguments; + /** Must always be memberwise-initialized last. + * See comment on `PuppeteerThread::thread` for explanation. + */ std::thread thread; public: @@ -156,11 +204,7 @@ public: namespace pptr { extern std::shared_ptr thread; - -// Forward declaration for puppeteer thread ID management -// Must be after sscl namespace so ThreadId is defined extern ThreadId puppeteerThreadId; -void setPuppeteerThreadId(ThreadId id); } // namespace pptr } // namespace sscl diff --git a/include/spinscale/marionette.h b/include/spinscale/marionette.h deleted file mode 100644 index 05759dd..0000000 --- a/include/spinscale/marionette.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef _MARIONETTE_H -#define _MARIONETTE_H - -#include -#include -#include -#include - -namespace sscl { - -class PuppeteerThread; -extern std::shared_ptr thread; - -namespace pptr { - -class MarionetteComponent -: public sscl::Component -{ -public: - MarionetteComponent(const std::shared_ptr &thread); - ~MarionetteComponent() = default; - -public: - typedef std::function mrnttLifetimeMgmtOpCbFn; - void initializeReq(sscl::Callback callback); - void finalizeReq(sscl::Callback callback); - // Intentionally doesn't take a callback. - void exceptionInd(); - -private: - class MrnttLifetimeMgmtOp; - class TerminationEvent; -}; - -extern std::atomic exitCode; -void exitMarionetteLoop(); -void marionetteFinalizeReqCb(bool success); -extern MarionetteComponent mrntt; - -} // namespace pptr - -struct CrtCommandLineArgs -{ - CrtCommandLineArgs(int argc, char *argv[], char *envp[]) - : argc(argc), argv(argv), envp(envp) - {} - - int argc; - char **argv; - char **envp; - - static void set(int argc, char *argv[], char *envp[]); -}; - -extern CrtCommandLineArgs crtCommandLineArgs; - -} // namespace sscl - -#endif // _MARIONETTE_H diff --git a/include/spinscale/runtime.h b/include/spinscale/runtime.h new file mode 100644 index 0000000..2479d27 --- /dev/null +++ b/include/spinscale/runtime.h @@ -0,0 +1,23 @@ +#ifndef RUNTIME_H +#define RUNTIME_H + +namespace sscl { + +struct CrtCommandLineArgs +{ + CrtCommandLineArgs(int argc, char *argv[], char *envp[]) + : argc(argc), argv(argv), envp(envp) + {} + + int argc; + char **argv; + char **envp; + + static void set(int argc, char *argv[], char *envp[]); +}; + +extern CrtCommandLineArgs crtCommandLineArgs; + +} // namespace sscl + +#endif // RUNTIME_H diff --git a/src/component.cpp b/src/component.cpp index d901548..81939e5 100644 --- a/src/component.cpp +++ b/src/component.cpp @@ -1,11 +1,13 @@ +#include +#include #include +#include #include -#include namespace sscl { Component::Component(const std::shared_ptr &thread) -: thread(thread) +: thread(thread) { } @@ -16,10 +18,76 @@ parent(parent) { } +void PuppetComponent::defaultPuppetMain( + const PuppetThread::EntryFnArguments &args) +{ + PuppetThread &thr = args.usableBeforeJolt; + PuppetComponent &comp = args.useOnlyAfterJolt; + + if (args.preJoltHook) { args.preJoltHook(thr); } + + /** FIXME: + * Figure out why we don't call reset() here, and then explicitly document + * it. + */ + thr.getIoService().run(); + thr.initializeTls(); + + comp.postJoltHook(); + comp.preLoopHook(); + + /* We loop here because when an exception is caught, we need to first catch + * it in the catch blocks and invoke handleLoopExceptionHook so the + * application can respond (e.g. notify a controller). We then re-enter + * the loop to await control messages. + * + * We can't just exit on our own. Rather, keepLooping must be set to false + * by the application when shutdown is desired. + */ + for (thr.keepLooping = true; thr.keepLooping;) + { + bool sendExceptionInd = false; + + try { + /** EXPLANATION: + * This reset() call is crucial for async bridging patterns + * to work. + * When the outermost thread's io_service is stop()ped (e.g., + * from JOLT sequence), it won't process any new work until + * reset() is called, even if nested async operations try to + * post work to it. This means async bridges invoked from + * the outermost thread main sequence won't work until this + * reset() call. + */ + thr.getIoService().reset(); + thr.getIoService().run(); + } + catch (const std::exception& e) + { + sendExceptionInd = true; + std::cerr << thr.name << ":" << __func__ + << ": Exception occurred: " << e.what() << "\n"; + } + catch (...) + { + sendExceptionInd = true; + std::cerr << thr.name << ":" << __func__ + << ": Unknown exception occurred" << "\n"; + } + + if (sendExceptionInd) + { + comp.handleLoopExceptionHook(); + } + } + + comp.postLoopHook(); +} + namespace pptr { -MarionetteComponent::MarionetteComponent( - const std::shared_ptr &thread) +PuppeteerComponent::PuppeteerComponent( + const std::shared_ptr &thread) : sscl::Component(thread) { } diff --git a/src/componentThread.cpp b/src/componentThread.cpp index 85be3af..d45986d 100644 --- a/src/componentThread.cpp +++ b/src/componentThread.cpp @@ -8,35 +8,48 @@ #include #include #include +#include #include -#include namespace sscl { namespace pptr { /* Global variable to store the puppeteer thread ID * Default value is 0, but should be set by application code via - * setPuppeteerThreadId(). + * ComponentThread::setPuppeteerThreadId(). */ ThreadId puppeteerThreadId = 0; -/* Global puppeteer thread instance - defined here but initialized by - * application code. +/* Global puppeteer thread instance - assigned by application code + * (e.g. smo::mrntt::thread) via setPuppeteerThread(). */ std::shared_ptr thread; -void setPuppeteerThreadId(ThreadId id) -{ - puppeteerThreadId = id; -} - } // namespace pptr thread_local std::shared_ptr thisComponentThread; -// Implementation of static method -std::shared_ptr ComponentThread::getMrntt() +void ComponentThread::setPuppeteerThreadId(ThreadId id) { - return sscl::pptr::thread; + pptr::puppeteerThreadId = id; +} + +void ComponentThread::setPuppeteerThread( + const std::shared_ptr &t + ) +{ + pptr::thread = t; +} + +std::shared_ptr ComponentThread::getPptr() +{ + return pptr::thread; +} + +void PuppeteerThread::exitLoop(void) +{ + keepLooping = false; + getIoService().stop(); + std::cout << name << ": Signaled main loop to exit." << "\n"; } void PuppeteerThread::initializeTls(void) @@ -191,7 +204,7 @@ void PuppetThread::joltThreadReq( + ": invoked on puppeteer thread"); } - std::shared_ptr puppeteer = sscl::pptr::thread; + std::shared_ptr puppeteer = pptr::thread; auto request = std::make_shared( puppeteer, selfPtr, callback); diff --git a/src/puppeteerComponent.cpp b/src/puppeteerComponent.cpp new file mode 100644 index 0000000..184ba89 --- /dev/null +++ b/src/puppeteerComponent.cpp @@ -0,0 +1,99 @@ +#include +#include +#include +#include + +namespace sscl { +namespace pptr { + +std::atomic exitCode{0}; + +void PuppeteerComponent::defaultPuppeteerMain( + const PuppeteerThread::EntryFnArguments &args) +{ + PuppeteerThread &thr = args.usableBeforeJolt; + PuppeteerComponent &comp = args.useOnlyAfterJolt; + + if (args.preJoltHook) { args.preJoltHook(thr); } + + thr.getIoService().reset(); + thr.getIoService().run(); + thr.initializeTls(); + + comp.postJoltHook(); + + try { + comp.tryBlock1Hook(); + comp.preLoopHook(); + + /* We loop here because when an exception occurs, we need to + * both direct the puppet threads to exit gracefully, and then we + * also need to post messages to our own event loop to initiate + * our own orderly exit. So we loop here to re-enter the event + * loop, both to receive the ACK messages from the puppet + * threads, and to post messages to our own event loop to + * initiate our own orderly exit. + */ + for (thr.keepLooping = true; thr.keepLooping;) + { + bool sendExceptionInd = false; + + try { + /** EXPLANATION: + * This reset() call is crucial for async bridging + * patterns to work. + * When the outermost thread's io_service is stop()ped + * (e.g., from JOLT sequence), it won't process any new + * work until reset() is called, even if nested async + * operations try to post work to it. This means async + * bridges invoked from the outermost thread main sequence + * won't work until this reset() call. + */ + thr.getIoService().reset(); + thr.getIoService().run(); + } + catch (const std::exception& e) + { + sendExceptionInd = true; + std::cerr << thr.name << ":main: Exception occurred: " + << e.what() << "\n"; + } + catch (...) + { + sendExceptionInd = true; + std::cerr << thr.name + << ":main: Unknown exception occurred" << "\n"; + } + + if (sendExceptionInd) + { + comp.handleLoopExceptionHook(); + } + } + + comp.postLoopHook(); + } + catch (const std::exception& e) + { + comp.handleTryBlock1TypedException(e); + } + catch (...) + { + comp.handleTryBlock1UnknownException(); + } + + comp.postTryBlock1CatchHook(); +} + +void PuppeteerComponent::handleTryBlock1TypedException(const std::exception& e) +{ + std::cerr << "main: Exception occurred: " << e.what() << std::endl; +} + +void PuppeteerComponent::handleTryBlock1UnknownException() +{ + std::cerr << "main: Unknown exception occurred" << std::endl; +} + +} // namespace pptr +} // namespace sscl diff --git a/src/runtime.cpp b/src/runtime.cpp index fb632ad..7d1fb32 100644 --- a/src/runtime.cpp +++ b/src/runtime.cpp @@ -1,4 +1,4 @@ -#include +#include namespace sscl {