1 //===--- Background.h - Build an index in a background thread ----*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
9 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_BACKGROUND_H
10 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_BACKGROUND_H
12 #include "GlobalCompilationDatabase.h"
13 #include "SourceCode.h"
14 #include "index/BackgroundRebuild.h"
15 #include "index/FileIndex.h"
16 #include "index/Index.h"
17 #include "index/Serialization.h"
18 #include "support/Context.h"
19 #include "support/MemoryTree.h"
20 #include "support/Path.h"
21 #include "support/Threading.h"
22 #include "support/ThreadsafeFS.h"
23 #include "clang/Tooling/CompilationDatabase.h"
24 #include "llvm/ADT/StringMap.h"
25 #include "llvm/Support/Threading.h"
27 #include <condition_variable>
39 // Handles storage and retrieval of index shards. Both store and load
40 // operations can be called from multiple-threads concurrently.
41 class BackgroundIndexStorage
{
43 virtual ~BackgroundIndexStorage() = default;
45 // Shards of the index are stored and retrieved independently, keyed by shard
46 // identifier - in practice this is a source file name
47 virtual llvm::Error
storeShard(llvm::StringRef ShardIdentifier
,
48 IndexFileOut Shard
) const = 0;
50 // Tries to load shard with given identifier, returns nullptr if shard
51 // couldn't be loaded.
52 virtual std::unique_ptr
<IndexFileIn
>
53 loadShard(llvm::StringRef ShardIdentifier
) const = 0;
55 // The factory provides storage for each File.
56 // It keeps ownership of the storage instances, and should manage caching
57 // itself. Factory must be threadsafe and never returns nullptr.
58 using Factory
= llvm::unique_function
<BackgroundIndexStorage
*(PathRef
)>;
60 // Creates an Index Storage that saves shards into disk. Index storage uses
61 // CDBDirectory + ".cache/clangd/index/" as the folder to save shards.
62 // CDBDirectory is the first directory containing a CDB in parent directories
63 // of a file, or user cache directory if none was found, e.g. stdlib headers.
64 static Factory
createDiskBackedStorageFactory(
65 std::function
<std::optional
<ProjectInfo
>(PathRef
)> GetProjectInfo
);
68 // A priority queue of tasks which can be run on (external) worker threads.
69 class BackgroundQueue
{
71 /// A work item on the thread pool's queue.
73 explicit Task(std::function
<void()> Run
) : Run(std::move(Run
)) {}
75 std::function
<void()> Run
;
76 llvm::ThreadPriority ThreadPri
= llvm::ThreadPriority::Low
;
77 unsigned QueuePri
= 0; // Higher-priority tasks will run first.
78 std::string Tag
; // Allows priority to be boosted later.
79 uint64_t Key
= 0; // If the key matches a previous task, drop this one.
80 // (in practice this means we never reindex a file).
82 bool operator<(const Task
&O
) const { return QueuePri
< O
.QueuePri
; }
85 // Describes the number of tasks processed by the queue.
87 unsigned Enqueued
= 0; // Total number of tasks ever enqueued.
88 unsigned Active
= 0; // Tasks being currently processed by a worker.
89 unsigned Completed
= 0; // Tasks that have been finished.
90 unsigned LastIdle
= 0; // Number of completed tasks when last empty.
93 BackgroundQueue(std::function
<void(Stats
)> OnProgress
= nullptr)
94 : OnProgress(OnProgress
) {}
96 // Add tasks to the queue.
98 void append(std::vector
<Task
>);
99 // Boost priority of current and new tasks with matching Tag, if they are
101 // Reducing the boost of a tag affects future tasks but not current ones.
102 void boost(llvm::StringRef Tag
, unsigned NewPriority
);
104 // Process items on the queue until the queue is stopped.
105 // If the queue becomes empty, OnIdle will be called (on one worker).
106 void work(std::function
<void()> OnIdle
= nullptr);
108 // Stop processing new tasks, allowing all work() calls to return soon.
111 // Disables thread priority lowering to ensure progress on loaded systems.
112 // Only affects tasks that run after the call.
113 static void preventThreadStarvationInTests();
115 blockUntilIdleForTest(std::optional
<double> TimeoutSeconds
);
118 void notifyProgress() const; // Requires lock Mu
119 bool adjust(Task
&T
);
123 std::condition_variable CV
;
124 bool ShouldStop
= false;
125 std::vector
<Task
> Queue
; // max-heap
126 llvm::StringMap
<unsigned> Boosts
;
127 std::function
<void(Stats
)> OnProgress
;
128 llvm::DenseSet
<uint64_t> SeenKeys
;
131 // Builds an in-memory index by by running the static indexer action over
132 // all commands in a compilation database. Indexing happens in the background.
133 // FIXME: it should watch for changes to files on disk.
134 class BackgroundIndex
: public SwapIndex
{
137 // Arbitrary value to ensure some concurrency in tests.
138 // In production an explicit value is specified.
139 size_t ThreadPoolSize
= 4;
140 // Thread priority when indexing files.
141 llvm::ThreadPriority IndexingPriority
= llvm::ThreadPriority::Low
;
142 // Callback that provides notifications as indexing makes progress.
143 std::function
<void(BackgroundQueue::Stats
)> OnProgress
= nullptr;
144 // Function called to obtain the Context to use while indexing the specified
145 // file. Called with the empty string for other tasks.
146 // (When called, the context from BackgroundIndex construction is active).
147 std::function
<Context(PathRef
)> ContextProvider
= nullptr;
150 /// Creates a new background index and starts its threads.
151 /// The current Context will be propagated to each worker thread.
152 BackgroundIndex(const ThreadsafeFS
&, const GlobalCompilationDatabase
&CDB
,
153 BackgroundIndexStorage::Factory IndexStorageFactory
,
155 ~BackgroundIndex(); // Blocks while the current task finishes.
157 // Enqueue translation units for indexing.
158 // The indexing happens in a background thread, so the symbols will be
159 // available sometime later.
160 void enqueue(const std::vector
<std::string
> &ChangedFiles
) {
161 Queue
.push(changedFilesTask(ChangedFiles
));
164 /// Boosts priority of indexing related to Path.
165 /// Typically used to index TUs when headers are opened.
166 void boostRelated(llvm::StringRef Path
);
168 // Cause background threads to stop after ther current task, any remaining
169 // tasks will be discarded.
171 Rebuilder
.shutdown();
175 // Wait until the queue is empty, to allow deterministic testing.
177 blockUntilIdleForTest(std::optional
<double> TimeoutSeconds
= 10) {
178 return Queue
.blockUntilIdleForTest(TimeoutSeconds
);
181 void profile(MemoryTree
&MT
) const;
184 /// Represents the state of a single file when indexing was performed.
185 struct ShardVersion
{
186 FileDigest Digest
{{0}};
187 bool HadErrors
= false;
190 /// Given index results from a TU, only update symbols coming from files with
191 /// different digests than \p ShardVersionsSnapshot. Also stores new index
192 /// information on IndexStorage.
193 void update(llvm::StringRef MainFile
, IndexFileIn Index
,
194 const llvm::StringMap
<ShardVersion
> &ShardVersionsSnapshot
,
198 const ThreadsafeFS
&TFS
;
199 const GlobalCompilationDatabase
&CDB
;
200 llvm::ThreadPriority IndexingPriority
;
201 std::function
<Context(PathRef
)> ContextProvider
;
203 llvm::Error
index(tooling::CompileCommand
);
205 FileSymbols IndexedSymbols
;
206 BackgroundIndexRebuilder Rebuilder
;
207 llvm::StringMap
<ShardVersion
> ShardVersions
; // Key is absolute file path.
208 std::mutex ShardVersionsMu
;
210 BackgroundIndexStorage::Factory IndexStorageFactory
;
211 // Tries to load shards for the MainFiles and their dependencies.
212 std::vector
<std::string
> loadProject(std::vector
<std::string
> MainFiles
);
214 BackgroundQueue::Task
215 changedFilesTask(const std::vector
<std::string
> &ChangedFiles
);
216 BackgroundQueue::Task
indexFileTask(std::string Path
);
218 // from lowest to highest priority
224 BackgroundQueue Queue
;
225 AsyncTaskRunner ThreadPool
;
226 GlobalCompilationDatabase::CommandChanged::Subscription CommandsChanged
;
229 } // namespace clangd