Threading: run all code in PThreads, add JOLTing & exception bubbling
This commit significantly restructures the way we setup threading in SMO. We now don't use the CRT main() thread at all. It's only used as a mechanism to ensure that Marionette doesn't execute before global constructors have been executed. JOLTing: This is a simple ASIO post()ed message that makes each thread setup its thread-local data pointer to its own ComponentThread object, and then enter its main ASIO run() loop to await commands from Marionette. Exception bubbling: We now cleanly cause mind threads to report their exceptions to marionette, so that marionette can cleanly shut the mind down in an orderly fashion. Thread Control messaging API: A namespace of asynchronous messages to be post()ed to threads to control them. It enables us to pause and resume threads. This will be very useful for Marionette when we add the ability for it to suspend Salmanoff's running mind, inject new goals, inspect current state, etc; and then resume the mind's execution.
This commit is contained in:
@@ -1,67 +1,130 @@
|
||||
#ifndef COMPONENT_THREAD_H
|
||||
#define COMPONENT_THREAD_H
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <condition_variable>
|
||||
#include <boost/asio.hpp>
|
||||
#include <stdexcept>
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class ComponentThread
|
||||
: public std::enable_shared_from_this<ComponentThread>
|
||||
{
|
||||
public:
|
||||
ComponentThread()
|
||||
: work(io_service), startupSync(),
|
||||
thread(ComponentThread::main, std::ref(*this))
|
||||
enum ThreadId
|
||||
{
|
||||
MRNTT = 0,
|
||||
DIRECTOR,
|
||||
SIMULATOR,
|
||||
SUBCONSCIOUS,
|
||||
BODY,
|
||||
WORLD,
|
||||
N_ITEMS
|
||||
};
|
||||
|
||||
ComponentThread(ThreadId id)
|
||||
: id(id), name(getThreadName(id)),
|
||||
work(io_service), pause_work(pause_io_service),
|
||||
thread(
|
||||
((id == MRNTT) ? marionetteMain : main),
|
||||
std::ref(*this))
|
||||
{}
|
||||
|
||||
void cleanup(void);
|
||||
|
||||
boost::asio::io_service& getIoService(void) { return io_service; }
|
||||
|
||||
static boost::asio::io_service& getEventLoop(
|
||||
std::thread::id id = std::this_thread::get_id())
|
||||
void initializeTls(void);
|
||||
const std::shared_ptr<ComponentThread> getSelf(void);
|
||||
|
||||
static std::shared_ptr<ComponentThread> getComponentThread(
|
||||
ThreadId id = N_ITEMS)
|
||||
{
|
||||
auto it = componentThreads.find(id);
|
||||
if (it == componentThreads.end())
|
||||
if (id < 0 || id > N_ITEMS)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Thread ID not found in componentThreads map");
|
||||
+ ": Invalid thread ID");
|
||||
}
|
||||
|
||||
return it->second.getIoService();
|
||||
return componentThreads[id];
|
||||
}
|
||||
|
||||
static void main(ComponentThread &self);
|
||||
static void signalThread(std::thread::id id);
|
||||
static void validateThreadIds(void);
|
||||
// Overload: search by name
|
||||
static std::shared_ptr<ComponentThread> getComponentThread(
|
||||
const std::string& name)
|
||||
{
|
||||
for (auto& thread : componentThreads) {
|
||||
if (thread->name == name) { return thread; }
|
||||
}
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Thread name not found in componentThreads map");
|
||||
}
|
||||
|
||||
static boost::asio::io_service& getEventLoop(
|
||||
ThreadId id = MRNTT)
|
||||
{
|
||||
return getComponentThread(id)->getIoService();
|
||||
}
|
||||
|
||||
|
||||
typedef void (mainFn)(ComponentThread &self);
|
||||
static mainFn main, marionetteMain;
|
||||
|
||||
// Thread management methods
|
||||
void startThreadReq(std::function<void()> callback = nullptr);
|
||||
void exitThreadReq(std::function<void()> callback = nullptr);
|
||||
void pauseThreadReq(std::function<void()> callback = nullptr);
|
||||
void resumeThreadReq(std::function<void()> callback = nullptr);
|
||||
void exceptionInd(ComponentThread& thread);
|
||||
|
||||
public:
|
||||
ThreadId id;
|
||||
std::string name;
|
||||
boost::asio::io_service io_service;
|
||||
boost::asio::io_service::work work;
|
||||
struct StartupSync {
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
bool ready;
|
||||
|
||||
StartupSync() : ready(false) {}
|
||||
} startupSync;
|
||||
boost::asio::io_service pause_io_service;
|
||||
boost::asio::io_service::work pause_work;
|
||||
std::atomic<bool> keepLooping;
|
||||
|
||||
/* Always ensure that this is last so that the thread is spawned after
|
||||
* everything else.
|
||||
* everything else is constructed.
|
||||
*/
|
||||
std::thread thread;
|
||||
static std::unordered_map<std::thread::id, ComponentThread&> componentThreads;
|
||||
static std::array<std::shared_ptr<ComponentThread>, ComponentThread::N_ITEMS>
|
||||
componentThreads;
|
||||
|
||||
static const std::string threadNames[ComponentThread::N_ITEMS];
|
||||
static const std::string getThreadName(ThreadId id)
|
||||
{
|
||||
if (id < 0 || id >= ComponentThread::N_ITEMS)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Invalid thread ID");
|
||||
}
|
||||
return threadNames[id];
|
||||
}
|
||||
};
|
||||
|
||||
namespace mrntt {
|
||||
extern std::shared_ptr<ComponentThread> mrntt;
|
||||
}
|
||||
namespace director {
|
||||
extern ComponentThread director;
|
||||
extern std::shared_ptr<ComponentThread> director;
|
||||
}
|
||||
namespace simulator {
|
||||
extern ComponentThread canvas;
|
||||
extern std::shared_ptr<ComponentThread> canvas;
|
||||
}
|
||||
namespace subconscious {
|
||||
extern ComponentThread subconscious;
|
||||
extern std::shared_ptr<ComponentThread> subconscious;
|
||||
}
|
||||
namespace body {
|
||||
extern std::shared_ptr<ComponentThread> body;
|
||||
}
|
||||
namespace world {
|
||||
extern std::shared_ptr<ComponentThread> world;
|
||||
}
|
||||
|
||||
} // namespace smo
|
||||
|
||||
Reference in New Issue
Block a user