Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/llvm-project/lldb/source/Host/common/Alarm.cpp
39606 views
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
224