Path: blob/main/contrib/llvm-project/lldb/source/Host/common/Alarm.cpp
39606 views
//===-- Alarm.cpp ---------------------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "lldb/Host/Alarm.h"9#include "lldb/Host/ThreadLauncher.h"10#include "lldb/Utility/LLDBLog.h"11#include "lldb/Utility/Log.h"1213using namespace lldb;14using namespace lldb_private;1516Alarm::Alarm(Duration timeout, bool run_callback_on_exit)17: m_timeout(timeout), m_run_callbacks_on_exit(run_callback_on_exit) {18StartAlarmThread();19}2021Alarm::~Alarm() { StopAlarmThread(); }2223Alarm::Handle Alarm::Create(std::function<void()> callback) {24// Gracefully deal with the unlikely event that the alarm thread failed to25// launch.26if (!AlarmThreadRunning())27return INVALID_HANDLE;2829// Compute the next expiration before we take the lock. This ensures that30// waiting on the lock doesn't eat into the timeout.31const TimePoint expiration = GetNextExpiration();3233Handle handle = INVALID_HANDLE;3435{36std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex);3738// Create a new unique entry and remember its handle.39m_entries.emplace_back(callback, expiration);40handle = m_entries.back().handle;4142// Tell the alarm thread we need to recompute the next alarm.43m_recompute_next_alarm = true;44}4546m_alarm_cv.notify_one();47return handle;48}4950bool Alarm::Restart(Handle handle) {51// Gracefully deal with the unlikely event that the alarm thread failed to52// launch.53if (!AlarmThreadRunning())54return false;5556// Compute the next expiration before we take the lock. This ensures that57// waiting on the lock doesn't eat into the timeout.58const TimePoint expiration = GetNextExpiration();5960{61std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex);6263// Find the entry corresponding to the given handle.64const auto it =65std::find_if(m_entries.begin(), m_entries.end(),66[handle](Entry &entry) { return entry.handle == handle; });67if (it == m_entries.end())68return false;6970// Update the expiration.71it->expiration = expiration;7273// Tell the alarm thread we need to recompute the next alarm.74m_recompute_next_alarm = true;75}7677m_alarm_cv.notify_one();78return true;79}8081bool Alarm::Cancel(Handle handle) {82// Gracefully deal with the unlikely event that the alarm thread failed to83// launch.84if (!AlarmThreadRunning())85return false;8687{88std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex);8990const auto it =91std::find_if(m_entries.begin(), m_entries.end(),92[handle](Entry &entry) { return entry.handle == handle; });9394if (it == m_entries.end())95return false;9697m_entries.erase(it);98}99100// No need to notify the alarm thread. This only affects the alarm thread if101// we removed the entry that corresponds to the next alarm. If that's the102// case, the thread will wake up as scheduled, find no expired events, and103// recompute the next alarm time.104return true;105}106107Alarm::Entry::Entry(Alarm::Callback callback, Alarm::TimePoint expiration)108: handle(Alarm::GetNextUniqueHandle()), callback(std::move(callback)),109expiration(std::move(expiration)) {}110111void Alarm::StartAlarmThread() {112if (!m_alarm_thread.IsJoinable()) {113llvm::Expected<HostThread> alarm_thread = ThreadLauncher::LaunchThread(114"lldb.debugger.alarm-thread", [this] { return AlarmThread(); },1158 * 1024 * 1024); // Use larger 8MB stack for this thread116if (alarm_thread) {117m_alarm_thread = *alarm_thread;118} else {119LLDB_LOG_ERROR(GetLog(LLDBLog::Host), alarm_thread.takeError(),120"failed to launch host thread: {0}");121}122}123}124125void Alarm::StopAlarmThread() {126if (m_alarm_thread.IsJoinable()) {127{128std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex);129m_exit = true;130}131m_alarm_cv.notify_one();132m_alarm_thread.Join(nullptr);133}134}135136bool Alarm::AlarmThreadRunning() { return m_alarm_thread.IsJoinable(); }137138lldb::thread_result_t Alarm::AlarmThread() {139bool exit = false;140std::optional<TimePoint> next_alarm;141142const auto predicate = [this] { return m_exit || m_recompute_next_alarm; };143144while (!exit) {145// Synchronization between the main thread and the alarm thread using a146// mutex and condition variable. There are 2 reasons the thread can wake up:147//148// 1. The timeout for the next alarm expired.149//150// 2. The condition variable is notified that one of our shared variables151// (see predicate) was modified. Either the thread is asked to shut down152// or a new alarm came in and we need to recompute the next timeout.153//154// Below we only deal with the timeout expiring and fall through for dealing155// with the rest.156llvm::SmallVector<Callback, 1> callbacks;157{158std::unique_lock<std::mutex> alarm_lock(m_alarm_mutex);159if (next_alarm) {160if (!m_alarm_cv.wait_until(alarm_lock, *next_alarm, predicate)) {161// The timeout for the next alarm expired.162163// Clear the next timeout to signal that we need to recompute the next164// timeout.165next_alarm.reset();166167// Iterate over all the callbacks. Call the ones that have expired168// and remove them from the list.169const TimePoint now = std::chrono::system_clock::now();170auto it = m_entries.begin();171while (it != m_entries.end()) {172if (it->expiration <= now) {173callbacks.emplace_back(std::move(it->callback));174it = m_entries.erase(it);175} else {176it++;177}178}179}180} else {181m_alarm_cv.wait(alarm_lock, predicate);182}183184// Fall through after waiting on the condition variable. At this point185// either the predicate is true or we woke up because an alarm expired.186187// The alarm thread is shutting down.188if (m_exit) {189exit = true;190if (m_run_callbacks_on_exit) {191for (Entry &entry : m_entries)192callbacks.emplace_back(std::move(entry.callback));193}194}195196// A new alarm was added or an alarm expired. Either way we need to197// recompute when this thread should wake up for the next alarm.198if (m_recompute_next_alarm || !next_alarm) {199for (Entry &entry : m_entries) {200if (!next_alarm || entry.expiration < *next_alarm)201next_alarm = entry.expiration;202}203m_recompute_next_alarm = false;204}205}206207// Outside the lock, call the callbacks.208for (Callback &callback : callbacks)209callback();210}211return {};212}213214Alarm::TimePoint Alarm::GetNextExpiration() const {215return std::chrono::system_clock::now() + m_timeout;216}217218Alarm::Handle Alarm::GetNextUniqueHandle() {219static std::atomic<Handle> g_next_handle = 1;220return g_next_handle++;221}222223224