Nursery: Initial integration
StimulusProducer: syncAwaitAllSettlements should pump caller io_context
This commit is contained in:
@@ -1,161 +1,88 @@
|
||||
#include <config.h>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <componentThread.h>
|
||||
#include <adapters/boostAsio/deadlineTimerAReq.h>
|
||||
#include <deviceManager/deviceReattacher.h>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
#include <marionette/marionetteThread.h>
|
||||
#include <spinscale/co/nonViralCompletion.h>
|
||||
|
||||
namespace smo {
|
||||
namespace device {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr unsigned int reattachInFlightStaleThresholdMultiplier = 4;
|
||||
|
||||
} // namespace
|
||||
|
||||
DeviceReattacher::DeviceReattacher(
|
||||
DeviceManager& parent, std::shared_ptr<sscl::ComponentThread> ioThread)
|
||||
: parent(parent), ioThread(ioThread), timer(ioThread->getIoContext())
|
||||
: parent(parent),
|
||||
ioThread(ioThread), daemonTimer(ioThread->getIoContext())
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* The thread on which DeviceReattacher runs is whichever thread executes
|
||||
* the io_context that owns deadline_timer. Timer async_wait handlers
|
||||
* (onTimeout, holdReattachCReq, reattachKnownListCReq) are dispatched on
|
||||
* that thread. ioThread selects that io_context here; start() only arms
|
||||
* the timer on it.
|
||||
* deviceReattacherCDaemon is a dynamic posting non-viral coroutine: start()
|
||||
* passes ExplicitPostTarget{ioThread->getIoContext()} so the daemon body
|
||||
* always runs on ioThread. daemonTimer is reused each loop iteration.
|
||||
*/
|
||||
}
|
||||
|
||||
mrntt::MrnttNonViralNonPostingInvoker DeviceReattacher::reattachKnownListCReq(
|
||||
sscl::co::DynamicNonViralPostingInvoker
|
||||
DeviceReattacher::deviceReattacherCDaemon(
|
||||
[[maybe_unused]] sscl::co::ExplicitPostTarget postTarget,
|
||||
[[maybe_unused]] std::exception_ptr &exceptionPtr,
|
||||
[[maybe_unused]] std::function<void()> callback)
|
||||
[[maybe_unused]] std::function<void()> callback,
|
||||
sscl::SyncCancelerForAsyncWork &canceler)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* Non-posting: invoked from holdReattachCReq on the timer callback thread
|
||||
* (see ctor). Nested DeviceManager attach APIs still post to MRNTT as
|
||||
* needed via their own viral posting invokers.
|
||||
*/
|
||||
co_await parent.attachAllUnattachedDevicesFromKnownListCReq();
|
||||
boost::asio::io_context &timerIoContext =
|
||||
sscl::ComponentThread::getSelf()->getIoContext();
|
||||
const auto periodMs = boost::posix_time::milliseconds(
|
||||
CONFIG_MRNTT_DEVMGR_REATTACHER_PERIOD_MS);
|
||||
|
||||
while (!canceler.isCancellationRequested())
|
||||
{
|
||||
const bool expiredNormally = co_await
|
||||
adapters::boostAsio::getDeadlineTimerAReqAwaiter(
|
||||
timerIoContext, daemonTimer, periodMs);
|
||||
|
||||
if (!expiredNormally) {
|
||||
break;
|
||||
}
|
||||
|
||||
co_await parent.attachAllUnattachedDevicesFromKnownListCReq();
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
void DeviceReattacher::start()
|
||||
{
|
||||
deviceReattacherCanceler.startAcceptingWork();
|
||||
scheduleNextTimeout();
|
||||
}
|
||||
|
||||
void DeviceReattacher::stop()
|
||||
{
|
||||
{
|
||||
sscl::SpinLock::Guard guard(deviceReattacherCanceler.s.lock);
|
||||
reattachOpInFlight = false;
|
||||
deviceReattacherCanceler.s.rsrc.shouldContinue = false;
|
||||
/** EXPLANATION:
|
||||
* Do not call reattachCReqInvoker.reset() here. Forcibly destroying
|
||||
* the invoker would tear down an in-flight reattach coroutine frame
|
||||
* mid-operation. During normal program teardown the optional (and
|
||||
* its invoker) are destroyed with the rest of the binary anyway; leave
|
||||
* a running reattach time to finish if shutdown races with it.
|
||||
*/
|
||||
}
|
||||
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
void DeviceReattacher::scheduleNextTimeout()
|
||||
{
|
||||
if (deviceReattacherCanceler.isCancellationRequestedUnlocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule the next timeout using the configured period
|
||||
timer.expires_from_now(
|
||||
boost::posix_time::milliseconds(
|
||||
CONFIG_MRNTT_DEVMGR_REATTACHER_PERIOD_MS));
|
||||
|
||||
timer.async_wait(
|
||||
std::bind(&DeviceReattacher::onTimeout, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void DeviceReattacher::holdReattachCReq()
|
||||
{
|
||||
reattachOpInFlight = true;
|
||||
lastReattachReqTimestamp = std::chrono::steady_clock::now();
|
||||
|
||||
reattachCReqInvoker.reset();
|
||||
reattachCReqInvoker.emplace(reattachKnownListCReq(
|
||||
reattachLifetimeExceptionPtr,
|
||||
[this]()
|
||||
taskNursery.openAdmission();
|
||||
taskNursery.launch(
|
||||
[this](sscl::co::NonViralTaskNursery::Slot::Lease &lease)
|
||||
{
|
||||
sscl::co::NonViralCompletion nvc(reattachLifetimeExceptionPtr);
|
||||
return deviceReattacherCDaemon(
|
||||
sscl::co::ExplicitPostTarget{ioThread->getIoContext()},
|
||||
lease.getExceptionStorage(),
|
||||
lease.getCallerLambda(),
|
||||
lease.getSyncCanceler());
|
||||
},
|
||||
[](std::exception_ptr &exceptionPtr)
|
||||
{
|
||||
sscl::co::NonViralCompletion nvc(exceptionPtr);
|
||||
if (nvc.hasException())
|
||||
{
|
||||
try {
|
||||
nvc.checkAndRethrowException();
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "DeviceReattacher: " << e.what()
|
||||
<< std::endl;
|
||||
std::cerr << "DeviceReattacher: "
|
||||
<< e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
sscl::SpinLock::Guard guard(deviceReattacherCanceler.s.lock);
|
||||
reattachOpInFlight = false;
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
void DeviceReattacher::onTimeout(const boost::system::error_code& error)
|
||||
void DeviceReattacher::stop()
|
||||
{
|
||||
// Timer was cancelled, which is expected when stopping
|
||||
if (error == boost::asio::error::operation_aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
std::cerr << "DeviceReattacher: Timer error: " << error.message()
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
sscl::SpinLock::Guard guard(deviceReattacherCanceler.s.lock);
|
||||
if (deviceReattacherCanceler.isCancellationRequestedUnlocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto staleThreshold = std::chrono::milliseconds(
|
||||
reattachInFlightStaleThresholdMultiplier
|
||||
* CONFIG_MRNTT_DEVMGR_REATTACHER_PERIOD_MS);
|
||||
|
||||
// Attempt to reattach all unattached devices from the known list
|
||||
if (!reattachOpInFlight)
|
||||
{
|
||||
holdReattachCReq();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto elapsedSinceLastReattachReq =
|
||||
std::chrono::steady_clock::now() - lastReattachReqTimestamp;
|
||||
|
||||
if (elapsedSinceLastReattachReq >= staleThreshold)
|
||||
{
|
||||
std::cerr << "DeviceReattacher: Reattach op still in flight after "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
elapsedSinceLastReattachReq).count()
|
||||
<< "ms (threshold "
|
||||
<< staleThreshold.count()
|
||||
<< "ms); forcing a new reattach request."
|
||||
<< std::endl;
|
||||
holdReattachCReq();
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule the next timeout
|
||||
scheduleNextTimeout();
|
||||
daemonTimer.cancel();
|
||||
taskNursery.requestCancelOnAll();
|
||||
taskNursery.closeAdmission();
|
||||
taskNursery.syncAwaitAllSettlements(ioThread->getIoContext());
|
||||
}
|
||||
|
||||
} // namespace device
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
#ifndef DEVICEREATTACHER_H
|
||||
#define DEVICEREATTACHER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
#include <marionette/marionetteThread.h>
|
||||
#include <spinscale/co/dynamicPostingInvoker.h>
|
||||
#include <spinscale/co/nonViralTaskNursery.h>
|
||||
#include <spinscale/co/postTarget.h>
|
||||
#include <spinscale/syncCancelerForAsyncWork.h>
|
||||
|
||||
namespace smo {
|
||||
@@ -32,23 +30,17 @@ public:
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void scheduleNextTimeout();
|
||||
void onTimeout(const boost::system::error_code& error);
|
||||
void holdReattachCReq();
|
||||
|
||||
mrntt::MrnttNonViralNonPostingInvoker reattachKnownListCReq(
|
||||
sscl::co::DynamicNonViralPostingInvoker deviceReattacherCDaemon(
|
||||
sscl::co::ExplicitPostTarget postTarget,
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> callback);
|
||||
std::function<void()> callback,
|
||||
sscl::SyncCancelerForAsyncWork &canceler);
|
||||
|
||||
DeviceManager &parent;
|
||||
// io_context thread for timer and non-posting reattach shell (see ctor).
|
||||
// io_context thread for timer (see ctor).
|
||||
std::shared_ptr<sscl::ComponentThread> ioThread;
|
||||
sscl::SyncCancelerForAsyncWork deviceReattacherCanceler;
|
||||
boost::asio::deadline_timer timer;
|
||||
std::exception_ptr reattachLifetimeExceptionPtr;
|
||||
std::optional<mrntt::MrnttNonViralNonPostingInvoker> reattachCReqInvoker;
|
||||
bool reattachOpInFlight = false;
|
||||
std::chrono::steady_clock::time_point lastReattachReqTimestamp{};
|
||||
sscl::co::NonViralTaskNursery taskNursery;
|
||||
boost::asio::deadline_timer daemonTimer;
|
||||
};
|
||||
|
||||
} // namespace device
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <spinscale/component.h>
|
||||
#include <spinscale/co/nonViralTaskNursery.h>
|
||||
#include <marionette/marionetteThread.h>
|
||||
|
||||
namespace sscl {
|
||||
@@ -29,8 +30,10 @@ public:
|
||||
{}
|
||||
~MarionetteComponent() = default;
|
||||
|
||||
void holdInitializeCReq(std::function<void()> completion);
|
||||
void holdFinalizeCReq(std::function<void()> completion);
|
||||
void holdInitializeCReq(
|
||||
std::function<void(std::exception_ptr &exceptionPtr)> completion);
|
||||
void holdFinalizeCReq(
|
||||
std::function<void(std::exception_ptr &exceptionPtr)> completion);
|
||||
|
||||
MrnttNonViralPostingInvoker initializeCReq(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
@@ -58,12 +61,7 @@ protected:
|
||||
private:
|
||||
std::unique_ptr<boost::asio::signal_set> signals;
|
||||
bool callShutdownSalmanoff = false;
|
||||
std::optional<MrnttNonViralPostingInvoker> initializeCReqInvoker;
|
||||
std::optional<MrnttNonViralPostingInvoker> finalizeCReqInvoker;
|
||||
|
||||
public:
|
||||
std::exception_ptr initializeLifetimeExceptionPtr;
|
||||
std::exception_ptr finalizeLifetimeExceptionPtr;
|
||||
sscl::co::NonViralTaskNursery taskNursery;
|
||||
};
|
||||
|
||||
extern std::shared_ptr<sscl::PuppeteerThread> thread;
|
||||
|
||||
@@ -30,33 +30,29 @@ void assertMarionetteThread()
|
||||
} // namespace
|
||||
|
||||
void MarionetteComponent::holdInitializeCReq(
|
||||
std::function<void()> completion)
|
||||
std::function<void(std::exception_ptr &exceptionPtr)> completion)
|
||||
{
|
||||
initializeLifetimeExceptionPtr = nullptr;
|
||||
initializeCReqInvoker.emplace(initializeCReq(
|
||||
initializeLifetimeExceptionPtr,
|
||||
[completion = std::move(completion)]()
|
||||
taskNursery.launch(
|
||||
[this](sscl::co::NonViralTaskNursery::Slot::Lease &lease)
|
||||
{
|
||||
sscl::co::NonViralCompletion nvc(
|
||||
mrntt.initializeLifetimeExceptionPtr);
|
||||
nvc.checkAndRethrowException();
|
||||
completion();
|
||||
}));
|
||||
return initializeCReq(
|
||||
lease.getExceptionStorage(),
|
||||
lease.getCallerLambda());
|
||||
},
|
||||
std::move(completion));
|
||||
}
|
||||
|
||||
void MarionetteComponent::holdFinalizeCReq(
|
||||
std::function<void()> completion)
|
||||
std::function<void(std::exception_ptr &exceptionPtr)> completion)
|
||||
{
|
||||
finalizeLifetimeExceptionPtr = nullptr;
|
||||
finalizeCReqInvoker.emplace(finalizeCReq(
|
||||
finalizeLifetimeExceptionPtr,
|
||||
[completion = std::move(completion)]()
|
||||
taskNursery.launch(
|
||||
[this](sscl::co::NonViralTaskNursery::Slot::Lease &lease)
|
||||
{
|
||||
sscl::co::NonViralCompletion nvc(
|
||||
mrntt.finalizeLifetimeExceptionPtr);
|
||||
nvc.checkAndRethrowException();
|
||||
completion();
|
||||
}));
|
||||
return finalizeCReq(
|
||||
lease.getExceptionStorage(),
|
||||
lease.getCallerLambda());
|
||||
},
|
||||
std::move(completion));
|
||||
}
|
||||
|
||||
MrnttNonViralPostingInvoker MarionetteComponent::initializeCReq(
|
||||
@@ -107,7 +103,12 @@ void MarionetteComponent::exceptionInd()
|
||||
[]
|
||||
{
|
||||
mrntt.holdFinalizeCReq(
|
||||
[]() { marionetteFinalizeReqCb(true); });
|
||||
[](std::exception_ptr &exceptionPtr)
|
||||
{
|
||||
sscl::co::NonViralCompletion nvc(exceptionPtr);
|
||||
nvc.checkAndRethrowException();
|
||||
marionetteFinalizeReqCb(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <spinscale/component.h>
|
||||
#include <spinscale/componentThread.h>
|
||||
#include <marionette/marionette.h>
|
||||
#include <spinscale/co/nonViralCompletion.h>
|
||||
#include <spinscale/runtime.h>
|
||||
#include <componentThread.h>
|
||||
#include <mindManager/mindManager.h>
|
||||
@@ -33,7 +34,12 @@ void marionetteInitializeReqCb(bool success)
|
||||
<< '\n';
|
||||
|
||||
mrntt.holdFinalizeCReq(
|
||||
[]() { marionetteFinalizeReqCb(true); });
|
||||
[](std::exception_ptr &exceptionPtr)
|
||||
{
|
||||
sscl::co::NonViralCompletion nvc(exceptionPtr);
|
||||
nvc.checkAndRethrowException();
|
||||
marionetteFinalizeReqCb(true);
|
||||
});
|
||||
}
|
||||
|
||||
void marionetteFinalizeReqCb(bool success)
|
||||
@@ -82,7 +88,12 @@ void MarionetteComponent::postJoltHook()
|
||||
break;
|
||||
}
|
||||
mrntt.holdFinalizeCReq(
|
||||
[]() { marionetteFinalizeReqCb(true); });
|
||||
[](std::exception_ptr &exceptionPtr)
|
||||
{
|
||||
sscl::co::NonViralCompletion nvc(exceptionPtr);
|
||||
nvc.checkAndRethrowException();
|
||||
marionetteFinalizeReqCb(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -128,14 +139,24 @@ void MarionetteComponent::preLoopHook()
|
||||
smo::initializeSalmanoff();
|
||||
callShutdownSalmanoff = true;
|
||||
|
||||
taskNursery.openAdmission();
|
||||
|
||||
holdInitializeCReq(
|
||||
[] { marionetteInitializeReqCb(true); });
|
||||
[](std::exception_ptr &exceptionPtr)
|
||||
{
|
||||
sscl::co::NonViralCompletion nvc(exceptionPtr);
|
||||
nvc.checkAndRethrowException();
|
||||
marionetteInitializeReqCb(true);
|
||||
});
|
||||
|
||||
std::cout << "PuppeteerThread::main: Entering event loop" << "\n";
|
||||
}
|
||||
|
||||
void MarionetteComponent::postLoopHook()
|
||||
{
|
||||
taskNursery.requestCancelOnAll();
|
||||
taskNursery.closeAdmission();
|
||||
|
||||
std::cout << "PuppeteerThread::main: Exited event loop" << "\n";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user