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, ++g_id, 29 /*m_progress_data.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(m_progress_data.progress_id, m_progress_data.title, 79 m_details, m_completed, m_total, 80 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); 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, 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(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, 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)) 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([=]() { Expire(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_data.progress_id, progress_data.title, "", 173 completed, Progress::kNonDeterministicTotal, 174 progress_data.debugger_id, 175 lldb::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)) 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(m_entries[key].data, EventType::End); 192 m_entries.erase(key); 193 } 194