Files
salmanoff/smocore/componentThread.cpp
T
hayodea 5845f1a41d Bug:Boost: Use shlibs instead of header-only for call_stack::top_
This symbol is defined as a static member object inside of a
boost detail header. When boost headers are used in a project
that uses Boost in both the main binary as well as dlopen()'d
shlibs, the top_ symbol gets duplicated and the metadata gets
partitioned.

We use the Boost shlib to unify both the main binary and the
shlibs to use the same memory address for top_.

This involves marking the templated object call_stack::top_ as
"extern" and then declaring to Boost that we intend to use the
shlibs.
2025-11-03 22:59:52 -04:00

364 lines
9.4 KiB
C++

#include <unistd.h>
#include <boostAsioLinkageFix.h>
#include <iostream>
#include <pthread.h>
#include <sched.h>
#include <boost/asio/io_service.hpp>
#include <opts.h>
#include <asynchronousContinuation.h>
#include <callback.h>
#include <mind.h>
#include <mindManager/mindManager.h>
#include <componentThread.h>
#include <marionette/marionette.h>
namespace smo {
thread_local std::shared_ptr<ComponentThread> thisComponentThread;
// Implementation of static method
std::shared_ptr<MarionetteThread> ComponentThread::getMrntt()
{
return mrntt::thread;
}
void MarionetteThread::initializeTls(void)
{
thisComponentThread = shared_from_this();
}
void MindThread::initializeTls(void)
{
thisComponentThread = shared_from_this();
}
const std::shared_ptr<ComponentThread> ComponentThread::getSelf(void)
{
if (!thisComponentThread)
{
throw std::runtime_error(std::string(__func__)
+ ": TLS not initialized");
}
return thisComponentThread;
}
void MindThread::main(MindThread& self)
{
if (OptionParser::getOptions().verbose)
{
std::cout << self.name << ":" << __func__ << ": Waiting for JOLT"
<<"\n";
}
self.getIoService().run();
self.initializeTls();
std::cout << self.name << ":" << __func__ << ": Entering event loop" <<"\n";
/* We loop here because when an exception is caught, we need to first catch
* it in the catch blocks. We bubble the exception to mrntt in the catch
* blocks, and then we loop here to await control messages from mrntt.
*
* We can't just exit on our own. Rather, we must wait for mrntt to tell us
* to exit. When we wish to finally exit, we set keepLooping to false.
*/
for (self.keepLooping = true; self.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.
*/
self.getIoService().reset();
self.getIoService().run();
}
catch (const std::exception& e)
{
sendExceptionInd = true;
std::cerr << self.name << ":" << __func__
<< ": Exception occurred: " << e.what() << "\n";
}
catch (...)
{
sendExceptionInd = true;
std::cerr << self.name << ":" << __func__
<< ": Unknown exception occurred" << "\n";
}
if (sendExceptionInd)
{
mrntt::mrntt.finalizeReq(
{nullptr, std::bind(
&mrntt::marionetteFinalizeReqCb, std::placeholders::_1)});
}
}
std::cout << self.name << ":" << __func__ << ": Exited event loop" << "\n";
}
class MindThread::ThreadLifetimeMgmtOp
: public PostedAsynchronousContinuation<threadLifetimeMgmtOpCbFn>
{
public:
ThreadLifetimeMgmtOp(
const std::shared_ptr<ComponentThread> &caller,
const std::shared_ptr<MindThread> &target,
Callback<threadLifetimeMgmtOpCbFn> callback)
: PostedAsynchronousContinuation<threadLifetimeMgmtOpCbFn>(
caller, callback),
target(target)
{}
public:
const std::shared_ptr<MindThread> target;
public:
void joltThreadReq1_posted(
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
)
{
std::cout << __func__ << ": Thread '" << target->name << "': handling "
"JOLT request."
<< "\n";
target->io_service.stop();
callOriginalCb();
}
void startThreadReq1_posted(
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
)
{
std::cout << __func__ << ": Thread '" << target->name << "': handling "
"startThread."
<< "\n";
// Execute private setup sequence here
// This is where each thread would implement its specific initialization
callOriginalCb();
}
void exitThreadReq1_mainQueue_posted(
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
)
{
std::cout << __func__ << ": Thread '" << target->name << "': handling "
"exitThread (main queue)." << "\n";
target->cleanup();
target->io_service.stop();
callOriginalCb();
}
void exitThreadReq1_pauseQueue_posted(
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
)
{
std::cout << __func__ << ": Thread '" << target->name << "': handling "
"exitThread (pause queue)."<< "\n";
target->cleanup();
target->pause_io_service.stop();
target->io_service.stop();
callOriginalCb();
}
void pauseThreadReq1_posted(
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
)
{
std::cout << __func__ << ": Thread '" << target->name << "': handling "
"pauseThread." << "\n";
/* We have to invoke the callback here before moving on because
* our next operation is going to block the thread, so it won't
* have a chance to invoke the callback until it's unblocked.
*/
callOriginalCb();
target->pause_io_service.reset();
target->pause_io_service.run();
}
void resumeThreadReq1_posted(
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
)
{
std::cout << __func__ << ": Thread '" << target->name << "': handling "
"resumeThread." << "\n";
target->pause_io_service.stop();
callOriginalCb();
}
};
void ComponentThread::cleanup(void)
{
this->keepLooping = false;
}
void MindThread::joltThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
{
/** EXPLANATION:
* We can't use shared_from_this() here because JOLTing occurs prior to
* TLS being set up.
*
* We also can't use getSelf() as yet for the same reason: getSelf()
* requires TLS to be set up.
*
* To obtain a sh_ptr to the caller, we just supply the mrntt thread since
* JOLT is always invoked by the mrntt thread. The JOLT sequence that the
* CRT main() function invokes on the mrntt thread is special since it
* supplies cmdline args and envp.
*
* To obtain a sh_ptr to the target thread, we explicitly look it up in the
* Mind object's collection of component threads.
*/
if (id == ComponentThread::MRNTT)
{
throw std::runtime_error(std::string(__func__)
+ ": invoked on mrntt thread");
}
std::shared_ptr<MarionetteThread> mrntt = mrntt::thread;
std::shared_ptr<MindThread> target = getParent().getComponentThread(id);
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
mrntt, target, callback);
this->getIoService().post(
std::bind(
&ThreadLifetimeMgmtOp::joltThreadReq1_posted,
request.get(), request));
}
// Thread management method implementations
void MindThread::startThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
{
std::shared_ptr<ComponentThread> caller = getSelf();
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
caller, shared_from_this(), callback);
this->getIoService().post(
std::bind(
&ThreadLifetimeMgmtOp::startThreadReq1_posted,
request.get(), request));
}
void MindThread::exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
{
std::shared_ptr<ComponentThread> caller = getSelf();
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
caller, shared_from_this(), callback);
this->getIoService().post(
std::bind(
&ThreadLifetimeMgmtOp::exitThreadReq1_mainQueue_posted,
request.get(), request));
pause_io_service.post(
std::bind(
&ThreadLifetimeMgmtOp::exitThreadReq1_pauseQueue_posted,
request.get(), request));
}
void MindThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
{
if (id == ComponentThread::MRNTT)
{
throw std::runtime_error(std::string(__func__)
+ ": invoked on mrntt thread");
}
std::shared_ptr<ComponentThread> caller = getSelf();
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
caller, shared_from_this(), callback);
this->getIoService().post(
std::bind(
&ThreadLifetimeMgmtOp::pauseThreadReq1_posted,
request.get(), request));
}
void MindThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
{
if (id == ComponentThread::MRNTT)
{
throw std::runtime_error(std::string(__func__)
+ ": invoked on mrntt thread");
}
// Post to the pause_io_service to unblock the paused thread
std::shared_ptr<ComponentThread> caller = getSelf();
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
caller, shared_from_this(), callback);
pause_io_service.post(
std::bind(
&ThreadLifetimeMgmtOp::resumeThreadReq1_posted,
request.get(), request));
}
// CPU management method implementations
int ComponentThread::getAvailableCpuCount()
{
int cpuCount = sysconf(_SC_NPROCESSORS_ONLN);
if (cpuCount <= 0)
{
throw std::runtime_error(std::string(__func__)
+ ": Failed to determine CPU count");
}
// Check if std::thread::hardware_concurrency() matches sysconf result
unsigned int hwConcurrency = std::thread::hardware_concurrency();
if (hwConcurrency != static_cast<unsigned int>(cpuCount))
{
std::cerr << "Warning: CPU count mismatch - "
"std::thread::hardware_concurrency() = "
<< hwConcurrency << ", sysconf(_SC_NPROCESSORS_ONLN) = "
<< cpuCount << "\n";
}
return cpuCount;
}
void MindThread::pinToCpu(int cpuId)
{
if (cpuId < 0)
{
throw std::runtime_error(std::string(__func__)
+ ": Invalid CPU ID: " + std::to_string(cpuId));
}
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpuId, &cpuset);
int result = pthread_setaffinity_np(
thread.native_handle(), sizeof(cpu_set_t), &cpuset);
if (result != 0)
{
throw std::runtime_error(std::string(__func__)
+ ": Failed to pin thread to CPU " + std::to_string(cpuId)
+ ": " + std::strerror(result));
}
pinnedCpuId = cpuId;
if (OptionParser::getOptions().verbose)
{
std::cout << name << ": Pinned to CPU " << cpuId << "\n";
}
}
} // namespace smo