1 | //===-- Progress.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/Core/Progress.h" |
10 | |
11 | #include "lldb/Core/Debugger.h" |
12 | #include "lldb/Utility/StreamString.h" |
13 | |
14 | #include <cstdint> |
15 | #include <mutex> |
16 | #include <optional> |
17 | |
18 | using namespace lldb; |
19 | using namespace lldb_private; |
20 | |
21 | std::atomic<uint64_t> Progress::g_id(0); |
22 | |
23 | Progress::Progress(std::string title, std::string details, |
24 | std::optional<uint64_t> total, |
25 | lldb_private::Debugger *debugger) |
26 | : m_details(details), m_completed(0), |
27 | m_total(Progress::kNonDeterministicTotal), |
28 | m_progress_data{.title: title, .progress_id: ++g_id, |
29 | /*m_progress_data.debugger_id=*/.debugger_id: std::nullopt} { |
30 | if (total) |
31 | m_total = *total; |
32 | |
33 | if (debugger) |
34 | m_progress_data.debugger_id = debugger->GetID(); |
35 | |
36 | std::lock_guard<std::mutex> guard(m_mutex); |
37 | ReportProgress(); |
38 | |
39 | // Report to the ProgressManager if that subsystem is enabled. |
40 | if (ProgressManager::Enabled()) |
41 | ProgressManager::Instance().Increment(m_progress_data); |
42 | } |
43 | |
44 | Progress::~Progress() { |
45 | // Make sure to always report progress completed when this object is |
46 | // destructed so it indicates the progress dialog/activity should go away. |
47 | std::lock_guard<std::mutex> guard(m_mutex); |
48 | if (!m_completed) |
49 | m_completed = m_total; |
50 | ReportProgress(); |
51 | |
52 | // Report to the ProgressManager if that subsystem is enabled. |
53 | if (ProgressManager::Enabled()) |
54 | ProgressManager::Instance().Decrement(m_progress_data); |
55 | } |
56 | |
57 | void Progress::Increment(uint64_t amount, |
58 | std::optional<std::string> updated_detail) { |
59 | if (amount > 0) { |
60 | std::lock_guard<std::mutex> guard(m_mutex); |
61 | if (updated_detail) |
62 | m_details = std::move(updated_detail.value()); |
63 | // Watch out for unsigned overflow and make sure we don't increment too |
64 | // much and exceed the total. |
65 | if (m_total && (amount > (m_total - m_completed))) |
66 | m_completed = m_total; |
67 | else |
68 | m_completed += amount; |
69 | ReportProgress(); |
70 | } |
71 | } |
72 | |
73 | void Progress::ReportProgress() { |
74 | if (!m_complete) { |
75 | // Make sure we only send one notification that indicates the progress is |
76 | // complete |
77 | m_complete = m_completed == m_total; |
78 | Debugger::ReportProgress(progress_id: m_progress_data.progress_id, title: m_progress_data.title, |
79 | details: m_details, completed: m_completed, total: m_total, |
80 | debugger_id: m_progress_data.debugger_id); |
81 | } |
82 | } |
83 | |
84 | ProgressManager::ProgressManager() |
85 | : m_entries(), m_alarm(std::chrono::milliseconds(100)) {} |
86 | |
87 | ProgressManager::~ProgressManager() {} |
88 | |
89 | void ProgressManager::Initialize() { |
90 | assert(!InstanceImpl() && "Already initialized." ); |
91 | InstanceImpl().emplace(); |
92 | } |
93 | |
94 | void ProgressManager::Terminate() { |
95 | assert(InstanceImpl() && "Already terminated." ); |
96 | InstanceImpl().reset(); |
97 | } |
98 | |
99 | bool ProgressManager::Enabled() { return InstanceImpl().operator bool(); } |
100 | |
101 | ProgressManager &ProgressManager::Instance() { |
102 | assert(InstanceImpl() && "ProgressManager must be initialized" ); |
103 | return *InstanceImpl(); |
104 | } |
105 | |
106 | std::optional<ProgressManager> &ProgressManager::InstanceImpl() { |
107 | static std::optional<ProgressManager> g_progress_manager; |
108 | return g_progress_manager; |
109 | } |
110 | |
111 | void ProgressManager::Increment(const Progress::ProgressData &progress_data) { |
112 | std::lock_guard<std::mutex> lock(m_entries_mutex); |
113 | |
114 | llvm::StringRef key = progress_data.title; |
115 | bool new_entry = !m_entries.contains(Key: key); |
116 | Entry &entry = m_entries[progress_data.title]; |
117 | |
118 | if (new_entry) { |
119 | // This is a new progress event. Report progress and store the progress |
120 | // data. |
121 | ReportProgress(progress_data, type: EventType::Begin); |
122 | entry.data = progress_data; |
123 | } else if (entry.refcount == 0) { |
124 | // This is an existing entry that was scheduled to be deleted but a new one |
125 | // came in before the timer expired. |
126 | assert(entry.handle != Alarm::INVALID_HANDLE); |
127 | |
128 | if (!m_alarm.Cancel(handle: entry.handle)) { |
129 | // The timer expired before we had a chance to cancel it. We have to treat |
130 | // this as an entirely new progress event. |
131 | ReportProgress(progress_data, type: EventType::Begin); |
132 | } |
133 | // Clear the alarm handle. |
134 | entry.handle = Alarm::INVALID_HANDLE; |
135 | } |
136 | |
137 | // Regardless of how we got here, we need to bump the reference count. |
138 | entry.refcount++; |
139 | } |
140 | |
141 | void ProgressManager::Decrement(const Progress::ProgressData &progress_data) { |
142 | std::lock_guard<std::mutex> lock(m_entries_mutex); |
143 | llvm::StringRef key = progress_data.title; |
144 | |
145 | if (!m_entries.contains(Key: key)) |
146 | return; |
147 | |
148 | Entry &entry = m_entries[key]; |
149 | entry.refcount--; |
150 | |
151 | if (entry.refcount == 0) { |
152 | assert(entry.handle == Alarm::INVALID_HANDLE); |
153 | |
154 | // Copy the key to a std::string so we can pass it by value to the lambda. |
155 | // The underlying StringRef will not exist by the time the callback is |
156 | // called. |
157 | std::string key_str = std::string(key); |
158 | |
159 | // Start a timer. If it expires before we see another progress event, it |
160 | // will be reported. |
161 | entry.handle = m_alarm.Create(callback: [=]() { Expire(key: key_str); }); |
162 | } |
163 | } |
164 | |
165 | void ProgressManager::ReportProgress( |
166 | const Progress::ProgressData &progress_data, EventType type) { |
167 | // The category bit only keeps track of when progress report categories have |
168 | // started and ended, so clear the details and reset other fields when |
169 | // broadcasting to it since that bit doesn't need that information. |
170 | const uint64_t completed = |
171 | (type == EventType::Begin) ? 0 : Progress::kNonDeterministicTotal; |
172 | Debugger::ReportProgress(progress_id: progress_data.progress_id, title: progress_data.title, details: "" , |
173 | completed, total: Progress::kNonDeterministicTotal, |
174 | debugger_id: progress_data.debugger_id, |
175 | progress_category_bit: Debugger::eBroadcastBitProgressCategory); |
176 | } |
177 | |
178 | void ProgressManager::Expire(llvm::StringRef key) { |
179 | std::lock_guard<std::mutex> lock(m_entries_mutex); |
180 | |
181 | // This shouldn't happen but be resilient anyway. |
182 | if (!m_entries.contains(Key: key)) |
183 | return; |
184 | |
185 | // A new event came in and the alarm fired before we had a chance to restart |
186 | // it. |
187 | if (m_entries[key].refcount != 0) |
188 | return; |
189 | |
190 | // We're done with this entry. |
191 | ReportProgress(progress_data: m_entries[key].data, type: EventType::End); |
192 | m_entries.erase(Key: key); |
193 | } |
194 | |