#include #include #include #include #include #include #include #include namespace smo { namespace device { namespace { constexpr unsigned int reattachInFlightStaleThresholdMultiplier = 4; } // namespace DeviceReattacher::DeviceReattacher( DeviceManager& parent, std::shared_ptr ioThread) : parent(parent), ioThread(ioThread), shouldContinue(false), timer(ioThread->getIoService()) { } mrntt::MrnttNonViralPostingInvoker DeviceReattacher::reattachKnownListCReq( [[maybe_unused]] std::exception_ptr &exceptionPtr, [[maybe_unused]] std::function callback) { sscl::MultiOperationResultSet results = co_await parent.attachAllUnattachedDevicesFromKnownListCReq(); if (results.nTotal > 0) { std::cout << "DeviceReattacher: Successfully reattached " << results.nSucceeded << " of " << results.nTotal << " devices" << std::endl; } co_return; } void DeviceReattacher::start() { shouldContinue = true; scheduleNextTimeout(); } void DeviceReattacher::stop() { { sscl::SpinLock::Guard lock(shouldContinueLock); shouldContinue = false; reattachOpInFlight = 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 (!shouldContinue) { 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]() { sscl::SpinLock::Guard lock(shouldContinueLock); reattachOpInFlight = false; })); } void DeviceReattacher::onTimeout(const boost::system::error_code& error) { // 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 lock(shouldContinueLock); if (!shouldContinue) { 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( elapsedSinceLastReattachReq).count() << "ms (threshold " << staleThreshold.count() << "ms); forcing a new reattach request." << std::endl; holdReattachCReq(); } } // Schedule the next timeout scheduleNextTimeout(); } } // namespace device } // namespace smo