xref: /llvm-project/clang-tools-extra/clangd/index/BackgroundQueue.cpp (revision f71ffd3b735b4d6ae3c12be1806cdd6205b3b378)
17e27d86aSSam McCall //===-- BackgroundQueue.cpp - Task queue for background index -------------===//
27e27d86aSSam McCall //
37e27d86aSSam McCall // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47e27d86aSSam McCall // See https://llvm.org/LICENSE.txt for license information.
57e27d86aSSam McCall // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67e27d86aSSam McCall //
77e27d86aSSam McCall //===----------------------------------------------------------------------===//
87e27d86aSSam McCall 
97e27d86aSSam McCall #include "index/Background.h"
10ad97ccf6SSam McCall #include "support/Logger.h"
1171f55735SKazu Hirata #include <optional>
127e27d86aSSam McCall 
137e27d86aSSam McCall namespace clang {
147e27d86aSSam McCall namespace clangd {
157e27d86aSSam McCall 
167e27d86aSSam McCall static std::atomic<bool> PreventStarvation = {false};
177e27d86aSSam McCall 
preventThreadStarvationInTests()187e27d86aSSam McCall void BackgroundQueue::preventThreadStarvationInTests() {
197e27d86aSSam McCall   PreventStarvation.store(true);
207e27d86aSSam McCall }
217e27d86aSSam McCall 
work(std::function<void ()> OnIdle)227e27d86aSSam McCall void BackgroundQueue::work(std::function<void()> OnIdle) {
237e27d86aSSam McCall   while (true) {
24*f71ffd3bSKazu Hirata     std::optional<Task> Task;
257e27d86aSSam McCall     {
267e27d86aSSam McCall       std::unique_lock<std::mutex> Lock(Mu);
277e27d86aSSam McCall       CV.wait(Lock, [&] { return ShouldStop || !Queue.empty(); });
287e27d86aSSam McCall       if (ShouldStop) {
297e27d86aSSam McCall         Queue.clear();
307e27d86aSSam McCall         CV.notify_all();
317e27d86aSSam McCall         return;
327e27d86aSSam McCall       }
337d20e802SSam McCall       ++Stat.Active;
347e27d86aSSam McCall       std::pop_heap(Queue.begin(), Queue.end());
357e27d86aSSam McCall       Task = std::move(Queue.back());
367e27d86aSSam McCall       Queue.pop_back();
377d20e802SSam McCall       notifyProgress();
387e27d86aSSam McCall     }
397e27d86aSSam McCall 
407e27d86aSSam McCall     if (Task->ThreadPri != llvm::ThreadPriority::Default &&
417e27d86aSSam McCall         !PreventStarvation.load())
427e27d86aSSam McCall       llvm::set_thread_priority(Task->ThreadPri);
437e27d86aSSam McCall     Task->Run();
447e27d86aSSam McCall     if (Task->ThreadPri != llvm::ThreadPriority::Default)
457e27d86aSSam McCall       llvm::set_thread_priority(llvm::ThreadPriority::Default);
467e27d86aSSam McCall 
477e27d86aSSam McCall     {
487e27d86aSSam McCall       std::unique_lock<std::mutex> Lock(Mu);
497d20e802SSam McCall       ++Stat.Completed;
507d20e802SSam McCall       if (Stat.Active == 1 && Queue.empty()) {
517e27d86aSSam McCall         // We just finished the last item, the queue is going idle.
527d20e802SSam McCall         assert(ShouldStop || Stat.Completed == Stat.Enqueued);
537d20e802SSam McCall         Stat.LastIdle = Stat.Completed;
547d20e802SSam McCall         if (OnIdle) {
557e27d86aSSam McCall           Lock.unlock();
567e27d86aSSam McCall           OnIdle();
577e27d86aSSam McCall           Lock.lock();
587e27d86aSSam McCall         }
597d20e802SSam McCall       }
607d20e802SSam McCall       assert(Stat.Active > 0 && "before decrementing");
617d20e802SSam McCall       --Stat.Active;
627d20e802SSam McCall       notifyProgress();
637e27d86aSSam McCall     }
647e27d86aSSam McCall     CV.notify_all();
657e27d86aSSam McCall   }
667e27d86aSSam McCall }
677e27d86aSSam McCall 
stop()687e27d86aSSam McCall void BackgroundQueue::stop() {
697e27d86aSSam McCall   {
707e27d86aSSam McCall     std::lock_guard<std::mutex> QueueLock(Mu);
717e27d86aSSam McCall     ShouldStop = true;
727e27d86aSSam McCall   }
737e27d86aSSam McCall   CV.notify_all();
747e27d86aSSam McCall }
757e27d86aSSam McCall 
7666d5994bSSam McCall // Tweaks the priority of a newly-enqueued task, or returns false to cancel it.
adjust(Task & T)7766d5994bSSam McCall bool BackgroundQueue::adjust(Task &T) {
7866d5994bSSam McCall   // It is tempting to drop duplicates of queued tasks, and merely deprioritize
7966d5994bSSam McCall   // duplicates of completed tasks (i.e. reindexing on CDB changes). But:
8066d5994bSSam McCall   //  - the background indexer doesn't support reindexing well, e.g. staleness
8166d5994bSSam McCall   //    is checked at *enqueue* time only, and doesn't account for compile flags
8266d5994bSSam McCall   //  - reindexing on compile flags is often a poor use of CPU in practice
8366d5994bSSam McCall   if (T.Key && !SeenKeys.insert(T.Key).second)
8466d5994bSSam McCall     return false;
8566d5994bSSam McCall   T.QueuePri = std::max(T.QueuePri, Boosts.lookup(T.Tag));
8666d5994bSSam McCall   return true;
8766d5994bSSam McCall }
8866d5994bSSam McCall 
push(Task T)897e27d86aSSam McCall void BackgroundQueue::push(Task T) {
907e27d86aSSam McCall   {
917e27d86aSSam McCall     std::lock_guard<std::mutex> Lock(Mu);
9266d5994bSSam McCall     if (!adjust(T))
9366d5994bSSam McCall       return;
947e27d86aSSam McCall     Queue.push_back(std::move(T));
957e27d86aSSam McCall     std::push_heap(Queue.begin(), Queue.end());
967d20e802SSam McCall     ++Stat.Enqueued;
977d20e802SSam McCall     notifyProgress();
987e27d86aSSam McCall   }
997e27d86aSSam McCall   CV.notify_all();
1007e27d86aSSam McCall }
1017e27d86aSSam McCall 
append(std::vector<Task> Tasks)1027e27d86aSSam McCall void BackgroundQueue::append(std::vector<Task> Tasks) {
1037e27d86aSSam McCall   {
1047e27d86aSSam McCall     std::lock_guard<std::mutex> Lock(Mu);
10566d5994bSSam McCall     for (Task &T : Tasks) {
10666d5994bSSam McCall       if (!adjust(T))
10766d5994bSSam McCall         continue;
10866d5994bSSam McCall       Queue.push_back(std::move(T));
10966d5994bSSam McCall       ++Stat.Enqueued;
11066d5994bSSam McCall     }
1117e27d86aSSam McCall     std::make_heap(Queue.begin(), Queue.end());
1127d20e802SSam McCall     notifyProgress();
1137e27d86aSSam McCall   }
1147e27d86aSSam McCall   CV.notify_all();
1157e27d86aSSam McCall }
1167e27d86aSSam McCall 
boost(llvm::StringRef Tag,unsigned NewPriority)1170f7146dbSSam McCall void BackgroundQueue::boost(llvm::StringRef Tag, unsigned NewPriority) {
1180f7146dbSSam McCall   std::lock_guard<std::mutex> Lock(Mu);
1190f7146dbSSam McCall   unsigned &Boost = Boosts[Tag];
1200f7146dbSSam McCall   bool Increase = NewPriority > Boost;
1210f7146dbSSam McCall   Boost = NewPriority;
1220f7146dbSSam McCall   if (!Increase)
1230f7146dbSSam McCall     return; // existing tasks unaffected
1240f7146dbSSam McCall 
1250f7146dbSSam McCall   unsigned Changes = 0;
1260f7146dbSSam McCall   for (Task &T : Queue)
1270f7146dbSSam McCall     if (Tag == T.Tag && NewPriority > T.QueuePri) {
1280f7146dbSSam McCall       T.QueuePri = NewPriority;
1290f7146dbSSam McCall       ++Changes;
1300f7146dbSSam McCall     }
1310f7146dbSSam McCall   if (Changes)
1320f7146dbSSam McCall     std::make_heap(Queue.begin(), Queue.end());
1330f7146dbSSam McCall   // No need to signal, only rearranged items in the queue.
1340f7146dbSSam McCall }
1350f7146dbSSam McCall 
blockUntilIdleForTest(std::optional<double> TimeoutSeconds)1367e27d86aSSam McCall bool BackgroundQueue::blockUntilIdleForTest(
137*f71ffd3bSKazu Hirata     std::optional<double> TimeoutSeconds) {
1387e27d86aSSam McCall   std::unique_lock<std::mutex> Lock(Mu);
1397e27d86aSSam McCall   return wait(Lock, CV, timeoutSeconds(TimeoutSeconds),
1407d20e802SSam McCall               [&] { return Queue.empty() && Stat.Active == 0; });
1417d20e802SSam McCall }
1427d20e802SSam McCall 
notifyProgress() const1437d20e802SSam McCall void BackgroundQueue::notifyProgress() const {
1447d20e802SSam McCall   dlog("Queue: {0}/{1} ({2} active). Last idle at {3}", Stat.Completed,
1457d20e802SSam McCall        Stat.Enqueued, Stat.Active, Stat.LastIdle);
1467d20e802SSam McCall   if (OnProgress)
1477d20e802SSam McCall     OnProgress(Stat);
1487e27d86aSSam McCall }
1497e27d86aSSam McCall 
1507e27d86aSSam McCall } // namespace clangd
1517e27d86aSSam McCall } // namespace clang
152