#include "lldb/Host/Alarm.h"
#include "lldb/Host/ThreadLauncher.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
using namespace lldb;
using namespace lldb_private;
Alarm::Alarm(Duration timeout, bool run_callback_on_exit)
: m_timeout(timeout), m_run_callbacks_on_exit(run_callback_on_exit) {
StartAlarmThread();
}
Alarm::~Alarm() { StopAlarmThread(); }
Alarm::Handle Alarm::Create(std::function<void()> callback) {
if (!AlarmThreadRunning())
return INVALID_HANDLE;
const TimePoint expiration = GetNextExpiration();
Handle handle = INVALID_HANDLE;
{
std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex);
m_entries.emplace_back(callback, expiration);
handle = m_entries.back().handle;
m_recompute_next_alarm = true;
}
m_alarm_cv.notify_one();
return handle;
}
bool Alarm::Restart(Handle handle) {
if (!AlarmThreadRunning())
return false;
const TimePoint expiration = GetNextExpiration();
{
std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex);
const auto it =
std::find_if(m_entries.begin(), m_entries.end(),
[handle](Entry &entry) { return entry.handle == handle; });
if (it == m_entries.end())
return false;
it->expiration = expiration;
m_recompute_next_alarm = true;
}
m_alarm_cv.notify_one();
return true;
}
bool Alarm::Cancel(Handle handle) {
if (!AlarmThreadRunning())
return false;
{
std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex);
const auto it =
std::find_if(m_entries.begin(), m_entries.end(),
[handle](Entry &entry) { return entry.handle == handle; });
if (it == m_entries.end())
return false;
m_entries.erase(it);
}
return true;
}
Alarm::Entry::Entry(Alarm::Callback callback, Alarm::TimePoint expiration)
: handle(Alarm::GetNextUniqueHandle()), callback(std::move(callback)),
expiration(std::move(expiration)) {}
void Alarm::StartAlarmThread() {
if (!m_alarm_thread.IsJoinable()) {
llvm::Expected<HostThread> alarm_thread = ThreadLauncher::LaunchThread(
"lldb.debugger.alarm-thread", [this] { return AlarmThread(); },
8 * 1024 * 1024);
if (alarm_thread) {
m_alarm_thread = *alarm_thread;
} else {
LLDB_LOG_ERROR(GetLog(LLDBLog::Host), alarm_thread.takeError(),
"failed to launch host thread: {0}");
}
}
}
void Alarm::StopAlarmThread() {
if (m_alarm_thread.IsJoinable()) {
{
std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex);
m_exit = true;
}
m_alarm_cv.notify_one();
m_alarm_thread.Join(nullptr);
}
}
bool Alarm::AlarmThreadRunning() { return m_alarm_thread.IsJoinable(); }
lldb::thread_result_t Alarm::AlarmThread() {
bool exit = false;
std::optional<TimePoint> next_alarm;
const auto predicate = [this] { return m_exit || m_recompute_next_alarm; };
while (!exit) {
llvm::SmallVector<Callback, 1> callbacks;
{
std::unique_lock<std::mutex> alarm_lock(m_alarm_mutex);
if (next_alarm) {
if (!m_alarm_cv.wait_until(alarm_lock, *next_alarm, predicate)) {
next_alarm.reset();
const TimePoint now = std::chrono::system_clock::now();
auto it = m_entries.begin();
while (it != m_entries.end()) {
if (it->expiration <= now) {
callbacks.emplace_back(std::move(it->callback));
it = m_entries.erase(it);
} else {
it++;
}
}
}
} else {
m_alarm_cv.wait(alarm_lock, predicate);
}
if (m_exit) {
exit = true;
if (m_run_callbacks_on_exit) {
for (Entry &entry : m_entries)
callbacks.emplace_back(std::move(entry.callback));
}
}
if (m_recompute_next_alarm || !next_alarm) {
for (Entry &entry : m_entries) {
if (!next_alarm || entry.expiration < *next_alarm)
next_alarm = entry.expiration;
}
m_recompute_next_alarm = false;
}
}
for (Callback &callback : callbacks)
callback();
}
return {};
}
Alarm::TimePoint Alarm::GetNextExpiration() const {
return std::chrono::system_clock::now() + m_timeout;
}
Alarm::Handle Alarm::GetNextUniqueHandle() {
static std::atomic<Handle> g_next_handle = 1;
return g_next_handle++;
}