Libspinscale: Initial top-level SMO port to coroutine framework

We haven't ported everything. Just the top-level methods. We'll
dig in to the leaf stuff later. Surprisingly, this all went without
any real difficulties.

Runs like a charm on first try.
This commit is contained in:
2026-05-24 16:12:29 -04:00
parent c539e6e924
commit cde2737876
44 changed files with 1296 additions and 1530 deletions
+77 -176
View File
@@ -1,190 +1,92 @@
#include <cstdlib>
#include <iostream>
#include <spinscale/asynchronousContinuation.h>
#include <spinscale/asynchronousLoop.h>
#include <spinscale/callback.h>
#include <spinscale/callableTracer.h>
#include <spinscale/component.h>
#include <marionette/marionette.h>
#include <stdexcept>
#include <boost/asio/post.hpp>
#include <componentThread.h>
#include <deviceManager/deviceManager.h>
#include <marionette/marionette.h>
#include <marionette/marionetteThread.h>
#include <mindManager/mindManager.h>
#include <spinscale/componentThread.h>
namespace smo {
namespace mrntt {
class MarionetteComponent::MrnttLifetimeMgmtOp
: public sscl::PostedAsynchronousContinuation<mrnttLifetimeMgmtOpCbFn>
namespace {
void assertMarionetteThread()
{
public:
MrnttLifetimeMgmtOp(
MarionetteComponent &parent,
const std::shared_ptr<sscl::ComponentThread> &caller,
sscl::Callback<mrnttLifetimeMgmtOpCbFn> callback)
: sscl::PostedAsynchronousContinuation<mrnttLifetimeMgmtOpCbFn>(
caller, callback),
parent(parent)
{}
private:
MarionetteComponent &parent;
public:
void initializeReq1_posted(
[[maybe_unused]] std::shared_ptr<MrnttLifetimeMgmtOp> context
)
auto self = sscl::ComponentThread::getSelf();
if (self->id != SmoThreadId::MRNTT)
{
auto self = sscl::ComponentThread::getSelf();
if (self->id != smo::SmoThreadId::MRNTT)
{
throw std::runtime_error(std::string(__func__)
+ ": Must be executed on Marionette thread");
}
smo::mind::globalMind = std::make_shared<smo::Mind>();
smo::mind::globalMind->initializeReq({context, std::bind(
&MrnttLifetimeMgmtOp::initializeReq2,
this, context, std::placeholders::_1)});
}
void initializeReq2(
std::shared_ptr<MrnttLifetimeMgmtOp> context,
bool success
)
{
if (!success)
{
std::cerr << __func__ << ": Failed to initialize globalMind"
<< std::endl;
context->callOriginalCb(false);
return;
}
smo::device::DeviceManager::getInstance().initializeDeviceReattacher();
// Call negtrinEventInd on the Director in the final callback
smo::mind::globalMind->director.negtrinEventInd();
context->callOriginalCb(success);
}
void finalizeReq1_posted(
[[maybe_unused]] std::shared_ptr<MrnttLifetimeMgmtOp> context
)
{
auto self = sscl::ComponentThread::getSelf();
if (self->id != smo::SmoThreadId::MRNTT)
{
throw std::runtime_error(std::string(__func__)
+ ": Must be executed on Marionette thread");
}
smo::device::DeviceManager::getInstance().finalizeDeviceReattacher();
/** FIXME:
* It may be necessary to add a delay here to ensure that all in-flight
* timer timeouts have finished executing? Or some other mechanism.
*
* We need some way to ensure that in-flight timeouts don't get fired
* during the finalize sequence. This is because they may depend on
* state that is being finalized or has been finalized at the point
* when they timeout.
*
* This seems to be actually happening with the delayed calls to
* AttachDeviceReq::attachDeviceReq2() inside of livoxGen1.cpp.
*
* One tactic might be to shut down device reattacher before finalizing
* and pause for a bit before continuing to shutdown other components.
*/
smo::mind::globalMind->finalizeReq({context, std::bind(
&MrnttLifetimeMgmtOp::finalizeReq2,
this, context, std::placeholders::_1)});
}
void finalizeReq2(
std::shared_ptr<MrnttLifetimeMgmtOp> context,
bool success
)
{
if (!success)
{
std::cerr << __func__ << ": globalMind finalization failed"
<< std::endl;
context->callOriginalCb(false);
return;
}
context->callOriginalCb(success);
}
};
class MarionetteComponent::TerminationEvent
: public sscl::PostedAsynchronousContinuation<mrnttLifetimeMgmtOpCbFn>
{
public:
TerminationEvent(
const std::shared_ptr<sscl::ComponentThread> &caller)
: sscl::PostedAsynchronousContinuation<mrnttLifetimeMgmtOpCbFn>(
caller, {nullptr, nullptr})
{}
public:
void exceptionInd1_posted(
[[maybe_unused]] std::shared_ptr<TerminationEvent> context
)
{
auto self = sscl::ComponentThread::getSelf();
if (self->id != smo::SmoThreadId::MRNTT)
{
throw std::runtime_error(std::string(__func__)
+ ": Must be executed on Marionette thread");
}
smo::mrntt::mrntt.finalizeReq({nullptr, std::bind(
&smo::mrntt::marionetteFinalizeReqCb,
std::placeholders::_1)});
}
};
void MarionetteComponent::initializeReq(
sscl::Callback<mrnttLifetimeMgmtOpCbFn> callback)
{
auto mrntt = sscl::ComponentThread::getSelf();
if (mrntt->id != smo::SmoThreadId::MRNTT)
{
throw std::runtime_error(std::string(__func__)
throw std::runtime_error(
std::string(__func__)
+ ": Must be executed on Marionette thread");
}
auto request = std::make_shared<MrnttLifetimeMgmtOp>(
*this, mrntt, callback);
mrntt->getIoService().post(
STC(std::bind(
&MrnttLifetimeMgmtOp::initializeReq1_posted,
request.get(), request)));
}
void MarionetteComponent::finalizeReq(
sscl::Callback<mrnttLifetimeMgmtOpCbFn> callback)
{
auto mrntt = sscl::ComponentThread::getSelf();
} // namespace
if (mrntt->id != smo::SmoThreadId::MRNTT)
void MarionetteComponent::holdInitializeCReq(
std::function<void()> completion)
{
initializeCReqInvoker.emplace(initializeCReq(
initializeLifetimeExceptionPtr, std::move(completion)));
}
void MarionetteComponent::holdFinalizeCReq(
std::function<void()> completion)
{
finalizeCReqInvoker.emplace(finalizeCReq(
finalizeLifetimeExceptionPtr, std::move(completion)));
}
MrnttNonViralPostingInvoker MarionetteComponent::initializeCReq(
[[maybe_unused]] std::exception_ptr &exceptionPtr,
[[maybe_unused]] std::function<void()> callback)
{
assertMarionetteThread();
smo::mind::globalMind = std::make_shared<smo::Mind>();
bool mindInitialized = co_await smo::mind::globalMind->initializeCReq();
if (!mindInitialized)
{
throw std::runtime_error(std::string(__func__)
+ ": Must be executed on Marionette thread");
std::cerr << __func__ << ": Failed to initialize globalMind"
<< std::endl;
co_return;
}
auto request = std::make_shared<MrnttLifetimeMgmtOp>(
*this, mrntt, callback);
smo::device::DeviceManager::getInstance().initializeDeviceReattacher();
mrntt->getIoService().post(
STC(std::bind(
&MrnttLifetimeMgmtOp::finalizeReq1_posted,
request.get(), request)));
// Call negtrinEventInd on the Director in the final callback
smo::mind::globalMind->director.negtrinEventInd();
co_return;
}
MrnttNonViralPostingInvoker MarionetteComponent::finalizeCReq(
[[maybe_unused]] std::exception_ptr &exceptionPtr,
[[maybe_unused]] std::function<void()> callback)
{
assertMarionetteThread();
smo::device::DeviceManager::getInstance().finalizeDeviceReattacher();
if (!smo::mind::globalMind)
{
co_return;
}
bool mindFinalized = co_await smo::mind::globalMind->finalizeCReq();
if (!mindFinalized)
{
std::cerr << __func__ << ": globalMind finalization failed"
<< std::endl;
}
co_return;
}
void MarionetteComponent::handleLoopExceptionHook()
@@ -195,16 +97,15 @@ void MarionetteComponent::handleLoopExceptionHook()
void MarionetteComponent::exceptionInd()
{
auto faultyThread = sscl::ComponentThread::getSelf();
auto mrntt = sscl::ComponentThread::getPptr();
auto puppeteer = sscl::ComponentThread::getPptr();
auto request = std::make_shared<TerminationEvent>(
faultyThread);
mrntt->getIoService().post(
STC(std::bind(
&TerminationEvent::exceptionInd1_posted,
request.get(), request)));
boost::asio::post(
puppeteer->getIoService(),
[]
{
mrntt.holdFinalizeCReq(
[]() { marionetteFinalizeReqCb(true); });
});
}
} // namespace mrntt
+11 -10
View File
@@ -33,9 +33,8 @@ void marionetteInitializeReqCb(bool success)
std::cerr << __func__ << ": Failed to initialize Marionette. Shutting down."
<< '\n';
mrntt.finalizeReq({nullptr, std::bind(
&smo::mrntt::marionetteFinalizeReqCb,
std::placeholders::_1)});
mrntt.holdFinalizeCReq(
[]() { marionetteFinalizeReqCb(true); });
}
void marionetteFinalizeReqCb(bool success)
@@ -83,9 +82,8 @@ void MarionetteComponent::postJoltHook()
default:
break;
}
mrntt.finalizeReq({nullptr, std::bind(
&marionetteFinalizeReqCb,
std::placeholders::_1)});
mrntt.holdFinalizeCReq(
[]() { marionetteFinalizeReqCb(true); });
});
}
@@ -110,7 +108,7 @@ void MarionetteComponent::tryBlock1Hook()
void MarionetteComponent::preLoopHook()
{
/** EXPLANATION:
/** 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.
@@ -131,9 +129,12 @@ void MarionetteComponent::preLoopHook()
smo::initializeSalmanoff();
callShutdownSalmanoff = true;
initializeReq(sscl::Callback<mrnttLifetimeMgmtOpCbFn>{
nullptr,
std::bind(&marionetteInitializeReqCb, std::placeholders::_1)});
holdInitializeCReq(
[]
{
marionetteInitializeReqCb(
!mrntt.initializeLifetimeExceptionPtr);
});
std::cout << "PuppeteerThread::main: Entering event loop" << "\n";
}