1*0fca6ea1SDimitry Andric //===-- Alarm.cpp ---------------------------------------------------------===// 2*0fca6ea1SDimitry Andric // 3*0fca6ea1SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4*0fca6ea1SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 5*0fca6ea1SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6*0fca6ea1SDimitry Andric // 7*0fca6ea1SDimitry Andric //===----------------------------------------------------------------------===// 8*0fca6ea1SDimitry Andric 9*0fca6ea1SDimitry Andric #include "lldb/Host/Alarm.h" 10*0fca6ea1SDimitry Andric #include "lldb/Host/ThreadLauncher.h" 11*0fca6ea1SDimitry Andric #include "lldb/Utility/LLDBLog.h" 12*0fca6ea1SDimitry Andric #include "lldb/Utility/Log.h" 13*0fca6ea1SDimitry Andric 14*0fca6ea1SDimitry Andric using namespace lldb; 15*0fca6ea1SDimitry Andric using namespace lldb_private; 16*0fca6ea1SDimitry Andric 17*0fca6ea1SDimitry Andric Alarm::Alarm(Duration timeout, bool run_callback_on_exit) 18*0fca6ea1SDimitry Andric : m_timeout(timeout), m_run_callbacks_on_exit(run_callback_on_exit) { 19*0fca6ea1SDimitry Andric StartAlarmThread(); 20*0fca6ea1SDimitry Andric } 21*0fca6ea1SDimitry Andric 22*0fca6ea1SDimitry Andric Alarm::~Alarm() { StopAlarmThread(); } 23*0fca6ea1SDimitry Andric 24*0fca6ea1SDimitry Andric Alarm::Handle Alarm::Create(std::function<void()> callback) { 25*0fca6ea1SDimitry Andric // Gracefully deal with the unlikely event that the alarm thread failed to 26*0fca6ea1SDimitry Andric // launch. 27*0fca6ea1SDimitry Andric if (!AlarmThreadRunning()) 28*0fca6ea1SDimitry Andric return INVALID_HANDLE; 29*0fca6ea1SDimitry Andric 30*0fca6ea1SDimitry Andric // Compute the next expiration before we take the lock. This ensures that 31*0fca6ea1SDimitry Andric // waiting on the lock doesn't eat into the timeout. 32*0fca6ea1SDimitry Andric const TimePoint expiration = GetNextExpiration(); 33*0fca6ea1SDimitry Andric 34*0fca6ea1SDimitry Andric Handle handle = INVALID_HANDLE; 35*0fca6ea1SDimitry Andric 36*0fca6ea1SDimitry Andric { 37*0fca6ea1SDimitry Andric std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex); 38*0fca6ea1SDimitry Andric 39*0fca6ea1SDimitry Andric // Create a new unique entry and remember its handle. 40*0fca6ea1SDimitry Andric m_entries.emplace_back(callback, expiration); 41*0fca6ea1SDimitry Andric handle = m_entries.back().handle; 42*0fca6ea1SDimitry Andric 43*0fca6ea1SDimitry Andric // Tell the alarm thread we need to recompute the next alarm. 44*0fca6ea1SDimitry Andric m_recompute_next_alarm = true; 45*0fca6ea1SDimitry Andric } 46*0fca6ea1SDimitry Andric 47*0fca6ea1SDimitry Andric m_alarm_cv.notify_one(); 48*0fca6ea1SDimitry Andric return handle; 49*0fca6ea1SDimitry Andric } 50*0fca6ea1SDimitry Andric 51*0fca6ea1SDimitry Andric bool Alarm::Restart(Handle handle) { 52*0fca6ea1SDimitry Andric // Gracefully deal with the unlikely event that the alarm thread failed to 53*0fca6ea1SDimitry Andric // launch. 54*0fca6ea1SDimitry Andric if (!AlarmThreadRunning()) 55*0fca6ea1SDimitry Andric return false; 56*0fca6ea1SDimitry Andric 57*0fca6ea1SDimitry Andric // Compute the next expiration before we take the lock. This ensures that 58*0fca6ea1SDimitry Andric // waiting on the lock doesn't eat into the timeout. 59*0fca6ea1SDimitry Andric const TimePoint expiration = GetNextExpiration(); 60*0fca6ea1SDimitry Andric 61*0fca6ea1SDimitry Andric { 62*0fca6ea1SDimitry Andric std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex); 63*0fca6ea1SDimitry Andric 64*0fca6ea1SDimitry Andric // Find the entry corresponding to the given handle. 65*0fca6ea1SDimitry Andric const auto it = 66*0fca6ea1SDimitry Andric std::find_if(m_entries.begin(), m_entries.end(), 67*0fca6ea1SDimitry Andric [handle](Entry &entry) { return entry.handle == handle; }); 68*0fca6ea1SDimitry Andric if (it == m_entries.end()) 69*0fca6ea1SDimitry Andric return false; 70*0fca6ea1SDimitry Andric 71*0fca6ea1SDimitry Andric // Update the expiration. 72*0fca6ea1SDimitry Andric it->expiration = expiration; 73*0fca6ea1SDimitry Andric 74*0fca6ea1SDimitry Andric // Tell the alarm thread we need to recompute the next alarm. 75*0fca6ea1SDimitry Andric m_recompute_next_alarm = true; 76*0fca6ea1SDimitry Andric } 77*0fca6ea1SDimitry Andric 78*0fca6ea1SDimitry Andric m_alarm_cv.notify_one(); 79*0fca6ea1SDimitry Andric return true; 80*0fca6ea1SDimitry Andric } 81*0fca6ea1SDimitry Andric 82*0fca6ea1SDimitry Andric bool Alarm::Cancel(Handle handle) { 83*0fca6ea1SDimitry Andric // Gracefully deal with the unlikely event that the alarm thread failed to 84*0fca6ea1SDimitry Andric // launch. 85*0fca6ea1SDimitry Andric if (!AlarmThreadRunning()) 86*0fca6ea1SDimitry Andric return false; 87*0fca6ea1SDimitry Andric 88*0fca6ea1SDimitry Andric { 89*0fca6ea1SDimitry Andric std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex); 90*0fca6ea1SDimitry Andric 91*0fca6ea1SDimitry Andric const auto it = 92*0fca6ea1SDimitry Andric std::find_if(m_entries.begin(), m_entries.end(), 93*0fca6ea1SDimitry Andric [handle](Entry &entry) { return entry.handle == handle; }); 94*0fca6ea1SDimitry Andric 95*0fca6ea1SDimitry Andric if (it == m_entries.end()) 96*0fca6ea1SDimitry Andric return false; 97*0fca6ea1SDimitry Andric 98*0fca6ea1SDimitry Andric m_entries.erase(it); 99*0fca6ea1SDimitry Andric } 100*0fca6ea1SDimitry Andric 101*0fca6ea1SDimitry Andric // No need to notify the alarm thread. This only affects the alarm thread if 102*0fca6ea1SDimitry Andric // we removed the entry that corresponds to the next alarm. If that's the 103*0fca6ea1SDimitry Andric // case, the thread will wake up as scheduled, find no expired events, and 104*0fca6ea1SDimitry Andric // recompute the next alarm time. 105*0fca6ea1SDimitry Andric return true; 106*0fca6ea1SDimitry Andric } 107*0fca6ea1SDimitry Andric 108*0fca6ea1SDimitry Andric Alarm::Entry::Entry(Alarm::Callback callback, Alarm::TimePoint expiration) 109*0fca6ea1SDimitry Andric : handle(Alarm::GetNextUniqueHandle()), callback(std::move(callback)), 110*0fca6ea1SDimitry Andric expiration(std::move(expiration)) {} 111*0fca6ea1SDimitry Andric 112*0fca6ea1SDimitry Andric void Alarm::StartAlarmThread() { 113*0fca6ea1SDimitry Andric if (!m_alarm_thread.IsJoinable()) { 114*0fca6ea1SDimitry Andric llvm::Expected<HostThread> alarm_thread = ThreadLauncher::LaunchThread( 115*0fca6ea1SDimitry Andric "lldb.debugger.alarm-thread", [this] { return AlarmThread(); }, 116*0fca6ea1SDimitry Andric 8 * 1024 * 1024); // Use larger 8MB stack for this thread 117*0fca6ea1SDimitry Andric if (alarm_thread) { 118*0fca6ea1SDimitry Andric m_alarm_thread = *alarm_thread; 119*0fca6ea1SDimitry Andric } else { 120*0fca6ea1SDimitry Andric LLDB_LOG_ERROR(GetLog(LLDBLog::Host), alarm_thread.takeError(), 121*0fca6ea1SDimitry Andric "failed to launch host thread: {0}"); 122*0fca6ea1SDimitry Andric } 123*0fca6ea1SDimitry Andric } 124*0fca6ea1SDimitry Andric } 125*0fca6ea1SDimitry Andric 126*0fca6ea1SDimitry Andric void Alarm::StopAlarmThread() { 127*0fca6ea1SDimitry Andric if (m_alarm_thread.IsJoinable()) { 128*0fca6ea1SDimitry Andric { 129*0fca6ea1SDimitry Andric std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex); 130*0fca6ea1SDimitry Andric m_exit = true; 131*0fca6ea1SDimitry Andric } 132*0fca6ea1SDimitry Andric m_alarm_cv.notify_one(); 133*0fca6ea1SDimitry Andric m_alarm_thread.Join(nullptr); 134*0fca6ea1SDimitry Andric } 135*0fca6ea1SDimitry Andric } 136*0fca6ea1SDimitry Andric 137*0fca6ea1SDimitry Andric bool Alarm::AlarmThreadRunning() { return m_alarm_thread.IsJoinable(); } 138*0fca6ea1SDimitry Andric 139*0fca6ea1SDimitry Andric lldb::thread_result_t Alarm::AlarmThread() { 140*0fca6ea1SDimitry Andric bool exit = false; 141*0fca6ea1SDimitry Andric std::optional<TimePoint> next_alarm; 142*0fca6ea1SDimitry Andric 143*0fca6ea1SDimitry Andric const auto predicate = [this] { return m_exit || m_recompute_next_alarm; }; 144*0fca6ea1SDimitry Andric 145*0fca6ea1SDimitry Andric while (!exit) { 146*0fca6ea1SDimitry Andric // Synchronization between the main thread and the alarm thread using a 147*0fca6ea1SDimitry Andric // mutex and condition variable. There are 2 reasons the thread can wake up: 148*0fca6ea1SDimitry Andric // 149*0fca6ea1SDimitry Andric // 1. The timeout for the next alarm expired. 150*0fca6ea1SDimitry Andric // 151*0fca6ea1SDimitry Andric // 2. The condition variable is notified that one of our shared variables 152*0fca6ea1SDimitry Andric // (see predicate) was modified. Either the thread is asked to shut down 153*0fca6ea1SDimitry Andric // or a new alarm came in and we need to recompute the next timeout. 154*0fca6ea1SDimitry Andric // 155*0fca6ea1SDimitry Andric // Below we only deal with the timeout expiring and fall through for dealing 156*0fca6ea1SDimitry Andric // with the rest. 157*0fca6ea1SDimitry Andric llvm::SmallVector<Callback, 1> callbacks; 158*0fca6ea1SDimitry Andric { 159*0fca6ea1SDimitry Andric std::unique_lock<std::mutex> alarm_lock(m_alarm_mutex); 160*0fca6ea1SDimitry Andric if (next_alarm) { 161*0fca6ea1SDimitry Andric if (!m_alarm_cv.wait_until(alarm_lock, *next_alarm, predicate)) { 162*0fca6ea1SDimitry Andric // The timeout for the next alarm expired. 163*0fca6ea1SDimitry Andric 164*0fca6ea1SDimitry Andric // Clear the next timeout to signal that we need to recompute the next 165*0fca6ea1SDimitry Andric // timeout. 166*0fca6ea1SDimitry Andric next_alarm.reset(); 167*0fca6ea1SDimitry Andric 168*0fca6ea1SDimitry Andric // Iterate over all the callbacks. Call the ones that have expired 169*0fca6ea1SDimitry Andric // and remove them from the list. 170*0fca6ea1SDimitry Andric const TimePoint now = std::chrono::system_clock::now(); 171*0fca6ea1SDimitry Andric auto it = m_entries.begin(); 172*0fca6ea1SDimitry Andric while (it != m_entries.end()) { 173*0fca6ea1SDimitry Andric if (it->expiration <= now) { 174*0fca6ea1SDimitry Andric callbacks.emplace_back(std::move(it->callback)); 175*0fca6ea1SDimitry Andric it = m_entries.erase(it); 176*0fca6ea1SDimitry Andric } else { 177*0fca6ea1SDimitry Andric it++; 178*0fca6ea1SDimitry Andric } 179*0fca6ea1SDimitry Andric } 180*0fca6ea1SDimitry Andric } 181*0fca6ea1SDimitry Andric } else { 182*0fca6ea1SDimitry Andric m_alarm_cv.wait(alarm_lock, predicate); 183*0fca6ea1SDimitry Andric } 184*0fca6ea1SDimitry Andric 185*0fca6ea1SDimitry Andric // Fall through after waiting on the condition variable. At this point 186*0fca6ea1SDimitry Andric // either the predicate is true or we woke up because an alarm expired. 187*0fca6ea1SDimitry Andric 188*0fca6ea1SDimitry Andric // The alarm thread is shutting down. 189*0fca6ea1SDimitry Andric if (m_exit) { 190*0fca6ea1SDimitry Andric exit = true; 191*0fca6ea1SDimitry Andric if (m_run_callbacks_on_exit) { 192*0fca6ea1SDimitry Andric for (Entry &entry : m_entries) 193*0fca6ea1SDimitry Andric callbacks.emplace_back(std::move(entry.callback)); 194*0fca6ea1SDimitry Andric } 195*0fca6ea1SDimitry Andric } 196*0fca6ea1SDimitry Andric 197*0fca6ea1SDimitry Andric // A new alarm was added or an alarm expired. Either way we need to 198*0fca6ea1SDimitry Andric // recompute when this thread should wake up for the next alarm. 199*0fca6ea1SDimitry Andric if (m_recompute_next_alarm || !next_alarm) { 200*0fca6ea1SDimitry Andric for (Entry &entry : m_entries) { 201*0fca6ea1SDimitry Andric if (!next_alarm || entry.expiration < *next_alarm) 202*0fca6ea1SDimitry Andric next_alarm = entry.expiration; 203*0fca6ea1SDimitry Andric } 204*0fca6ea1SDimitry Andric m_recompute_next_alarm = false; 205*0fca6ea1SDimitry Andric } 206*0fca6ea1SDimitry Andric } 207*0fca6ea1SDimitry Andric 208*0fca6ea1SDimitry Andric // Outside the lock, call the callbacks. 209*0fca6ea1SDimitry Andric for (Callback &callback : callbacks) 210*0fca6ea1SDimitry Andric callback(); 211*0fca6ea1SDimitry Andric } 212*0fca6ea1SDimitry Andric return {}; 213*0fca6ea1SDimitry Andric } 214*0fca6ea1SDimitry Andric 215*0fca6ea1SDimitry Andric Alarm::TimePoint Alarm::GetNextExpiration() const { 216*0fca6ea1SDimitry Andric return std::chrono::system_clock::now() + m_timeout; 217*0fca6ea1SDimitry Andric } 218*0fca6ea1SDimitry Andric 219*0fca6ea1SDimitry Andric Alarm::Handle Alarm::GetNextUniqueHandle() { 220*0fca6ea1SDimitry Andric static std::atomic<Handle> g_next_handle = 1; 221*0fca6ea1SDimitry Andric return g_next_handle++; 222*0fca6ea1SDimitry Andric } 223