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.
224 lines
6.3 KiB
C++
224 lines
6.3 KiB
C++
#include <config.h>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <exception>
|
|
#include <opts.h>
|
|
#include <typeinfo>
|
|
#include <boost/asio/signal_set.hpp>
|
|
#include <asynchronousBridge.h>
|
|
#include <mind.h>
|
|
#include <componentThread.h>
|
|
#include <marionette/marionette.h>
|
|
#include <salmanoff.h>
|
|
|
|
namespace smo {
|
|
|
|
CrtCommandLineArgs crtCommandLineArgs(0, nullptr, nullptr);
|
|
|
|
void CrtCommandLineArgs::set(int argc, char *argv[], char *envp[])
|
|
{
|
|
crtCommandLineArgs = CrtCommandLineArgs(argc, argv, envp);
|
|
}
|
|
|
|
// Global Mind instance
|
|
std::shared_ptr<Mind> globalMind;
|
|
|
|
namespace mrntt {
|
|
std::atomic<int> exitCode;
|
|
// Global marionette thread instance
|
|
std::shared_ptr<MarionetteThread> mrntt = std::make_shared<MarionetteThread>();
|
|
|
|
void exitMarionetteLoop()
|
|
{
|
|
mrntt::mrntt->keepLooping = false;
|
|
mrntt::mrntt->getIoService().stop();
|
|
std::cout << "Mrntt: Signaled main loop to exit." << "\n";
|
|
}
|
|
|
|
} // namespace mrntt
|
|
|
|
void MarionetteThread::main(MarionetteThread& self)
|
|
{
|
|
// Wait for CRT's main() to post us the command line args.
|
|
std::cout << __func__ << ": Waiting for command line JOLT" << std::endl;
|
|
self.getIoService().run();
|
|
self.initializeTls();
|
|
mrntt::exitCode = EXIT_SUCCESS;
|
|
static boost::asio::signal_set signals(self.getIoService(), SIGINT);
|
|
bool callFinalizeReq = false, callShutdownSalmanoff = false;
|
|
|
|
try {
|
|
// Register SIGINT (Ctrl+C) and SIGSEGV handlers
|
|
signals.async_wait(
|
|
[&self](const boost::system::error_code& ec, int signal)
|
|
{
|
|
if (ec) { return; }
|
|
|
|
switch (signal) {
|
|
case SIGINT:
|
|
std::cerr << "SIGINT (Ctrl+C) received. Initiating "
|
|
"shutdown..." << '\n';
|
|
break;
|
|
case SIGSEGV:
|
|
std::cerr << "FATAL: Segmentation fault detected. "
|
|
"Initiating shutdown..." << '\n';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
self.userShutdownInd();
|
|
}
|
|
);
|
|
|
|
OptionParser &options = OptionParser::getOptions();
|
|
|
|
std::cout << __func__ << ": " << PACKAGE_NAME << " " << PACKAGE_VERSION
|
|
<< std::endl;
|
|
|
|
options.parseArguments(
|
|
crtCommandLineArgs.argc, crtCommandLineArgs.argv,
|
|
crtCommandLineArgs.envp);
|
|
|
|
std::cout << __func__ << ": " << options.stringifyOptions()
|
|
<< std::endl;
|
|
|
|
if (options.printUsage) {
|
|
throw JustPrintUsageNoError(options);
|
|
}
|
|
|
|
initializeSalmanoff();
|
|
callShutdownSalmanoff = true;
|
|
|
|
self.getIoService().post([]()
|
|
{
|
|
/** EXPLANATION:
|
|
* Initialize Salmanoff's Manager classes first.
|
|
* Manager classes' initialization is synchronous in nature, so we
|
|
* don't need the minds to be running to initialize them.
|
|
*
|
|
* Then we initialize the Minds. Minds are asynchronous and they
|
|
* call upon the async methods of the Manager classes. Device
|
|
* attachment is actually Mind specific and not Smo-global
|
|
*
|
|
* It is arguable whether library loading is Mind specific or
|
|
* Smo-global. You could argue that libraries should be loaded and
|
|
* unloaded dynamically as-needed by the bodies and worlds of
|
|
* particular Minds. You could also argue that we should load all
|
|
* libraries at startup and unload them at shutdown.
|
|
*
|
|
* The latter is cleaner and more resource-respecting. The former is
|
|
* easier to implement.
|
|
*/
|
|
// Create new Mind instance just before initializeReq
|
|
globalMind = std::make_shared<Mind>();
|
|
globalMind->initializeReq(
|
|
[](bool success)
|
|
{
|
|
if (!success)
|
|
{
|
|
std::cerr << "Failed to initialize Mind object "
|
|
"(threads)" << '\n';
|
|
}
|
|
|
|
std::cout << "Mrntt: Mind object (threads) initialized."
|
|
<< '\n';
|
|
});
|
|
});
|
|
|
|
std::cout << __func__ << ": Entering event loop" << "\n";
|
|
|
|
/* We loop here because when an exception occurs in mrntt, we need to
|
|
* both direct the mind 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 mind threads, and to post
|
|
* messages to our own event loop to initiate our own orderly exit.
|
|
*/
|
|
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::exitCode = EXIT_FAILURE;
|
|
self.exceptionInd(self.shared_from_this());
|
|
}
|
|
}
|
|
|
|
std::cout << __func__ << ": Exited event loop" << "\n";
|
|
}
|
|
catch (const OptionParser::Exception& e)
|
|
{
|
|
std::ostream *out = &std::cout;
|
|
std::string outUsageMsg;
|
|
|
|
if (typeid(e) == typeid(OptionsParserError))
|
|
{
|
|
mrntt::exitCode = EXIT_FAILURE;
|
|
out = &std::cerr;
|
|
outUsageMsg = std::string(__func__) + ": ";
|
|
}
|
|
|
|
*out << outUsageMsg << e.what() << std::endl;
|
|
callFinalizeReq = true;
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
std::cerr << __func__ << ": Exception occurred: " << e.what()
|
|
<< std::endl;
|
|
mrntt::exitCode = EXIT_FAILURE;
|
|
callShutdownSalmanoff = callFinalizeReq = true;
|
|
}
|
|
catch (...)
|
|
{
|
|
std::cerr << __func__ << ": Unknown exception occurred" << std::endl;
|
|
mrntt::exitCode = EXIT_FAILURE;
|
|
callShutdownSalmanoff = callFinalizeReq = true;
|
|
}
|
|
|
|
if (callFinalizeReq)
|
|
{
|
|
globalMind->finalizeReq([](bool success)
|
|
{
|
|
if (!success) {
|
|
std::cerr << "Failed to finalize Mind object (threads)" << '\n';
|
|
}
|
|
mrntt::mrntt->getIoService().stop();
|
|
});
|
|
self.getIoService().reset();
|
|
self.getIoService().run();
|
|
}
|
|
|
|
if (callShutdownSalmanoff) {
|
|
shutdownSalmanoff();
|
|
}
|
|
}
|
|
|
|
} // namespace smo
|