Skip to content

Commit 3aae84b

Browse files
committed
[lldb] Add an Alarm class
Add an Alarm class which allows scheduling callbacks after a specific timeout has elapsed.
1 parent 8467457 commit 3aae84b

File tree

5 files changed

+447
-0
lines changed

5 files changed

+447
-0
lines changed

lldb/include/lldb/Host/Alarm.h

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//===-- Alarm.h -------------------------------------------------*- C++ -*-===//
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+
#ifndef LLDB_HOST_ALARM_H
10+
#define LLDB_HOST_ALARM_H
11+
12+
#include "lldb/Host/HostThread.h"
13+
#include "lldb/lldb-types.h"
14+
#include "llvm/Support/Chrono.h"
15+
16+
namespace lldb_private {
17+
18+
class Alarm {
19+
public:
20+
using Handle = uint64_t;
21+
using Callback = std::function<void()>;
22+
using TimePoint = llvm::sys::TimePoint<>;
23+
using Duration = std::chrono::milliseconds;
24+
25+
Alarm(Duration timeout, bool run_callback_on_exit = false);
26+
~Alarm();
27+
28+
Handle Create(Callback callback);
29+
bool Restart(Handle handle);
30+
bool Cancel(Handle handle);
31+
32+
static constexpr Handle INVALID_HANDLE = 0;
33+
34+
private:
35+
/// Helper functions to start, stop and check the status of the alarm thread.
36+
/// @{
37+
void StartAlarmThread();
38+
void StopAlarmThread();
39+
bool AlarmThreadRunning();
40+
/// @}
41+
42+
/// Return an unique, monotonically increasing handle.
43+
static Handle GetNextUniqueHandle();
44+
45+
TimePoint GetNextExpiration() const;
46+
47+
/// Alarm entry.
48+
struct Entry {
49+
Handle handle;
50+
Callback callback;
51+
TimePoint expiration;
52+
53+
Entry(Callback callback, TimePoint expiration);
54+
bool operator==(const Entry &rhs) { return handle == rhs.handle; }
55+
};
56+
57+
/// List of alarm entries.
58+
std::vector<Entry> m_entries;
59+
60+
/// Timeout between when an alarm is created and when it fires.
61+
Duration m_timeout;
62+
63+
/// The alarm thread.
64+
/// @{
65+
HostThread m_alarm_thread;
66+
lldb::thread_result_t AlarmThread();
67+
/// @}
68+
69+
/// Synchronize access between the alarm thread and the main thread.
70+
std::mutex m_alarm_mutex;
71+
72+
/// Condition variable used to wake up the alarm thread.
73+
std::condition_variable m_alarm_cv;
74+
75+
/// Flag to signal the alarm thread that something changed and we need to
76+
// recompute the next alarm.
77+
bool m_recompute_next_alarm = false;
78+
79+
/// Flag to signal the alarm thread to exit.
80+
bool m_exit = false;
81+
82+
/// Flag to signal we should run all callbacks on exit.
83+
bool m_run_callbacks_on_exit = false;
84+
};
85+
86+
} // namespace lldb_private
87+
88+
#endif // LLDB_HOST_ALARM_H

lldb/source/Host/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ macro(add_host_subdirectory group)
1313
endmacro()
1414

1515
add_host_subdirectory(common
16+
common/Alarm.cpp
1617
common/FileAction.cpp
1718
common/FileCache.cpp
1819
common/File.cpp

lldb/source/Host/common/Alarm.cpp

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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 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 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 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 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+
std::unique_lock alarm_lock(m_alarm_mutex);
147+
148+
if (next_alarm) {
149+
if (!m_alarm_cv.wait_until(alarm_lock, *next_alarm, predicate)) {
150+
next_alarm.reset();
151+
const TimePoint now = std::chrono::system_clock::now();
152+
auto it = m_entries.begin();
153+
while (it != m_entries.end()) {
154+
if (it->expiration <= now) {
155+
it->callback();
156+
it = m_entries.erase(it);
157+
} else {
158+
it++;
159+
}
160+
}
161+
}
162+
} else {
163+
m_alarm_cv.wait(alarm_lock, predicate);
164+
}
165+
166+
if (m_exit) {
167+
exit = true;
168+
if (m_run_callbacks_on_exit) {
169+
for (Entry &entry : m_entries)
170+
entry.callback();
171+
}
172+
continue;
173+
}
174+
175+
if (m_recompute_next_alarm || !next_alarm) {
176+
for (Entry &entry : m_entries) {
177+
if (!next_alarm || entry.expiration < *next_alarm)
178+
next_alarm = entry.expiration;
179+
}
180+
m_recompute_next_alarm = false;
181+
}
182+
}
183+
return {};
184+
}
185+
186+
Alarm::TimePoint Alarm::GetNextExpiration() const {
187+
return std::chrono::system_clock::now() + m_timeout;
188+
}
189+
190+
Alarm::Handle Alarm::GetNextUniqueHandle() {
191+
static std::atomic<Handle> g_next_handle = 1;
192+
return g_next_handle++;
193+
}

0 commit comments

Comments
 (0)