xref: /freebsd-src/contrib/llvm-project/lldb/source/Host/common/Alarm.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
1*0fca6ea1SDimitry Andric //===-- Alarm.cpp ---------------------------------------------------------===//
2*0fca6ea1SDimitry Andric //
3*0fca6ea1SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*0fca6ea1SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5*0fca6ea1SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*0fca6ea1SDimitry Andric //
7*0fca6ea1SDimitry Andric //===----------------------------------------------------------------------===//
8*0fca6ea1SDimitry Andric 
9*0fca6ea1SDimitry Andric #include "lldb/Host/Alarm.h"
10*0fca6ea1SDimitry Andric #include "lldb/Host/ThreadLauncher.h"
11*0fca6ea1SDimitry Andric #include "lldb/Utility/LLDBLog.h"
12*0fca6ea1SDimitry Andric #include "lldb/Utility/Log.h"
13*0fca6ea1SDimitry Andric 
14*0fca6ea1SDimitry Andric using namespace lldb;
15*0fca6ea1SDimitry Andric using namespace lldb_private;
16*0fca6ea1SDimitry Andric 
17*0fca6ea1SDimitry Andric Alarm::Alarm(Duration timeout, bool run_callback_on_exit)
18*0fca6ea1SDimitry Andric     : m_timeout(timeout), m_run_callbacks_on_exit(run_callback_on_exit) {
19*0fca6ea1SDimitry Andric   StartAlarmThread();
20*0fca6ea1SDimitry Andric }
21*0fca6ea1SDimitry Andric 
22*0fca6ea1SDimitry Andric Alarm::~Alarm() { StopAlarmThread(); }
23*0fca6ea1SDimitry Andric 
24*0fca6ea1SDimitry Andric Alarm::Handle Alarm::Create(std::function<void()> callback) {
25*0fca6ea1SDimitry Andric   // Gracefully deal with the unlikely event that the alarm thread failed to
26*0fca6ea1SDimitry Andric   // launch.
27*0fca6ea1SDimitry Andric   if (!AlarmThreadRunning())
28*0fca6ea1SDimitry Andric     return INVALID_HANDLE;
29*0fca6ea1SDimitry Andric 
30*0fca6ea1SDimitry Andric   // Compute the next expiration before we take the lock. This ensures that
31*0fca6ea1SDimitry Andric   // waiting on the lock doesn't eat into the timeout.
32*0fca6ea1SDimitry Andric   const TimePoint expiration = GetNextExpiration();
33*0fca6ea1SDimitry Andric 
34*0fca6ea1SDimitry Andric   Handle handle = INVALID_HANDLE;
35*0fca6ea1SDimitry Andric 
36*0fca6ea1SDimitry Andric   {
37*0fca6ea1SDimitry Andric     std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex);
38*0fca6ea1SDimitry Andric 
39*0fca6ea1SDimitry Andric     // Create a new unique entry and remember its handle.
40*0fca6ea1SDimitry Andric     m_entries.emplace_back(callback, expiration);
41*0fca6ea1SDimitry Andric     handle = m_entries.back().handle;
42*0fca6ea1SDimitry Andric 
43*0fca6ea1SDimitry Andric     // Tell the alarm thread we need to recompute the next alarm.
44*0fca6ea1SDimitry Andric     m_recompute_next_alarm = true;
45*0fca6ea1SDimitry Andric   }
46*0fca6ea1SDimitry Andric 
47*0fca6ea1SDimitry Andric   m_alarm_cv.notify_one();
48*0fca6ea1SDimitry Andric   return handle;
49*0fca6ea1SDimitry Andric }
50*0fca6ea1SDimitry Andric 
51*0fca6ea1SDimitry Andric bool Alarm::Restart(Handle handle) {
52*0fca6ea1SDimitry Andric   // Gracefully deal with the unlikely event that the alarm thread failed to
53*0fca6ea1SDimitry Andric   // launch.
54*0fca6ea1SDimitry Andric   if (!AlarmThreadRunning())
55*0fca6ea1SDimitry Andric     return false;
56*0fca6ea1SDimitry Andric 
57*0fca6ea1SDimitry Andric   // Compute the next expiration before we take the lock. This ensures that
58*0fca6ea1SDimitry Andric   // waiting on the lock doesn't eat into the timeout.
59*0fca6ea1SDimitry Andric   const TimePoint expiration = GetNextExpiration();
60*0fca6ea1SDimitry Andric 
61*0fca6ea1SDimitry Andric   {
62*0fca6ea1SDimitry Andric     std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex);
63*0fca6ea1SDimitry Andric 
64*0fca6ea1SDimitry Andric     // Find the entry corresponding to the given handle.
65*0fca6ea1SDimitry Andric     const auto it =
66*0fca6ea1SDimitry Andric         std::find_if(m_entries.begin(), m_entries.end(),
67*0fca6ea1SDimitry Andric                      [handle](Entry &entry) { return entry.handle == handle; });
68*0fca6ea1SDimitry Andric     if (it == m_entries.end())
69*0fca6ea1SDimitry Andric       return false;
70*0fca6ea1SDimitry Andric 
71*0fca6ea1SDimitry Andric     // Update the expiration.
72*0fca6ea1SDimitry Andric     it->expiration = expiration;
73*0fca6ea1SDimitry Andric 
74*0fca6ea1SDimitry Andric     // Tell the alarm thread we need to recompute the next alarm.
75*0fca6ea1SDimitry Andric     m_recompute_next_alarm = true;
76*0fca6ea1SDimitry Andric   }
77*0fca6ea1SDimitry Andric 
78*0fca6ea1SDimitry Andric   m_alarm_cv.notify_one();
79*0fca6ea1SDimitry Andric   return true;
80*0fca6ea1SDimitry Andric }
81*0fca6ea1SDimitry Andric 
82*0fca6ea1SDimitry Andric bool Alarm::Cancel(Handle handle) {
83*0fca6ea1SDimitry Andric   // Gracefully deal with the unlikely event that the alarm thread failed to
84*0fca6ea1SDimitry Andric   // launch.
85*0fca6ea1SDimitry Andric   if (!AlarmThreadRunning())
86*0fca6ea1SDimitry Andric     return false;
87*0fca6ea1SDimitry Andric 
88*0fca6ea1SDimitry Andric   {
89*0fca6ea1SDimitry Andric     std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex);
90*0fca6ea1SDimitry Andric 
91*0fca6ea1SDimitry Andric     const auto it =
92*0fca6ea1SDimitry Andric         std::find_if(m_entries.begin(), m_entries.end(),
93*0fca6ea1SDimitry Andric                      [handle](Entry &entry) { return entry.handle == handle; });
94*0fca6ea1SDimitry Andric 
95*0fca6ea1SDimitry Andric     if (it == m_entries.end())
96*0fca6ea1SDimitry Andric       return false;
97*0fca6ea1SDimitry Andric 
98*0fca6ea1SDimitry Andric     m_entries.erase(it);
99*0fca6ea1SDimitry Andric   }
100*0fca6ea1SDimitry Andric 
101*0fca6ea1SDimitry Andric   // No need to notify the alarm thread. This only affects the alarm thread if
102*0fca6ea1SDimitry Andric   // we removed the entry that corresponds to the next alarm. If that's the
103*0fca6ea1SDimitry Andric   // case, the thread will wake up as scheduled, find no expired events, and
104*0fca6ea1SDimitry Andric   // recompute the next alarm time.
105*0fca6ea1SDimitry Andric   return true;
106*0fca6ea1SDimitry Andric }
107*0fca6ea1SDimitry Andric 
108*0fca6ea1SDimitry Andric Alarm::Entry::Entry(Alarm::Callback callback, Alarm::TimePoint expiration)
109*0fca6ea1SDimitry Andric     : handle(Alarm::GetNextUniqueHandle()), callback(std::move(callback)),
110*0fca6ea1SDimitry Andric       expiration(std::move(expiration)) {}
111*0fca6ea1SDimitry Andric 
112*0fca6ea1SDimitry Andric void Alarm::StartAlarmThread() {
113*0fca6ea1SDimitry Andric   if (!m_alarm_thread.IsJoinable()) {
114*0fca6ea1SDimitry Andric     llvm::Expected<HostThread> alarm_thread = ThreadLauncher::LaunchThread(
115*0fca6ea1SDimitry Andric         "lldb.debugger.alarm-thread", [this] { return AlarmThread(); },
116*0fca6ea1SDimitry Andric         8 * 1024 * 1024); // Use larger 8MB stack for this thread
117*0fca6ea1SDimitry Andric     if (alarm_thread) {
118*0fca6ea1SDimitry Andric       m_alarm_thread = *alarm_thread;
119*0fca6ea1SDimitry Andric     } else {
120*0fca6ea1SDimitry Andric       LLDB_LOG_ERROR(GetLog(LLDBLog::Host), alarm_thread.takeError(),
121*0fca6ea1SDimitry Andric                      "failed to launch host thread: {0}");
122*0fca6ea1SDimitry Andric     }
123*0fca6ea1SDimitry Andric   }
124*0fca6ea1SDimitry Andric }
125*0fca6ea1SDimitry Andric 
126*0fca6ea1SDimitry Andric void Alarm::StopAlarmThread() {
127*0fca6ea1SDimitry Andric   if (m_alarm_thread.IsJoinable()) {
128*0fca6ea1SDimitry Andric     {
129*0fca6ea1SDimitry Andric       std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex);
130*0fca6ea1SDimitry Andric       m_exit = true;
131*0fca6ea1SDimitry Andric     }
132*0fca6ea1SDimitry Andric     m_alarm_cv.notify_one();
133*0fca6ea1SDimitry Andric     m_alarm_thread.Join(nullptr);
134*0fca6ea1SDimitry Andric   }
135*0fca6ea1SDimitry Andric }
136*0fca6ea1SDimitry Andric 
137*0fca6ea1SDimitry Andric bool Alarm::AlarmThreadRunning() { return m_alarm_thread.IsJoinable(); }
138*0fca6ea1SDimitry Andric 
139*0fca6ea1SDimitry Andric lldb::thread_result_t Alarm::AlarmThread() {
140*0fca6ea1SDimitry Andric   bool exit = false;
141*0fca6ea1SDimitry Andric   std::optional<TimePoint> next_alarm;
142*0fca6ea1SDimitry Andric 
143*0fca6ea1SDimitry Andric   const auto predicate = [this] { return m_exit || m_recompute_next_alarm; };
144*0fca6ea1SDimitry Andric 
145*0fca6ea1SDimitry Andric   while (!exit) {
146*0fca6ea1SDimitry Andric     // Synchronization between the main thread and the alarm thread using a
147*0fca6ea1SDimitry Andric     // mutex and condition variable. There are 2 reasons the thread can wake up:
148*0fca6ea1SDimitry Andric     //
149*0fca6ea1SDimitry Andric     // 1. The timeout for the next alarm expired.
150*0fca6ea1SDimitry Andric     //
151*0fca6ea1SDimitry Andric     // 2. The condition variable is notified that one of our shared variables
152*0fca6ea1SDimitry Andric     //    (see predicate) was modified. Either the thread is asked to shut down
153*0fca6ea1SDimitry Andric     //    or a new alarm came in and we need to recompute the next timeout.
154*0fca6ea1SDimitry Andric     //
155*0fca6ea1SDimitry Andric     // Below we only deal with the timeout expiring and fall through for dealing
156*0fca6ea1SDimitry Andric     // with the rest.
157*0fca6ea1SDimitry Andric     llvm::SmallVector<Callback, 1> callbacks;
158*0fca6ea1SDimitry Andric     {
159*0fca6ea1SDimitry Andric       std::unique_lock<std::mutex> alarm_lock(m_alarm_mutex);
160*0fca6ea1SDimitry Andric       if (next_alarm) {
161*0fca6ea1SDimitry Andric         if (!m_alarm_cv.wait_until(alarm_lock, *next_alarm, predicate)) {
162*0fca6ea1SDimitry Andric           // The timeout for the next alarm expired.
163*0fca6ea1SDimitry Andric 
164*0fca6ea1SDimitry Andric           // Clear the next timeout to signal that we need to recompute the next
165*0fca6ea1SDimitry Andric           // timeout.
166*0fca6ea1SDimitry Andric           next_alarm.reset();
167*0fca6ea1SDimitry Andric 
168*0fca6ea1SDimitry Andric           // Iterate over all the callbacks. Call the ones that have expired
169*0fca6ea1SDimitry Andric           // and remove them from the list.
170*0fca6ea1SDimitry Andric           const TimePoint now = std::chrono::system_clock::now();
171*0fca6ea1SDimitry Andric           auto it = m_entries.begin();
172*0fca6ea1SDimitry Andric           while (it != m_entries.end()) {
173*0fca6ea1SDimitry Andric             if (it->expiration <= now) {
174*0fca6ea1SDimitry Andric               callbacks.emplace_back(std::move(it->callback));
175*0fca6ea1SDimitry Andric               it = m_entries.erase(it);
176*0fca6ea1SDimitry Andric             } else {
177*0fca6ea1SDimitry Andric               it++;
178*0fca6ea1SDimitry Andric             }
179*0fca6ea1SDimitry Andric           }
180*0fca6ea1SDimitry Andric         }
181*0fca6ea1SDimitry Andric       } else {
182*0fca6ea1SDimitry Andric         m_alarm_cv.wait(alarm_lock, predicate);
183*0fca6ea1SDimitry Andric       }
184*0fca6ea1SDimitry Andric 
185*0fca6ea1SDimitry Andric       // Fall through after waiting on the condition variable. At this point
186*0fca6ea1SDimitry Andric       // either the predicate is true or we woke up because an alarm expired.
187*0fca6ea1SDimitry Andric 
188*0fca6ea1SDimitry Andric       // The alarm thread is shutting down.
189*0fca6ea1SDimitry Andric       if (m_exit) {
190*0fca6ea1SDimitry Andric         exit = true;
191*0fca6ea1SDimitry Andric         if (m_run_callbacks_on_exit) {
192*0fca6ea1SDimitry Andric           for (Entry &entry : m_entries)
193*0fca6ea1SDimitry Andric             callbacks.emplace_back(std::move(entry.callback));
194*0fca6ea1SDimitry Andric         }
195*0fca6ea1SDimitry Andric       }
196*0fca6ea1SDimitry Andric 
197*0fca6ea1SDimitry Andric       // A new alarm was added or an alarm expired. Either way we need to
198*0fca6ea1SDimitry Andric       // recompute when this thread should wake up for the next alarm.
199*0fca6ea1SDimitry Andric       if (m_recompute_next_alarm || !next_alarm) {
200*0fca6ea1SDimitry Andric         for (Entry &entry : m_entries) {
201*0fca6ea1SDimitry Andric           if (!next_alarm || entry.expiration < *next_alarm)
202*0fca6ea1SDimitry Andric             next_alarm = entry.expiration;
203*0fca6ea1SDimitry Andric         }
204*0fca6ea1SDimitry Andric         m_recompute_next_alarm = false;
205*0fca6ea1SDimitry Andric       }
206*0fca6ea1SDimitry Andric     }
207*0fca6ea1SDimitry Andric 
208*0fca6ea1SDimitry Andric     // Outside the lock, call the callbacks.
209*0fca6ea1SDimitry Andric     for (Callback &callback : callbacks)
210*0fca6ea1SDimitry Andric       callback();
211*0fca6ea1SDimitry Andric   }
212*0fca6ea1SDimitry Andric   return {};
213*0fca6ea1SDimitry Andric }
214*0fca6ea1SDimitry Andric 
215*0fca6ea1SDimitry Andric Alarm::TimePoint Alarm::GetNextExpiration() const {
216*0fca6ea1SDimitry Andric   return std::chrono::system_clock::now() + m_timeout;
217*0fca6ea1SDimitry Andric }
218*0fca6ea1SDimitry Andric 
219*0fca6ea1SDimitry Andric Alarm::Handle Alarm::GetNextUniqueHandle() {
220*0fca6ea1SDimitry Andric   static std::atomic<Handle> g_next_handle = 1;
221*0fca6ea1SDimitry Andric   return g_next_handle++;
222*0fca6ea1SDimitry Andric }
223