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