xref: /freebsd/contrib/llvm-project/lldb/source/Host/common/Alarm.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
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 
Alarm(Duration timeout,bool run_callback_on_exit)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 
~Alarm()22 Alarm::~Alarm() { StopAlarmThread(); }
23 
Create(std::function<void ()> callback)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 
Restart(Handle handle)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 
Cancel(Handle handle)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 
Entry(Alarm::Callback callback,Alarm::TimePoint expiration)108 Alarm::Entry::Entry(Alarm::Callback callback, Alarm::TimePoint expiration)
109     : handle(Alarm::GetNextUniqueHandle()), callback(std::move(callback)),
110       expiration(std::move(expiration)) {}
111 
StartAlarmThread()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 
StopAlarmThread()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 
AlarmThreadRunning()137 bool Alarm::AlarmThreadRunning() { return m_alarm_thread.IsJoinable(); }
138 
AlarmThread()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 
GetNextExpiration() const215 Alarm::TimePoint Alarm::GetNextExpiration() const {
216   return std::chrono::system_clock::now() + m_timeout;
217 }
218 
GetNextUniqueHandle()219 Alarm::Handle Alarm::GetNextUniqueHandle() {
220   static std::atomic<Handle> g_next_handle = 1;
221   return g_next_handle++;
222 }
223