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:
2025-07-28 07:20:44 -04:00
parent 513405a831
commit 36c79f3a2e
9 changed files with 469 additions and 178 deletions
+20 -105
View File
@@ -1,111 +1,26 @@
#include <iostream>
#include <exception>
#include <thread>
#include <mutex>
#include <unordered_map>
#include <condition_variable>
#include <boost/asio.hpp>
#include <opts.h>
#include <mind.h>
#include <deviceManager/deviceManager.h>
#include <senseApis/senseApiManager.h>
#include "componentThread.h"
#include <componentThread.h>
#include <marionette/marionette.h>
namespace smo {
static int initializeSalmanoff(int argc, char **argv, char **envp);
} // namespace smo
int main(int argc, char **argv, char **envp)
int main(int argc, char *argv[], char *envp[])
{
try {
std::cout << __func__ << ": Entering main()" << std::endl;
boost::asio::io_service mrntLoop;
boost::asio::io_service::work work(mrntLoop);
// Validate thread IDs
smo::ComponentThread::validateThreadIds();
// Post initializeSalmanoff to mrntLoop
mrntLoop.post([&]()
/* We don't do anything inside of main()
* Main merely waits for the marionette thread to exit.
*/
std::cout << "CRT:" << __func__ << ": about to JOLT Mrntt with cmdline args"
<< '\n';
smo::mrntt::mrntt->getIoService().post(
[argc, argv, envp]()
{
int ret = smo::initializeSalmanoff(argc, argv, envp);
if (ret != 0)
{
std::cerr << "Initialization failed with code: "
<< ret << std::endl;
std::exit(ret);
}
});
std::cout << "Mrntt:" << __func__ << ":JOLTED: setting cmdline args"
<< '\n';
smo::CrtCommandLineArgs::set(argc, argv, envp);
smo::mrntt::mrntt->getIoService().stop();
}
);
mrntLoop.run();
}
catch (const std::exception& e)
{
std::cerr << __func__ << ": Exception occurred: " << e.what()
<< std::endl;
return EXIT_FAILURE;
}
catch (...)
{
std::cerr << __func__ << ": Unknown exception occurred" << std::endl;
return EXIT_FAILURE;
}
std::cout << __func__ << ": Exiting normally" << std::endl;
return 0;
smo::mrntt::mrntt->thread.join();
std::cout << "CRT:" << __func__ << ": Mrntt exited with code '"
<< smo::mrntt::exitCode << "'\n";
return smo::mrntt::exitCode;
}
namespace smo {
static int initializeSalmanoff(int argc, char **argv, char **envp)
{
std::cout << __func__ << ": Entering" << std::endl;
using namespace smo;
OptionParser &options = OptionParser::getOptions();
smo::Mind mind;
std::cout << PACKAGE_NAME << " " << PACKAGE_VERSION << std::endl;
try {
options.parseArguments(argc, argv, envp);
std::cout << options.stringifyOptions() << std::endl;
}
catch (const std::invalid_argument& e)
{
std::cerr << __func__ << ": Exception occurred: " << e.what() << '\n'
<< options.getUsage() << '\n';
return EXIT_FAILURE;
}
if (options.printUsage)
{
std::cout << options.getUsage() << std::endl;
return EXIT_SUCCESS;
}
device::DeviceManager::getInstance().collateAllDeviceSpecs();
device::DeviceManager::getInstance().parseAllDeviceSpecs();
std::cout << device::DeviceManager::stringifyDeviceSpecs() << std::endl;
sense_api::SenseApiManager::getInstance().loadAllSenseApiLibsFromOptions();
std::cout << sense_api::SenseApiManager::getInstance().stringifyLibs()
<< std::endl;
std::cerr << "About to initializeAllSenseApiLibs" << std::endl;
sense_api::SenseApiManager::getInstance().initializeAllSenseApiLibs();
std::cerr << "About to attachAllSenseDevicesFromSpecs" << std::endl;
sense_api::SenseApiManager::getInstance().attachAllSenseDevicesFromSpecs();
std::cerr << "Done attachAllSenseDevicesFromSpecs" << std::endl;
/* Start the threads */
for (const auto& [id, componentThread]
: smo::ComponentThread::componentThreads) {
smo::ComponentThread::signalThread(id);
}
std::cout << __func__ << ": Exiting" << std::endl;
return 0;
}
} // namespace smo