da0ef64f62
We now allocate globalMind locally inside of marionetteMain. Why? Before now, we had an asymmetric threading situation where the globalMind's threads were initialized at during global constructor invocation and not on demand. This meant that we had to shut down those threads even if we had never got to the point of calling Mind::initializeReq. This significantly complicated our shutdown sequence since we had to factor in the lifetime of the std::thread objects inside of the ComponentThreads which were inside of the globalMind object. Now, if we hadn't called Mind::initializeReq, we don't have to perform any Mind::finalizeReq or adjacent operations. Shutdown is symmetrically mirrored against the operations we actually performed during execution. We introduced some complexity by splitting ComponentThreads into two derivative types (MindThread and MarionetteThread) but I think in the long term we'll be able to massage this split into a much cleaner situation overall.
176 lines
3.6 KiB
C++
176 lines
3.6 KiB
C++
#ifndef COMPONENT_THREAD_H
|
|
#define COMPONENT_THREAD_H
|
|
|
|
#include <atomic>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <boost/asio.hpp>
|
|
#include <stdexcept>
|
|
#include <queue>
|
|
#include <functional>
|
|
#include <pthread.h>
|
|
#include <sched.h>
|
|
#include <unistd.h>
|
|
#include <memory>
|
|
|
|
namespace smo {
|
|
|
|
class Mind; // Forward declaration
|
|
class MarionetteThread;
|
|
class MindThread;
|
|
|
|
class ComponentThread
|
|
{
|
|
public:
|
|
enum ThreadId
|
|
{
|
|
MRNTT = 0,
|
|
DIRECTOR,
|
|
SIMULATOR,
|
|
SUBCONSCIOUS,
|
|
BODY,
|
|
WORLD,
|
|
N_ITEMS
|
|
};
|
|
|
|
protected:
|
|
ComponentThread(ThreadId _id)
|
|
: id(_id), name(getThreadName(_id)),
|
|
work(io_service)
|
|
{}
|
|
|
|
public:
|
|
virtual ~ComponentThread() = default;
|
|
|
|
void cleanup(void);
|
|
|
|
boost::asio::io_service& getIoService(void) { return io_service; }
|
|
|
|
static const std::shared_ptr<ComponentThread> getSelf(void);
|
|
static std::shared_ptr<MarionetteThread> getMrntt();
|
|
|
|
typedef void (mainFn)(ComponentThread &self);
|
|
|
|
// CPU management methods
|
|
static int getAvailableCpuCount();
|
|
|
|
typedef std::function<void()> mindShutdownIndOpCbFn;
|
|
// Intentionally doesn't take a callback.
|
|
void exceptionInd(const std::shared_ptr<ComponentThread> &faultyThread);
|
|
// Intentionally doesn't take a callback.
|
|
void userShutdownInd();
|
|
|
|
public:
|
|
ThreadId id;
|
|
std::string name;
|
|
boost::asio::io_service io_service;
|
|
boost::asio::io_service::work work;
|
|
std::atomic<bool> keepLooping;
|
|
|
|
static const std::string getThreadName(ThreadId id)
|
|
{
|
|
if (id < 0 || id >= ComponentThread::N_ITEMS)
|
|
{
|
|
throw std::runtime_error(std::string(__func__)
|
|
+ ": Invalid thread ID");
|
|
}
|
|
|
|
// Use function-local static to ensure proper initialization order
|
|
static const std::string threadNames[N_ITEMS] = {
|
|
"mrntt",
|
|
"director",
|
|
"simulator",
|
|
"subconscious",
|
|
"body",
|
|
"world"
|
|
};
|
|
|
|
return threadNames[id];
|
|
}
|
|
};
|
|
|
|
class MarionetteThread
|
|
: public std::enable_shared_from_this<MarionetteThread>,
|
|
public ComponentThread
|
|
{
|
|
public:
|
|
MarionetteThread()
|
|
: ComponentThread(MRNTT),
|
|
thread(main, std::ref(*this))
|
|
{
|
|
}
|
|
|
|
static void main(MarionetteThread& self);
|
|
void initializeTls(void);
|
|
|
|
public:
|
|
std::thread thread;
|
|
};
|
|
|
|
class MindThread
|
|
: public std::enable_shared_from_this<MindThread>, public ComponentThread
|
|
{
|
|
public:
|
|
enum class ThreadOp
|
|
{
|
|
START,
|
|
PAUSE,
|
|
RESUME,
|
|
EXIT,
|
|
JOLT,
|
|
N_ITEMS
|
|
};
|
|
|
|
MindThread(ThreadId _id, Mind& parent)
|
|
: ComponentThread(_id),
|
|
pinnedCpuId(-1),
|
|
pause_work(pause_io_service),
|
|
parent(parent),
|
|
thread(main, std::ref(*this))
|
|
{
|
|
}
|
|
|
|
static void main(MindThread& self);
|
|
void initializeTls(void);
|
|
|
|
Mind& getParent() const { return parent; }
|
|
|
|
// Thread management methods
|
|
typedef std::function<void()> threadLifetimeMgmtOpCbFn;
|
|
void startThreadReq(threadLifetimeMgmtOpCbFn callback);
|
|
void exitThreadReq(threadLifetimeMgmtOpCbFn callback);
|
|
void pauseThreadReq(threadLifetimeMgmtOpCbFn callback);
|
|
void resumeThreadReq(threadLifetimeMgmtOpCbFn callback);
|
|
|
|
/**
|
|
* JOLTs this thread to begin processing after global initialization.
|
|
*
|
|
* JOLTing is the mechanism that allows threads to enter their main
|
|
* event loops and set up TLS vars after all global constructors have
|
|
* completed. This prevents race conditions during system startup.
|
|
*/
|
|
void joltThreadReq(threadLifetimeMgmtOpCbFn callback);
|
|
|
|
// CPU management methods
|
|
void pinToCpu(int cpuId);
|
|
|
|
public:
|
|
int pinnedCpuId;
|
|
boost::asio::io_service pause_io_service;
|
|
boost::asio::io_service::work pause_work;
|
|
Mind& parent;
|
|
std::thread thread;
|
|
|
|
public:
|
|
class ThreadLifetimeMgmtOp;
|
|
class MindShutdownIndOp;
|
|
};
|
|
|
|
namespace mrntt {
|
|
extern std::shared_ptr<MarionetteThread> mrntt;
|
|
}
|
|
|
|
} // namespace smo
|
|
|
|
#endif // COMPONENT_THREAD_H
|