LLDB mainline
Alarm.cpp
Go to the documentation of this file.
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"
12#include "lldb/Utility/Log.h"
13
14using namespace lldb;
15using namespace lldb_private;
16
17Alarm::Alarm(Duration timeout, bool run_callback_on_exit)
18 : m_timeout(timeout), m_run_callbacks_on_exit(run_callback_on_exit) {
20}
21
23
24Alarm::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.
45 }
46
47 m_alarm_cv.notify_one();
48 return handle;
49}
50
51bool 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.
76 }
77
78 m_alarm_cv.notify_one();
79 return true;
80}
81
82bool 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
109 : handle(Alarm::GetNextUniqueHandle()), callback(std::move(callback)),
110 expiration(std::move(expiration)) {}
111
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
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
138
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;
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 }
205 }
206 }
207
208 // Outside the lock, call the callbacks.
209 for (Callback &callback : callbacks)
210 callback();
211 }
212 return {};
213}
214
216 return std::chrono::system_clock::now() + m_timeout;
217}
218
220 static std::atomic<Handle> g_next_handle = 1;
221 return g_next_handle++;
222}
#define LLDB_LOG_ERROR(log, error,...)
Definition: Log.h:382
enables scheduling a callback function after a specified timeout.
Definition: Alarm.h:24
Alarm(Duration timeout, bool run_callback_on_exit=false)
Definition: Alarm.cpp:17
bool m_exit
Flag to signal the alarm thread to exit.
Definition: Alarm.h:107
std::function< void()> Callback
Definition: Alarm.h:27
llvm::sys::TimePoint<> TimePoint
Definition: Alarm.h:28
TimePoint GetNextExpiration() const
Helper to compute the next time the alarm thread needs to wake up.
Definition: Alarm.cpp:215
lldb::thread_result_t AlarmThread()
Definition: Alarm.cpp:139
bool AlarmThreadRunning()
Definition: Alarm.cpp:137
static constexpr Handle INVALID_HANDLE
Definition: Alarm.h:58
Duration m_timeout
Timeout between when an alarm is created and when it fires.
Definition: Alarm.h:88
void StartAlarmThread()
Helper functions to start, stop and check the status of the alarm thread.
Definition: Alarm.cpp:112
std::vector< Entry > m_entries
List of alarm entries.
Definition: Alarm.h:85
bool m_run_callbacks_on_exit
Flag to signal we should run all callbacks on exit.
Definition: Alarm.h:110
std::condition_variable m_alarm_cv
Condition variable used to wake up the alarm thread.
Definition: Alarm.h:100
void StopAlarmThread()
Definition: Alarm.cpp:126
static Handle GetNextUniqueHandle()
Return an unique, monotonically increasing handle.
Definition: Alarm.cpp:219
bool Cancel(Handle handle)
Cancel the alarm for the given Handle.
Definition: Alarm.cpp:82
bool Restart(Handle handle)
Restart the alarm for the given Handle.
Definition: Alarm.cpp:51
bool m_recompute_next_alarm
Flag to signal the alarm thread that something changed and we need to recompute the next alarm.
Definition: Alarm.h:104
std::mutex m_alarm_mutex
Synchronize access between the alarm thread and the main thread.
Definition: Alarm.h:97
Handle Create(Callback callback)
Create an alarm for the given callback.
Definition: Alarm.cpp:24
uint64_t Handle
Definition: Alarm.h:26
std::chrono::milliseconds Duration
Definition: Alarm.h:29
HostThread m_alarm_thread
The alarm thread.
Definition: Alarm.h:92
Status Join(lldb::thread_result_t *result)
Definition: HostThread.cpp:20
static llvm::Expected< HostThread > LaunchThread(llvm::StringRef name, std::function< lldb::thread_result_t()> thread_function, size_t min_stack_byte_size=0)
A class that represents a running process on the host machine.
Log * GetLog(Cat mask)
Retrieve the Log object for the channel associated with the given log enum.
Definition: Log.h:331
Definition: SBAddress.h:15
void * thread_result_t
Definition: lldb-types.h:62
Alarm entry.
Definition: Alarm.h:75
Entry(Type t=Type::Invalid, const char *s=nullptr, const char *f=nullptr)
Definition: FormatEntity.h:148