1 //===--- ConfigProvider.cpp - Loading of user configuration ---------------===//
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 "ConfigProvider.h"
10 #include "Config.h"
11 #include "ConfigFragment.h"
12 #include "support/ThreadsafeFS.h"
13 #include "support/Trace.h"
14 #include "llvm/ADT/ScopeExit.h"
15 #include "llvm/ADT/StringMap.h"
16 #include "llvm/Support/Path.h"
17 #include <chrono>
18 #include <mutex>
19
20 namespace clang {
21 namespace clangd {
22 namespace config {
23
24 // Threadsafe cache around reading a YAML config file from disk.
25 class FileConfigCache {
26 std::mutex Mu;
27 std::chrono::steady_clock::time_point ValidTime = {};
28 llvm::SmallVector<CompiledFragment, 1> CachedValue;
29 llvm::sys::TimePoint<> MTime = {};
30 unsigned Size = -1;
31
32 // Called once we are sure we want to read the file.
33 // REQUIRES: Cache keys are set. Mutex must be held.
fillCacheFromDisk(llvm::vfs::FileSystem & FS,DiagnosticCallback DC)34 void fillCacheFromDisk(llvm::vfs::FileSystem &FS, DiagnosticCallback DC) {
35 CachedValue.clear();
36
37 auto Buf = FS.getBufferForFile(Path);
38 // If we failed to read (but stat succeeded), don't cache failure.
39 if (!Buf) {
40 Size = -1;
41 MTime = {};
42 return;
43 }
44
45 // If file changed between stat and open, we don't know its mtime.
46 // For simplicity, don't cache the value in this case (use a bad key).
47 if (Buf->get()->getBufferSize() != Size) {
48 Size = -1;
49 MTime = {};
50 }
51
52 // Finally parse and compile the actual fragments.
53 for (auto &Fragment :
54 Fragment::parseYAML(Buf->get()->getBuffer(), Path, DC))
55 CachedValue.push_back(std::move(Fragment).compile(DC));
56 }
57
58 public:
59 // Must be set before the cache is used. Not a constructor param to allow
60 // computing ancestor-relative paths to be deferred.
61 std::string Path;
62
63 // Retrieves up-to-date config fragments from disk.
64 // A cached result may be reused if the mtime and size are unchanged.
65 // (But several concurrent read()s can miss the cache after a single change).
66 // Future performance ideas:
67 // - allow caches to be reused based on short elapsed walltime
68 // - allow latency-sensitive operations to skip revalidating the cache
read(const ThreadsafeFS & TFS,DiagnosticCallback DC,llvm::Optional<std::chrono::steady_clock::time_point> FreshTime,std::vector<CompiledFragment> & Out)69 void read(const ThreadsafeFS &TFS, DiagnosticCallback DC,
70 llvm::Optional<std::chrono::steady_clock::time_point> FreshTime,
71 std::vector<CompiledFragment> &Out) {
72 std::lock_guard<std::mutex> Lock(Mu);
73 // We're going to update the cache and return whatever's in it.
74 auto Return = llvm::make_scope_exit(
75 [&] { llvm::copy(CachedValue, std::back_inserter(Out)); });
76
77 // Return any sufficiently recent result without doing any further work.
78 if (FreshTime && ValidTime >= FreshTime)
79 return;
80
81 // Ensure we bump the ValidTime at the end to allow for reuse.
82 auto MarkTime = llvm::make_scope_exit(
83 [&] { ValidTime = std::chrono::steady_clock::now(); });
84
85 // Stat is cheaper than opening the file, it's usually unchanged.
86 assert(llvm::sys::path::is_absolute(Path));
87 auto FS = TFS.view(/*CWD=*/llvm::None);
88 auto Stat = FS->status(Path);
89 // If there's no file, the result is empty. Ensure we have an invalid key.
90 if (!Stat || !Stat->isRegularFile()) {
91 MTime = {};
92 Size = -1;
93 CachedValue.clear();
94 return;
95 }
96 // If the modified-time and size match, assume the content does too.
97 if (Size == Stat->getSize() && MTime == Stat->getLastModificationTime())
98 return;
99
100 // OK, the file has actually changed. Update cache key, compute new value.
101 Size = Stat->getSize();
102 MTime = Stat->getLastModificationTime();
103 fillCacheFromDisk(*FS, DC);
104 }
105 };
106
fromYAMLFile(llvm::StringRef AbsPath,const ThreadsafeFS & FS)107 std::unique_ptr<Provider> Provider::fromYAMLFile(llvm::StringRef AbsPath,
108 const ThreadsafeFS &FS) {
109 class AbsFileProvider : public Provider {
110 mutable FileConfigCache Cache; // threadsafe
111 const ThreadsafeFS &FS;
112
113 std::vector<CompiledFragment>
114 getFragments(const Params &P, DiagnosticCallback DC) const override {
115 std::vector<CompiledFragment> Result;
116 Cache.read(FS, DC, P.FreshTime, Result);
117 return Result;
118 };
119
120 public:
121 AbsFileProvider(llvm::StringRef Path, const ThreadsafeFS &FS) : FS(FS) {
122 assert(llvm::sys::path::is_absolute(Path));
123 Cache.Path = Path.str();
124 }
125 };
126
127 return std::make_unique<AbsFileProvider>(AbsPath, FS);
128 }
129
130 std::unique_ptr<Provider>
fromAncestorRelativeYAMLFiles(llvm::StringRef RelPath,const ThreadsafeFS & FS)131 Provider::fromAncestorRelativeYAMLFiles(llvm::StringRef RelPath,
132 const ThreadsafeFS &FS) {
133 class RelFileProvider : public Provider {
134 std::string RelPath;
135 const ThreadsafeFS &FS;
136
137 mutable std::mutex Mu;
138 // Keys are the ancestor directory, not the actual config path within it.
139 // We only insert into this map, so pointers to values are stable forever.
140 // Mutex guards the map itself, not the values (which are threadsafe).
141 mutable llvm::StringMap<FileConfigCache> Cache;
142
143 std::vector<CompiledFragment>
144 getFragments(const Params &P, DiagnosticCallback DC) const override {
145 namespace path = llvm::sys::path;
146
147 if (P.Path.empty())
148 return {};
149
150 // Compute absolute paths to all ancestors (substrings of P.Path).
151 llvm::StringRef Parent = path::parent_path(P.Path);
152 llvm::SmallVector<llvm::StringRef, 8> Ancestors;
153 for (auto I = path::begin(Parent, path::Style::posix),
154 E = path::end(Parent);
155 I != E; ++I) {
156 // Avoid weird non-substring cases like phantom "." components.
157 // In practice, Component is a substring for all "normal" ancestors.
158 if (I->end() < Parent.begin() && I->end() > Parent.end())
159 continue;
160 Ancestors.emplace_back(Parent.begin(), I->end() - Parent.begin());
161 }
162 // Ensure corresponding cache entries exist in the map.
163 llvm::SmallVector<FileConfigCache *, 8> Caches;
164 {
165 std::lock_guard<std::mutex> Lock(Mu);
166 for (llvm::StringRef Ancestor : Ancestors) {
167 auto R = Cache.try_emplace(Ancestor);
168 // Assemble the actual config file path only once.
169 if (R.second) {
170 llvm::SmallString<256> ConfigPath = Ancestor;
171 path::append(ConfigPath, RelPath);
172 R.first->second.Path = ConfigPath.str().str();
173 }
174 Caches.push_back(&R.first->second);
175 }
176 }
177 // Finally query each individual file.
178 // This will take a (per-file) lock for each file that actually exists.
179 std::vector<CompiledFragment> Result;
180 for (FileConfigCache *Cache : Caches)
181 Cache->read(FS, DC, P.FreshTime, Result);
182 return Result;
183 };
184
185 public:
186 RelFileProvider(llvm::StringRef RelPath, const ThreadsafeFS &FS)
187 : RelPath(RelPath), FS(FS) {
188 assert(llvm::sys::path::is_relative(RelPath));
189 }
190 };
191
192 return std::make_unique<RelFileProvider>(RelPath, FS);
193 }
194
195 std::unique_ptr<Provider>
combine(std::vector<const Provider * > Providers)196 Provider::combine(std::vector<const Provider *> Providers) {
197 struct CombinedProvider : Provider {
198 std::vector<const Provider *> Providers;
199
200 std::vector<CompiledFragment>
201 getFragments(const Params &P, DiagnosticCallback DC) const override {
202 std::vector<CompiledFragment> Result;
203 for (const auto &Provider : Providers) {
204 for (auto &Fragment : Provider->getFragments(P, DC))
205 Result.push_back(std::move(Fragment));
206 }
207 return Result;
208 }
209 };
210 auto Result = std::make_unique<CombinedProvider>();
211 Result->Providers = std::move(Providers);
212 return Result;
213 }
214
getConfig(const Params & P,DiagnosticCallback DC) const215 Config Provider::getConfig(const Params &P, DiagnosticCallback DC) const {
216 trace::Span Tracer("getConfig");
217 if (!P.Path.empty())
218 SPAN_ATTACH(Tracer, "path", P.Path);
219 Config C;
220 for (const auto &Fragment : getFragments(P, DC))
221 Fragment(P, C);
222 return C;
223 }
224
225 } // namespace config
226 } // namespace clangd
227 } // namespace clang
228