| //===-- BackgroundQueue.cpp - Task queue for background index -------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "index/Background.h" |
| #include "support/Logger.h" |
| |
| namespace clang { |
| namespace clangd { |
| |
| static std::atomic<bool> PreventStarvation = {false}; |
| |
| void BackgroundQueue::preventThreadStarvationInTests() { |
| PreventStarvation.store(true); |
| } |
| |
| void BackgroundQueue::work(std::function<void()> OnIdle) { |
| while (true) { |
| llvm::Optional<Task> Task; |
| { |
| std::unique_lock<std::mutex> Lock(Mu); |
| CV.wait(Lock, [&] { return ShouldStop || !Queue.empty(); }); |
| if (ShouldStop) { |
| Queue.clear(); |
| CV.notify_all(); |
| return; |
| } |
| ++Stat.Active; |
| std::pop_heap(Queue.begin(), Queue.end()); |
| Task = std::move(Queue.back()); |
| Queue.pop_back(); |
| notifyProgress(); |
| } |
| |
| if (Task->ThreadPri != llvm::ThreadPriority::Default && |
| !PreventStarvation.load()) |
| llvm::set_thread_priority(Task->ThreadPri); |
| Task->Run(); |
| if (Task->ThreadPri != llvm::ThreadPriority::Default) |
| llvm::set_thread_priority(llvm::ThreadPriority::Default); |
| |
| { |
| std::unique_lock<std::mutex> Lock(Mu); |
| ++Stat.Completed; |
| if (Stat.Active == 1 && Queue.empty()) { |
| // We just finished the last item, the queue is going idle. |
| assert(ShouldStop || Stat.Completed == Stat.Enqueued); |
| Stat.LastIdle = Stat.Completed; |
| if (OnIdle) { |
| Lock.unlock(); |
| OnIdle(); |
| Lock.lock(); |
| } |
| } |
| assert(Stat.Active > 0 && "before decrementing"); |
| --Stat.Active; |
| notifyProgress(); |
| } |
| CV.notify_all(); |
| } |
| } |
| |
| void BackgroundQueue::stop() { |
| { |
| std::lock_guard<std::mutex> QueueLock(Mu); |
| ShouldStop = true; |
| } |
| CV.notify_all(); |
| } |
| |
| // Tweaks the priority of a newly-enqueued task, or returns false to cancel it. |
| bool BackgroundQueue::adjust(Task &T) { |
| // It is tempting to drop duplicates of queued tasks, and merely deprioritize |
| // duplicates of completed tasks (i.e. reindexing on CDB changes). But: |
| // - the background indexer doesn't support reindexing well, e.g. staleness |
| // is checked at *enqueue* time only, and doesn't account for compile flags |
| // - reindexing on compile flags is often a poor use of CPU in practice |
| if (T.Key && !SeenKeys.insert(T.Key).second) |
| return false; |
| T.QueuePri = std::max(T.QueuePri, Boosts.lookup(T.Tag)); |
| return true; |
| } |
| |
| void BackgroundQueue::push(Task T) { |
| { |
| std::lock_guard<std::mutex> Lock(Mu); |
| if (!adjust(T)) |
| return; |
| Queue.push_back(std::move(T)); |
| std::push_heap(Queue.begin(), Queue.end()); |
| ++Stat.Enqueued; |
| notifyProgress(); |
| } |
| CV.notify_all(); |
| } |
| |
| void BackgroundQueue::append(std::vector<Task> Tasks) { |
| { |
| std::lock_guard<std::mutex> Lock(Mu); |
| for (Task &T : Tasks) { |
| if (!adjust(T)) |
| continue; |
| Queue.push_back(std::move(T)); |
| ++Stat.Enqueued; |
| } |
| std::make_heap(Queue.begin(), Queue.end()); |
| notifyProgress(); |
| } |
| CV.notify_all(); |
| } |
| |
| void BackgroundQueue::boost(llvm::StringRef Tag, unsigned NewPriority) { |
| std::lock_guard<std::mutex> Lock(Mu); |
| unsigned &Boost = Boosts[Tag]; |
| bool Increase = NewPriority > Boost; |
| Boost = NewPriority; |
| if (!Increase) |
| return; // existing tasks unaffected |
| |
| unsigned Changes = 0; |
| for (Task &T : Queue) |
| if (Tag == T.Tag && NewPriority > T.QueuePri) { |
| T.QueuePri = NewPriority; |
| ++Changes; |
| } |
| if (Changes) |
| std::make_heap(Queue.begin(), Queue.end()); |
| // No need to signal, only rearranged items in the queue. |
| } |
| |
| bool BackgroundQueue::blockUntilIdleForTest( |
| llvm::Optional<double> TimeoutSeconds) { |
| std::unique_lock<std::mutex> Lock(Mu); |
| return wait(Lock, CV, timeoutSeconds(TimeoutSeconds), |
| [&] { return Queue.empty() && Stat.Active == 0; }); |
| } |
| |
| void BackgroundQueue::notifyProgress() const { |
| dlog("Queue: {0}/{1} ({2} active). Last idle at {3}", Stat.Completed, |
| Stat.Enqueued, Stat.Active, Stat.LastIdle); |
| if (OnProgress) |
| OnProgress(Stat); |
| } |
| |
| } // namespace clangd |
| } // namespace clang |