Compare commits

..

3 Commits

Author SHA1 Message Date
b4b61bb2b6 CompThr: Expose name arg for CompThr derivatives' ctors
This enables us to statically set the thread names at compile time,
or at least during construction at runtime.

This completes the crux of the splitting work required to
functionally split libspinscale off from SMO.
2026-02-22 18:51:21 -04:00
a7521f3760 Split: Cleanly split spinscale off from SMO
Remaining areas to split off:
* The handleLoopException, exceptionInd logic.
* getThreadName().
2026-02-22 17:38:53 -04:00
e6a924a3f7 Add new class PuppeteerComponent 2026-02-19 19:53:41 -04:00
9 changed files with 335 additions and 110 deletions

View File

@@ -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

View File

@@ -2,8 +2,8 @@
#define COMPONENT_H
#include <config.h>
#include <atomic>
#include <memory>
#include <functional>
#include <spinscale/callback.h>
#include <spinscale/puppetApplication.h>
@@ -40,15 +40,52 @@ class PuppetComponent
: public Component
{
public:
virtual void handleLoopExceptionHook() = 0;
PuppetComponent(
PuppetApplication &parent,
const std::shared_ptr<PuppetThread> &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 {
class PuppeteerComponent
: public Component
{
public:
virtual void handleLoopExceptionHook() = 0;
PuppeteerComponent(const std::shared_ptr<PuppeteerThread> &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<int> exitCode;
} // namespace pptr
} // namespace sscl
#endif // COMPONENT_H

View File

@@ -19,35 +19,38 @@
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;
class ComponentThread
{
protected:
ComponentThread(ThreadId _id)
: id(_id), name(getThreadName(_id)),
work(io_service)
ComponentThread(ThreadId _id, std::string _name)
: id(_id), name(std::move(_name)), work(io_service)
{}
public:
virtual ~ComponentThread() = default;
// getThreadName implementation is provided by application code
static std::string getThreadName(ThreadId id);
void cleanup(void);
boost::asio::io_service& getIoService(void) { return io_service; }
static const std::shared_ptr<ComponentThread> getSelf(void);
static bool tlsInitialized(void);
static std::shared_ptr<PuppeteerThread> getMrntt();
typedef void (mainFn)(ComponentThread &self);
static void setPuppeteerThread(const std::shared_ptr<PuppeteerThread> &t);
static void setPuppeteerThreadId(ThreadId id);
static std::shared_ptr<PuppeteerThread> getPptr();
static std::shared_ptr<PuppeteerThread> getPuppeteer()
{ return getPptr(); }
// CPU management methods
static int getAvailableCpuCount();
@@ -71,16 +74,45 @@ 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<void(const EntryFnArguments &)>;
PuppeteerThread(
ThreadId id, std::string name,
entryPointFn entryPoint,
pptr::PuppeteerComponent &component,
preJoltHookFn preJoltFn)
: ComponentThread(id, std::move(name)),
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 +121,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<void(const EntryFnArguments &)>;
enum class ThreadOp
{
START,
@@ -99,17 +145,19 @@ public:
N_ITEMS
};
PuppetThread(ThreadId _id)
: ComponentThread(_id),
PuppetThread(
ThreadId _id, std::string name,
entryPointFn entryPoint, PuppetComponent &component,
preJoltHookFn preJoltFn)
: ComponentThread(_id, std::move(name)),
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 +185,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 +203,7 @@ public:
namespace pptr {
extern std::shared_ptr<PuppeteerThread> 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

View File

@@ -1,59 +0,0 @@
#ifndef _MARIONETTE_H
#define _MARIONETTE_H
#include <cstdint>
#include <atomic>
#include <memory>
#include <spinscale/component.h>
namespace sscl {
class PuppeteerThread;
extern std::shared_ptr<sscl::PuppeteerThread> thread;
namespace pptr {
class MarionetteComponent
: public sscl::Component
{
public:
MarionetteComponent(const std::shared_ptr<sscl::ComponentThread> &thread);
~MarionetteComponent() = default;
public:
typedef std::function<void(bool)> mrnttLifetimeMgmtOpCbFn;
void initializeReq(sscl::Callback<mrnttLifetimeMgmtOpCbFn> callback);
void finalizeReq(sscl::Callback<mrnttLifetimeMgmtOpCbFn> callback);
// Intentionally doesn't take a callback.
void exceptionInd();
private:
class MrnttLifetimeMgmtOp;
class TerminationEvent;
};
extern std::atomic<int> 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

View File

@@ -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

View File

@@ -1,11 +1,13 @@
#include <iostream>
#include <pthread.h>
#include <spinscale/component.h>
#include <spinscale/componentThread.h>
#include <spinscale/puppetApplication.h>
#include <spinscale/marionette.h>
namespace sscl {
Component::Component(const std::shared_ptr<ComponentThread> &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<sscl::ComponentThread> &thread)
PuppeteerComponent::PuppeteerComponent(
const std::shared_ptr<sscl::PuppeteerThread> &thread)
: sscl::Component(thread)
{
}

View File

@@ -8,35 +8,48 @@
#include <spinscale/asynchronousContinuation.h>
#include <spinscale/callback.h>
#include <spinscale/callableTracer.h>
#include <spinscale/component.h>
#include <spinscale/componentThread.h>
#include <spinscale/marionette.h>
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<PuppeteerThread> thread;
void setPuppeteerThreadId(ThreadId id)
{
puppeteerThreadId = id;
}
} // namespace pptr
thread_local std::shared_ptr<ComponentThread> thisComponentThread;
// Implementation of static method
std::shared_ptr<PuppeteerThread> ComponentThread::getMrntt()
void ComponentThread::setPuppeteerThreadId(ThreadId id)
{
return sscl::pptr::thread;
pptr::puppeteerThreadId = id;
}
void ComponentThread::setPuppeteerThread(
const std::shared_ptr<PuppeteerThread> &t
)
{
pptr::thread = t;
}
std::shared_ptr<PuppeteerThread> 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<PuppeteerThread> puppeteer = sscl::pptr::thread;
std::shared_ptr<PuppeteerThread> puppeteer = pptr::thread;
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
puppeteer, selfPtr, callback);

View File

@@ -0,0 +1,99 @@
#include <iostream>
#include <pthread.h>
#include <spinscale/component.h>
#include <spinscale/componentThread.h>
namespace sscl {
namespace pptr {
std::atomic<int> 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

View File

@@ -1,4 +1,4 @@
#include <spinscale/marionette.h>
#include <spinscale/runtime.h>
namespace sscl {