From 77ab8962e32fe92701407f890756cc56e47156fa Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sat, 19 Jul 2025 16:47:14 +0200 Subject: [PATCH] eventloop: improve timer handling to avoid crashes ref #11062 --- src/managers/eventLoop/EventLoopManager.cpp | 22 +++++++++++++++++---- src/managers/eventLoop/EventLoopManager.hpp | 6 ++++-- src/managers/eventLoop/EventLoopTimer.cpp | 4 ++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/managers/eventLoop/EventLoopManager.cpp b/src/managers/eventLoop/EventLoopManager.cpp index 0389e7b86..b9ac7242e 100644 --- a/src/managers/eventLoop/EventLoopManager.cpp +++ b/src/managers/eventLoop/EventLoopManager.cpp @@ -102,25 +102,25 @@ void CEventLoopManager::enterLoop() { void CEventLoopManager::onTimerFire() { const auto CPY = m_timers.timers; for (auto const& t : CPY) { - if (t.strongRef() > 1 /* if it's 1, it was lost. Don't call it. */ && t->passed() && !t->cancelled()) + if (t.strongRef() > 2 /* if it's 2, it was lost. Don't call it. */ && t->passed() && !t->cancelled()) t->call(t); } - nudgeTimers(); + scheduleRecalc(); } void CEventLoopManager::addTimer(SP timer) { if (std::ranges::contains(m_timers.timers, timer)) return; m_timers.timers.emplace_back(timer); - nudgeTimers(); + scheduleRecalc(); } void CEventLoopManager::removeTimer(SP timer) { if (!std::ranges::contains(m_timers.timers, timer)) return; std::erase_if(m_timers.timers, [timer](const auto& t) { return timer == t; }); - nudgeTimers(); + scheduleRecalc(); } static void timespecAddNs(timespec* pTimespec, int64_t delta) { @@ -136,7 +136,21 @@ static void timespecAddNs(timespec* pTimespec, int64_t delta) { } } +void CEventLoopManager::scheduleRecalc() { + // do not do it instantly, do it later. Avoid recursive access to the timer + // vector, it could be catastrophic if we modify it while iterating + + if (m_timers.recalcScheduled) + return; + + m_timers.recalcScheduled = true; + + doLater([this] { nudgeTimers(); }); +} + void CEventLoopManager::nudgeTimers() { + m_timers.recalcScheduled = false; + // remove timers that have gone missing std::erase_if(m_timers.timers, [](const auto& t) { return t.strongRef() <= 1; }); diff --git a/src/managers/eventLoop/EventLoopManager.hpp b/src/managers/eventLoop/EventLoopManager.hpp index 0835b242f..d3a67ae57 100644 --- a/src/managers/eventLoop/EventLoopManager.hpp +++ b/src/managers/eventLoop/EventLoopManager.hpp @@ -27,8 +27,8 @@ class CEventLoopManager { void onTimerFire(); - // recalculates timers - void nudgeTimers(); + // schedules a recalc of the timers + void scheduleRecalc(); // schedules a function to run later, aka in a wayland idle event. void doLater(const std::function& fn); @@ -69,6 +69,7 @@ class CEventLoopManager { private: // Manages the event sources after AQ pollFDs change. void syncPollFDs(); + void nudgeTimers(); struct SEventSourceData { SP pollFD; @@ -84,6 +85,7 @@ class CEventLoopManager { struct { std::vector> timers; Hyprutils::OS::CFileDescriptor timerfd; + bool recalcScheduled = false; } m_timers; SIdleData m_idle; diff --git a/src/managers/eventLoop/EventLoopTimer.cpp b/src/managers/eventLoop/EventLoopTimer.cpp index d4633b0d8..fa86c861a 100644 --- a/src/managers/eventLoop/EventLoopTimer.cpp +++ b/src/managers/eventLoop/EventLoopTimer.cpp @@ -14,13 +14,13 @@ CEventLoopTimer::CEventLoopTimer(std::optional timeout, std::f void CEventLoopTimer::updateTimeout(std::optional timeout) { if (!timeout.has_value()) { m_expires.reset(); - g_pEventLoopManager->nudgeTimers(); + g_pEventLoopManager->scheduleRecalc(); return; } m_expires = Time::steadyNow() + *timeout; - g_pEventLoopManager->nudgeTimers(); + g_pEventLoopManager->scheduleRecalc(); } bool CEventLoopTimer::passed() {