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(args&: callback, args: 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(first: m_entries.begin(), last: m_entries.end(), |
67 | pred: [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(first: m_entries.begin(), last: m_entries.end(), |
93 | pred: [handle](Entry &entry) { return entry.handle == handle; }); |
94 | |
95 | if (it == m_entries.end()) |
96 | return false; |
97 | |
98 | m_entries.erase(position: 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 | name: "lldb.debugger.alarm-thread" , thread_function: [this] { return AlarmThread(); }, |
116 | min_stack_byte_size: 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(result: 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(lock&: alarm_lock, atime: *next_alarm, p: 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(Args: std::move(it->callback)); |
175 | it = m_entries.erase(position: it); |
176 | } else { |
177 | it++; |
178 | } |
179 | } |
180 | } |
181 | } else { |
182 | m_alarm_cv.wait(lock&: alarm_lock, p: 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(Args: 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 | |