#include #include #include #include #include #include #include #include #include namespace smo { namespace device { namespace { long computeTimesliceResidueMs( long workDurationMs, long periodMs) { if (workDurationMs >= periodMs) { return 0; } return periodMs - workDurationMs; } long durationMsSince( const std::chrono::high_resolution_clock::time_point &startStamp, const std::chrono::high_resolution_clock::time_point &endStamp) { const auto duration = endStamp - startStamp; return std::chrono::duration_cast( duration).count(); } } // namespace DeviceReattacher::DeviceReattacher( DeviceManager& parent, std::shared_ptr ioThread) : parent(parent), ioThread(ioThread), daemonTimer(ioThread->getIoContext()) { /** EXPLANATION: * 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. * Each timeslice runs attach work first, then sleeps only the period * residue so reattach polls stay on a wall-clock cadence. */ } sscl::co::DynamicNonViralPostingInvoker DeviceReattacher::deviceReattacherCDaemon( [[maybe_unused]] sscl::co::ExplicitPostTarget postTarget, [[maybe_unused]] std::exception_ptr &exceptionPtr, [[maybe_unused]] std::function callback, sscl::SyncCancelerForAsyncWork &canceler) { boost::asio::io_context &timerIoContext = sscl::ComponentThread::getSelf()->getIoContext(); const long reattacherPeriodMs = CONFIG_MRNTT_DEVMGR_REATTACHER_PERIOD_MS; while (!canceler.isCancellationRequested()) { const auto workStartStamp = std::chrono::high_resolution_clock::now(); co_await parent.attachAllUnattachedDevicesFromKnownListCReq(); const auto workEndStamp = std::chrono::high_resolution_clock::now(); const long workDurationMs = durationMsSince( workStartStamp, workEndStamp); const long residueMs = computeTimesliceResidueMs( workDurationMs, reattacherPeriodMs); const bool expiredNormally = co_await adapters::boostAsio::getDeadlineTimerAReqAwaiter( timerIoContext, daemonTimer, boost::posix_time::milliseconds(residueMs)); if (!expiredNormally) { break; } } co_return; } void DeviceReattacher::start() { taskNursery.openAdmission(); taskNursery.launch( [this](sscl::co::NonViralTaskNursery::Slot::Lease &lease) { 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; } } }); } void DeviceReattacher::stop() { daemonTimer.cancel(); taskNursery.requestCancelOnAll(); taskNursery.closeAdmission(); taskNursery.syncAwaitAllSettlements( sscl::ComponentThread::getSelf()->getIoContext()); } } // namespace device } // namespace smo