Compare commits

..

9 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
01a9c6ecc9 Change type: PuppetComponent::thread to PuppetThread 2026-02-18 02:03:19 -04:00
e813962168 Rename mrntt=>pptr
We'll probably use the name "marionette" as an application-level
name.
2026-02-18 01:13:02 -04:00
a3931d7b8f CompThread: Rearrange global var defs 2026-02-17 23:45:46 -04:00
e77ecd447d Import SequenceLock from SMO 2026-02-17 11:18:33 -04:00
130921062c CallableTracer: fix namespace and decouple from SMO::OptionsParser
We now have a static local var that informs libspinscale whether or
not to print the callable traces at runtime.
2026-02-17 11:05:00 -04:00
18b632e5bb Move CRT Cmdline args obj & impl into libspinscale 2026-02-17 10:40:19 -04:00
13 changed files with 489 additions and 159 deletions

View File

@@ -75,7 +75,10 @@ add_library(spinscale SHARED
src/lockerAndInvokerBase.cpp src/lockerAndInvokerBase.cpp
src/componentThread.cpp src/componentThread.cpp
src/component.cpp src/component.cpp
src/puppeteerComponent.cpp
src/puppetApplication.cpp src/puppetApplication.cpp
src/runtime.cpp
src/callableTracer.cpp
) )
# Conditionally add qutexAcquisitionHistoryTracker.cpp only when debug locks # Conditionally add qutexAcquisitionHistoryTracker.cpp only when debug locks

View File

@@ -34,9 +34,9 @@ public:
{ break; } { break; }
/** EXPLANATION: /** EXPLANATION:
* In the mrntt and mind thread loops we call checkException() after * In the puppeteer and mind thread loops we call checkException()
* run() returns, but we don't have to do that here because * after run() returns, but we don't have to do that here because
* setException() calls stop. * setException() calls stop().
* *
* So if an exception is set on our thread, we'll break out of this * So if an exception is set on our thread, we'll break out of this
* loop due to the check for stopped() above, and that'll take us * loop due to the check for stopped() above, and that'll take us

View File

@@ -8,11 +8,6 @@
#include <cstdint> #include <cstdint>
#include <spinscale/componentThread.h> #include <spinscale/componentThread.h>
// Forward declaration - OptionParser is defined in smocore/include/opts.h
// If you need tracing, include opts.h before including this header
// The code will check for OPTS_H define to see if opts.h has been included
class OptionParser;
namespace sscl { namespace sscl {
/** /**
@@ -50,11 +45,8 @@ public:
void operator()() void operator()()
{ {
// OptionParser::getOptions() requires opts.h to be included
// Only check traceCallables if opts.h has been included (OPTS_H is defined)
#ifdef CONFIG_DEBUG_TRACE_CALLABLES #ifdef CONFIG_DEBUG_TRACE_CALLABLES
#ifdef OPTS_H if (optTraceCallables)
if (OptionParser::getOptions().traceCallables)
{ {
std::cout << "" << __func__ << ": On thread " std::cout << "" << __func__ << ": On thread "
<< (ComponentThread::tlsInitialized() << (ComponentThread::tlsInitialized()
@@ -66,11 +58,13 @@ public:
<< std::endl; << std::endl;
} }
#endif #endif
#endif
callable(); callable();
} }
public: public:
/// Set by application (e.g. from opts) to enable per-callable trace output
static bool optTraceCallables;
/// Name of the function that created this callable /// Name of the function that created this callable
std::string callerFuncName; std::string callerFuncName;
/// Line number where this callable was created /// Line number where this callable was created
@@ -115,7 +109,7 @@ private:
// e.g., "void smo::SomeClass::method(int, int)" // e.g., "void smo::SomeClass::method(int, int)"
// __builtin_return_address(0) = direct caller // __builtin_return_address(0) = direct caller
// __builtin_return_address(1) = caller before that // __builtin_return_address(1) = caller before that
#define STC(arg) smo::CallableTracer( \ #define STC(arg) sscl::CallableTracer( \
__PRETTY_FUNCTION__, \ __PRETTY_FUNCTION__, \
__LINE__, \ __LINE__, \
__builtin_return_address(0), \ __builtin_return_address(0), \
@@ -126,7 +120,7 @@ private:
// e.g., "void __cdecl smo::SomeClass::method(int, int)" // e.g., "void __cdecl smo::SomeClass::method(int, int)"
// _ReturnAddress() = direct caller (only one level available) // _ReturnAddress() = direct caller (only one level available)
#include <intrin.h> #include <intrin.h>
#define STC(arg) smo::CallableTracer( \ #define STC(arg) sscl::CallableTracer( \
__FUNCSIG__, \ __FUNCSIG__, \
__LINE__, \ __LINE__, \
_ReturnAddress(), \ _ReturnAddress(), \
@@ -135,7 +129,7 @@ private:
#else #else
// Fallback to standard __func__ (unqualified name only) // Fallback to standard __func__ (unqualified name only)
// No return address support // No return address support
#define STC(arg) smo::CallableTracer( \ #define STC(arg) sscl::CallableTracer( \
__func__, \ __func__, \
__LINE__, \ __LINE__, \
nullptr, \ nullptr, \

View File

@@ -2,15 +2,28 @@
#define COMPONENT_H #define COMPONENT_H
#include <config.h> #include <config.h>
#include <atomic>
#include <memory> #include <memory>
#include <functional>
#include <spinscale/callback.h> #include <spinscale/callback.h>
#include <spinscale/puppetApplication.h> #include <spinscale/puppetApplication.h>
namespace sscl { namespace sscl {
class ComponentThread; class ComponentThread;
class PuppetThread;
/** EXPLANATION:
* Components are API-exposing sub-components of an application. They are used
* aggregate the resources and API of some logically distinct sub-system into
* a single abstract entity. Basically, a component is a way to bind some APIs
* and resources to a particular thread. Ideally, all accesses to the resources
* of a component should be made through the component's APIs.
*
* Multiple components can share the same thread; and for this reason, each
* component must be supplied with a reference to the thread it shares.
* This amounts to saying that a single thread may expose and serve multiple
* APIs.
*/
class Component class Component
{ {
public: public:
@@ -27,15 +40,52 @@ class PuppetComponent
: public Component : public Component
{ {
public: public:
virtual void handleLoopExceptionHook() = 0;
PuppetComponent( PuppetComponent(
PuppetApplication &parent, PuppetApplication &parent,
const std::shared_ptr<ComponentThread> &thread); const std::shared_ptr<PuppetThread> &thread);
~PuppetComponent() = default; ~PuppetComponent() = default;
static void defaultPuppetMain(const PuppetThread::EntryFnArguments &args);
public: public:
PuppetApplication &parent; 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 } // namespace sscl
#endif // COMPONENT_H #endif // COMPONENT_H

View File

@@ -19,35 +19,38 @@
namespace sscl { namespace sscl {
class MarionetteThread; class PuppetComponent;
class PuppeteerThread;
class PuppetThread; class PuppetThread;
namespace pptr {
class PuppeteerComponent;
}
// ThreadId is a generic type - application-specific enums should be defined elsewhere // ThreadId is a generic type - application-specific enums should be defined elsewhere
typedef uint8_t ThreadId; typedef uint8_t ThreadId;
class ComponentThread class ComponentThread
{ {
protected: protected:
ComponentThread(ThreadId _id) ComponentThread(ThreadId _id, std::string _name)
: id(_id), name(getThreadName(_id)), : id(_id), name(std::move(_name)), work(io_service)
work(io_service)
{} {}
public: public:
virtual ~ComponentThread() = default; virtual ~ComponentThread() = default;
// getThreadName implementation is provided by application code
static std::string getThreadName(ThreadId id);
void cleanup(void); void cleanup(void);
boost::asio::io_service& getIoService(void) { return io_service; } boost::asio::io_service& getIoService(void) { return io_service; }
static const std::shared_ptr<ComponentThread> getSelf(void); static const std::shared_ptr<ComponentThread> getSelf(void);
static bool tlsInitialized(void); static bool tlsInitialized(void);
static std::shared_ptr<MarionetteThread> getMrntt(); static void setPuppeteerThread(const std::shared_ptr<PuppeteerThread> &t);
static void setPuppeteerThreadId(ThreadId id);
typedef void (mainFn)(ComponentThread &self); static std::shared_ptr<PuppeteerThread> getPptr();
static std::shared_ptr<PuppeteerThread> getPuppeteer()
{ return getPptr(); }
// CPU management methods // CPU management methods
static int getAvailableCpuCount(); static int getAvailableCpuCount();
@@ -66,21 +69,50 @@ public:
std::atomic<bool> keepLooping; std::atomic<bool> keepLooping;
}; };
class MarionetteThread class PuppeteerThread
: public std::enable_shared_from_this<MarionetteThread>, : public std::enable_shared_from_this<PuppeteerThread>,
public ComponentThread public ComponentThread
{ {
public: public:
MarionetteThread(ThreadId id = 0) typedef void (*preJoltHookFn)(PuppeteerThread &);
: ComponentThread(id),
thread(main, std::ref(*this)) 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(MarionetteThread& self);
void initializeTls(void); void initializeTls(void);
void exitLoop(void);
public: 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; std::thread thread;
}; };
@@ -89,6 +121,20 @@ class PuppetThread
public ComponentThread public ComponentThread
{ {
public: 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 enum class ThreadOp
{ {
START, START,
@@ -99,17 +145,19 @@ public:
N_ITEMS N_ITEMS
}; };
PuppetThread(ThreadId _id) PuppetThread(
: ComponentThread(_id), ThreadId _id, std::string name,
entryPointFn entryPoint, PuppetComponent &component,
preJoltHookFn preJoltFn)
: ComponentThread(_id, std::move(name)),
pinnedCpuId(-1), pinnedCpuId(-1),
pause_work(pause_io_service), pause_work(pause_io_service),
thread(main, std::ref(*this)) entryFnArguments(*this, component, preJoltFn),
{ thread(std::move(entryPoint), std::cref(entryFnArguments))
} {}
virtual ~PuppetThread() = default; virtual ~PuppetThread() = default;
static void main(PuppetThread& self);
void initializeTls(void); void initializeTls(void);
// Thread management methods // Thread management methods
@@ -137,31 +185,27 @@ public:
// CPU management methods // CPU management methods
void pinToCpu(int cpuId); 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: public:
int pinnedCpuId; int pinnedCpuId;
boost::asio::io_service pause_io_service; boost::asio::io_service pause_io_service;
boost::asio::io_service::work pause_work; 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; std::thread thread;
public: public:
class ThreadLifetimeMgmtOp; class ThreadLifetimeMgmtOp;
}; };
namespace mrntt { namespace pptr {
extern std::shared_ptr<MarionetteThread> thread; extern std::shared_ptr<PuppeteerThread> thread;
extern ThreadId puppeteerThreadId;
} // namespace pptr
// Forward declaration for marionette thread ID management } // namespace sscl
// Must be after sscl namespace so ThreadId is defined
extern ThreadId marionetteThreadId;
void setMarionetteThreadId(ThreadId id);
} // namespace mrntt
}
#endif // COMPONENT_THREAD_H #endif // COMPONENT_THREAD_H

View File

@@ -1,58 +0,0 @@
#ifndef _MARIONETTE_H
#define _MARIONETTE_H
#include <cstdint>
#include <atomic>
#include <memory>
#include <spinscale/component.h>
namespace sscl {
class MarionetteThread;
namespace mrntt {
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::shared_ptr<sscl::MarionetteThread> thread;
extern std::atomic<int> exitCode;
void exitMarionetteLoop();
void marionetteFinalizeReqCb(bool success);
extern MarionetteComponent mrntt;
} // namespace mrntt
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[]);
};
} // 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

@@ -0,0 +1,76 @@
#ifndef SPINSCALE_SEQUENCE_LOCK_H
#define SPINSCALE_SEQUENCE_LOCK_H
#include <atomic>
#include <optional>
namespace sscl {
/**
* @brief Sequence lock synchronization primitive
*
* A reader-writer synchronization primitive where writers increment the
* sequence number (odd = writing in progress, even = stable) and readers
* check the sequence number to detect concurrent modifications.
*/
class SequenceLock
{
public:
SequenceLock()
: sequenceNo(0)
{}
~SequenceLock() = default;
// Non-copyable, non-movable (std::atomic is neither copyable nor movable)
SequenceLock(const SequenceLock&) = delete;
SequenceLock& operator=(const SequenceLock&) = delete;
SequenceLock(SequenceLock&&) = delete;
SequenceLock& operator=(SequenceLock&&) = delete;
/* Atomically increments sequenceNo and issues a release barrier.
* Makes the sequence number odd, indicating a write is in progress.
*/
void writeAcquire()
{ sequenceNo.fetch_add(1, std::memory_order_release); }
/* Atomically increments sequenceNo and issues a release barrier.
* Makes the sequence number even again, indicating write is complete.
*/
void writeRelease()
{ sequenceNo.fetch_add(1, std::memory_order_release); }
/* Issues an acquire barrier and checks if the sequence number is even
* (stable state). If odd (writer active), returns nullopt. Otherwise
* returns the sequence number.
*
* @return std::nullopt if writer is active, otherwise the sequence number
*/
std::optional<size_t> readAcquire()
{
size_t seq = sequenceNo.load(std::memory_order_acquire);
if (seq & 1) {
return std::nullopt;
}
return seq;
}
/* Issues an acquire barrier and checks if the sequence number matches
* the original value from readAcquire(). If equal, the read was consistent.
*
* @param originalSequenceNo The sequence number obtained from readAcquire()
* @return true if read was consistent, false if writer modified during read
*/
bool readRelease(size_t originalSequenceNo)
{
size_t seq = sequenceNo.load(std::memory_order_acquire);
return seq == originalSequenceNo;
}
private:
std::atomic<size_t> sequenceNo;
};
} // namespace sscl
#endif // SPINSCALE_SEQUENCE_LOCK_H

7
src/callableTracer.cpp Normal file
View File

@@ -0,0 +1,7 @@
#include <spinscale/callableTracer.h>
namespace sscl {
bool CallableTracer::optTraceCallables = false;
} // namespace sscl

View File

@@ -1,6 +1,8 @@
#include <iostream>
#include <pthread.h>
#include <spinscale/component.h> #include <spinscale/component.h>
#include <spinscale/componentThread.h>
#include <spinscale/puppetApplication.h> #include <spinscale/puppetApplication.h>
#include <spinscale/marionette.h>
namespace sscl { namespace sscl {
@@ -10,19 +12,86 @@ Component::Component(const std::shared_ptr<ComponentThread> &thread)
} }
PuppetComponent::PuppetComponent( PuppetComponent::PuppetComponent(
PuppetApplication &parent, const std::shared_ptr<ComponentThread> &thread) PuppetApplication &parent, const std::shared_ptr<PuppetThread> &thread)
: Component(thread), : Component(thread),
parent(parent) parent(parent)
{ {
} }
namespace mrntt { void PuppetComponent::defaultPuppetMain(
const PuppetThread::EntryFnArguments &args)
{
PuppetThread &thr = args.usableBeforeJolt;
PuppetComponent &comp = args.useOnlyAfterJolt;
MarionetteComponent::MarionetteComponent( if (args.preJoltHook) { args.preJoltHook(thr); }
const std::shared_ptr<sscl::ComponentThread> &thread)
/** 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 {
PuppeteerComponent::PuppeteerComponent(
const std::shared_ptr<sscl::PuppeteerThread> &thread)
: sscl::Component(thread) : sscl::Component(thread)
{ {
} }
} // namespace mrntt } // namespace pptr
} // namespace sscl } // namespace sscl

View File

@@ -8,40 +8,51 @@
#include <spinscale/asynchronousContinuation.h> #include <spinscale/asynchronousContinuation.h>
#include <spinscale/callback.h> #include <spinscale/callback.h>
#include <spinscale/callableTracer.h> #include <spinscale/callableTracer.h>
#include <spinscale/component.h>
#include <spinscale/componentThread.h> #include <spinscale/componentThread.h>
#include <spinscale/marionette.h>
namespace sscl { namespace sscl {
namespace mrntt { namespace pptr {
// Global variable to store the marionette thread ID /* Global variable to store the puppeteer thread ID
// Default value is 0, but should be set by application code via setMarionetteThreadId() * Default value is 0, but should be set by application code via
ThreadId marionetteThreadId = 0; * ComponentThread::setPuppeteerThreadId().
*/
ThreadId puppeteerThreadId = 0;
/* Global puppeteer thread instance - assigned by application code
* (e.g. smo::mrntt::thread) via setPuppeteerThread().
*/
std::shared_ptr<PuppeteerThread> thread;
void setMarionetteThreadId(ThreadId id) } // namespace pptr
{
marionetteThreadId = id;
}
} // namespace mrntt
} // namespace sscl
namespace sscl {
thread_local std::shared_ptr<ComponentThread> thisComponentThread; thread_local std::shared_ptr<ComponentThread> thisComponentThread;
namespace mrntt { void ComponentThread::setPuppeteerThreadId(ThreadId id)
// Global marionette thread instance - defined here but initialized by application
std::shared_ptr<MarionetteThread> thread;
} // namespace mrntt
// Implementation of static method
std::shared_ptr<MarionetteThread> ComponentThread::getMrntt()
{ {
return sscl::mrntt::thread; pptr::puppeteerThreadId = id;
} }
void MarionetteThread::initializeTls(void) 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)
{ {
thisComponentThread = shared_from_this(); thisComponentThread = shared_from_this();
} }
@@ -179,24 +190,24 @@ void PuppetThread::joltThreadReq(
* We also can't use getSelf() as yet for the same reason: getSelf() * We also can't use getSelf() as yet for the same reason: getSelf()
* requires TLS to be set up. * requires TLS to be set up.
* *
* To obtain a sh_ptr to the caller, we just supply the mrntt thread since * To obtain a sh_ptr to the caller, we just supply the puppeteer thread
* JOLT is always invoked by the mrntt thread. The JOLT sequence that the * since JOLT is always invoked by the puppeteer thread. The JOLT sequence
* CRT main() function invokes on the mrntt thread is special since it * that the CRT main() function invokes on the puppeteer thread is special
* supplies cmdline args and envp. * since it supplies cmdline args and envp.
* *
* To obtain a sh_ptr to the target thread, we use the selfPtr parameter * To obtain a sh_ptr to the target thread, we use the selfPtr parameter
* passed in by the caller. * passed in by the caller.
*/ */
if (id == sscl::mrntt::marionetteThreadId) if (id == sscl::pptr::puppeteerThreadId)
{ {
throw std::runtime_error(std::string(__func__) throw std::runtime_error(std::string(__func__)
+ ": invoked on mrntt thread"); + ": invoked on puppeteer thread");
} }
std::shared_ptr<MarionetteThread> mrntt = sscl::mrntt::thread; std::shared_ptr<PuppeteerThread> puppeteer = pptr::thread;
auto request = std::make_shared<ThreadLifetimeMgmtOp>( auto request = std::make_shared<ThreadLifetimeMgmtOp>(
mrntt, selfPtr, callback); puppeteer, selfPtr, callback);
this->getIoService().post( this->getIoService().post(
STC(std::bind( STC(std::bind(
@@ -238,10 +249,10 @@ void PuppetThread::exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
void PuppetThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback) void PuppetThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
{ {
if (id == sscl::mrntt::marionetteThreadId) if (id == sscl::pptr::puppeteerThreadId)
{ {
throw std::runtime_error(std::string(__func__) throw std::runtime_error(std::string(__func__)
+ ": invoked on mrntt thread"); + ": invoked on puppeteer thread");
} }
std::shared_ptr<ComponentThread> caller = getSelf(); std::shared_ptr<ComponentThread> caller = getSelf();
@@ -257,10 +268,10 @@ void PuppetThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
void PuppetThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback) void PuppetThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
{ {
if (id == sscl::mrntt::marionetteThreadId) if (id == sscl::pptr::puppeteerThreadId)
{ {
throw std::runtime_error(std::string(__func__) throw std::runtime_error(std::string(__func__)
+ ": invoked on mrntt thread"); + ": invoked on puppeteer thread");
} }
// Post to the pause_io_service to unblock the paused thread // Post to the pause_io_service to unblock the paused thread

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

12
src/runtime.cpp Normal file
View File

@@ -0,0 +1,12 @@
#include <spinscale/runtime.h>
namespace sscl {
CrtCommandLineArgs crtCommandLineArgs(0, nullptr, nullptr);
void CrtCommandLineArgs::set(int argc, char *argv[], char *envp[])
{
crtCommandLineArgs = CrtCommandLineArgs(argc, argv, envp);
}
} // namespace sscl