clang 24.0.0git
ModuleCache.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
10
14#include "llvm/ADT/ScopeExit.h"
15#include "llvm/Support/Error.h"
16#include "llvm/Support/FileSystem.h"
17#include "llvm/Support/IOSandbox.h"
18#include "llvm/Support/LockFileManager.h"
19#include "llvm/Support/Path.h"
20
21#include <chrono>
22
23using namespace clang;
24
26 auto [ByNameIt, ByNameInserted] = ByPath.insert({Path, nullptr});
27 if (!ByNameIt->second) {
28 // This is a compiler-internal input/output, let's bypass the sandbox.
29 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
30
31 // If we cannot get status of the module cache directory, try if trying to
32 // create it helps.
33 llvm::sys::fs::file_status Status;
34 if (std::error_code EC = llvm::sys::fs::status(Path, Status)) {
35 // Unless the status failed because the directory does not exist yet.
36 if (EC != std::errc::no_such_file_or_directory)
37 return nullptr;
38 // If we're unable to create the directory.
39 if (llvm::sys::fs::create_directories(Path))
40 return nullptr;
41 // If we're unable to stat the newly created directory.
42 if (llvm::sys::fs::status(Path, Status))
43 return nullptr;
44 }
45
46 llvm::sys::fs::UniqueID UID = Status.getUniqueID();
47 auto [ByUIDIt, ByUIDInserted] = ByUID.insert({UID, nullptr});
48 if (!ByUIDIt->second)
49 ByUIDIt->second = std::make_unique<ModuleCacheDirectory>();
50 ByNameIt->second = ByUIDIt->second.get();
51 }
52 return ByNameIt->second;
53}
54
55/// Write a new timestamp file with the given path.
56static void writeTimestampFile(StringRef TimestampFile) {
57 std::error_code EC;
58 llvm::raw_fd_ostream Out(TimestampFile.str(), EC, llvm::sys::fs::OF_None);
59}
60
61void clang::maybePruneImpl(StringRef Path, time_t PruneInterval,
62 time_t PruneAfter, bool PruneTopLevel,
63 llvm::function_ref<void(StringRef)> OnPrune) {
64 if (PruneInterval <= 0 || PruneAfter <= 0)
65 return;
66
67 // This is a compiler-internal input/output, let's bypass the sandbox.
68 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
69
70 llvm::SmallString<256> RootPath(Path);
71 (void)llvm::sys::fs::make_absolute(RootPath);
72
73 llvm::SmallString<128> TimestampFile(RootPath);
74 llvm::sys::path::append(TimestampFile, "modules.timestamp");
75
76 // Try to stat() the timestamp file.
77 llvm::sys::fs::file_status StatBuf;
78 if (std::error_code EC = llvm::sys::fs::status(TimestampFile, StatBuf)) {
79 // If the timestamp file wasn't there, create one now.
80 if (EC == std::errc::no_such_file_or_directory)
81 writeTimestampFile(TimestampFile);
82 return;
83 }
84
85 // Check whether the time stamp is older than our pruning interval.
86 // If not, do nothing.
87 time_t TimestampModTime =
88 llvm::sys::toTimeT(StatBuf.getLastModificationTime());
89 time_t CurrentTime = time(nullptr);
90 if (CurrentTime - TimestampModTime <= PruneInterval)
91 return;
92
93 // Write a new timestamp file so that nobody else attempts to prune.
94 // There is a benign race condition here, if two Clang instances happen to
95 // notice at the same time that the timestamp is out-of-date.
96 writeTimestampFile(TimestampFile);
97
98 auto NotifyPruned = [&](StringRef RemovedPath) {
99 if (OnPrune)
100 OnPrune(RemovedPath);
101 };
102
103 // Walk the entire module cache, looking for unused module files and module
104 // indices.
105 std::error_code EC;
106 auto TryPruneFile = [&](StringRef FilePath) {
107 // We only care about module and global module index files.
108 StringRef Filename = llvm::sys::path::filename(FilePath);
109 StringRef Extension = llvm::sys::path::extension(FilePath);
110 if (Extension != ".pcm" && Extension != ".timestamp" &&
111 Filename != "modules.idx")
112 return;
113
114 // Don't prune the pruning timestamp file.
115 if (Filename == "modules.timestamp")
116 return;
117
118 // Look at this file. If we can't stat it, there's nothing interesting
119 // there.
120 if (llvm::sys::fs::status(FilePath, StatBuf))
121 return;
122
123 // If the file has been used recently enough, leave it there.
124 time_t FileAccessTime = llvm::sys::toTimeT(StatBuf.getLastAccessedTime());
125 if (CurrentTime - FileAccessTime <= PruneAfter)
126 return;
127
128 // Remove the file.
129 if (!llvm::sys::fs::remove(FilePath))
130 NotifyPruned(FilePath);
131
132 // Remove the timestamp file created by implicit module builds.
133 std::string TimestampFilename = FilePath.str() + ".timestamp";
134 if (!llvm::sys::fs::remove(TimestampFilename))
135 NotifyPruned(TimestampFilename);
136 };
137
138 for (llvm::sys::fs::directory_iterator Dir(RootPath, EC), DirEnd;
139 Dir != DirEnd && !EC; Dir.increment(EC)) {
140 // If we don't have a directory, try to prune it as a file in the root.
141 if (!llvm::sys::fs::is_directory(Dir->path())) {
142 if (PruneTopLevel)
143 TryPruneFile(Dir->path());
144 continue;
145 }
146
147 // Walk all the files within this directory.
148 for (llvm::sys::fs::directory_iterator File(Dir->path(), EC), FileEnd;
149 File != FileEnd && !EC; File.increment(EC))
150 TryPruneFile(File->path());
151
152 // If we removed all the files in the directory, remove the directory
153 // itself.
154 if (llvm::sys::fs::directory_iterator(Dir->path(), EC) ==
155 llvm::sys::fs::directory_iterator() &&
156 !EC) {
157 if (!llvm::sys::fs::remove(Dir->path()))
158 NotifyPruned(Dir->path());
159 }
160 }
161}
162
163std::error_code clang::writeImpl(StringRef Path, llvm::MemoryBufferRef Buffer,
164 off_t &Size, time_t &ModTime) {
165 StringRef Extension = llvm::sys::path::extension(Path);
166 SmallString<128> ModelPath = StringRef(Path).drop_back(Extension.size());
167 ModelPath += "-%%%%%%%%";
168 ModelPath += Extension;
169 ModelPath += ".tmp";
170
171 std::error_code EC;
172 int FD;
173 SmallString<128> TmpPath;
174 if ((EC = llvm::sys::fs::createUniqueFile(ModelPath, FD, TmpPath))) {
175 if (EC != std::errc::no_such_file_or_directory)
176 return EC;
177
178 StringRef Dir = llvm::sys::path::parent_path(Path);
179 if (std::error_code InnerEC = llvm::sys::fs::create_directories(Dir))
180 return InnerEC;
181
182 if ((EC = llvm::sys::fs::createUniqueFile(ModelPath, FD, TmpPath)))
183 return EC;
184 }
185
186 llvm::sys::fs::file_status Status;
187 {
188 llvm::raw_fd_ostream OS(FD, /*shouldClose=*/true);
189 OS << Buffer.getBuffer();
190 // Make sure all contents reach the file before querying its status.
191 OS.flush();
192 // Using the status from an open file descriptor ensures this is not racy.
193 if ((EC = llvm::sys::fs::status(FD, Status)))
194 return EC;
195 }
196
197 Size = Status.getSize();
198 ModTime = llvm::sys::toTimeT(Status.getLastModificationTime());
199
200 // This preserves both size and modification time.
201 if ((EC = llvm::sys::fs::rename(TmpPath, Path)))
202 return EC;
203
204 return {};
205}
206
207/// The access time of a module file is only refreshed explicitly when it is
208/// older than this interval so we avoid frequent yet unnecessary updates.
209static constexpr std::chrono::seconds AccessTimeRefreshInterval(60);
210
211/// Record that the module file open as \p FD has just been used by setting its
212/// access time to now, if it is older than AccessTimeRefreshInterval. Module
213/// cache pruning goes by access time, and file systems don't reliably update it
214/// when a file is read (e.g., Linux relatime).
215static void
216refreshAccessTime([[maybe_unused]] llvm::sys::fs::file_t FD,
217 [[maybe_unused]] const llvm::sys::fs::file_status &Status) {
218// FIXME: Support Windows. The handle (FD) is opened for reading only, and
219// SetFileTime needs one with FILE_WRITE_ATTRIBUTES access.
220#ifndef _WIN32
221 llvm::sys::TimePoint<> Now = std::chrono::system_clock::now();
222 if (Now - Status.getLastAccessedTime() <= AccessTimeRefreshInterval)
223 return;
224
225 // Keep the modification time: modules importing this one record it.
226 // Go through the descriptor so a module file replaced meanwhile is left
227 // alone.
228 (void)llvm::sys::fs::setLastAccessAndModificationTime(
229 FD.get(), Now, Status.getLastModificationTime());
230#endif
231}
232
234clang::readImpl(StringRef FileName, off_t &Size, time_t &ModTime) {
236 llvm::sys::fs::openNativeFileForRead(FileName);
237 if (!FD)
238 return FD.takeError();
239 llvm::scope_exit CloseFD([&FD]() { llvm::sys::fs::closeFile(*FD); });
240 llvm::sys::fs::file_status Status;
241 if (std::error_code EC = llvm::sys::fs::status(*FD, Status))
242 return llvm::errorCodeToError(EC);
243 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Buf =
244 llvm::MemoryBuffer::getOpenFile(*FD, FileName, Status.getSize(),
245 /*RequiresNullTerminator=*/false);
246 if (!Buf)
247 return llvm::errorCodeToError(Buf.getError());
248 Size = Status.getSize();
249 ModTime = llvm::sys::toTimeT(Status.getLastModificationTime());
250 refreshAccessTime(*FD, Status);
251 return std::move(*Buf);
252}
253
254namespace {
255
256static AtomicLineLogger NoOpLogger;
257class CrossProcessModuleCache : public ModuleCache {
258 InMemoryModuleCache InMemory;
259
260public:
261 explicit CrossProcessModuleCache()
262 : ModuleCache(NoOpLogger), InMemory(NoOpLogger) {}
263
264 std::unique_ptr<llvm::AdvisoryLock>
265 getLock(StringRef ModuleFilename) override {
266 return std::make_unique<llvm::LockFileManager>(ModuleFilename);
267 }
268
269 std::time_t getModuleTimestamp(StringRef ModuleFilename) override {
270 // This is a compiler-internal input/output, let's bypass the sandbox.
271 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
272
273 std::string TimestampFilename =
275 llvm::sys::fs::file_status Status;
276 if (llvm::sys::fs::status(TimestampFilename, Status) != std::error_code{})
277 return 0;
278 return llvm::sys::toTimeT(Status.getLastModificationTime());
279 }
280
281 void updateModuleTimestamp(StringRef ModuleFilename) override {
282 // This is a compiler-internal input/output, let's bypass the sandbox.
283 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
284
285 // Overwrite the timestamp file contents so that file's mtime changes.
286 std::error_code EC;
287 llvm::raw_fd_ostream OS(
289 llvm::sys::fs::OF_TextWithCRLF);
290 if (EC)
291 return;
292 OS << "Timestamp file\n";
293 OS.close();
294 OS.clear_error(); // Avoid triggering a fatal error.
295 }
296
297 void maybePrune(StringRef Path, time_t PruneInterval,
298 time_t PruneAfter) override {
299 // This is a compiler-internal input/output, let's bypass the sandbox.
300 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
301
302 maybePruneImpl(Path, PruneInterval, PruneAfter);
303 }
304
305 InMemoryModuleCache &getInMemoryModuleCache() override { return InMemory; }
306 const InMemoryModuleCache &getInMemoryModuleCache() const override {
307 return InMemory;
308 }
309
310 std::error_code write(StringRef Path, llvm::MemoryBufferRef Buffer,
311 off_t &Size, time_t &ModTime) override {
312 // This is a compiler-internal input/output, let's bypass the sandbox.
313 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
314
315 return writeImpl(Path, Buffer, Size, ModTime);
316 }
317
318 Expected<std::unique_ptr<llvm::MemoryBuffer>>
319 read(StringRef FileName, off_t &Size, time_t &ModTime) override {
320 // This is a compiler-internal input/output, let's bypass the sandbox.
321 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
322
323 return readImpl(FileName, Size, ModTime);
324 }
325};
326} // namespace
327
328std::shared_ptr<ModuleCache> clang::createCrossProcessModuleCache() {
329 return std::make_shared<CrossProcessModuleCache>();
330}
Defines a logger where each line is written atomically to the file.
static constexpr std::chrono::seconds AccessTimeRefreshInterval(60)
The access time of a module file is only refreshed explicitly when it is older than this interval so ...
static void refreshAccessTime(llvm::sys::fs::file_t FD, const llvm::sys::fs::file_status &Status)
Record that the module file open as FD has just been used by setting its access time to now,...
static void writeTimestampFile(StringRef TimestampFile)
Write a new timestamp file with the given path.
In-memory cache for modules.
The address of an instance of this class represents the identity of a module cache directory.
Definition ModuleCache.h:36
The module cache used for compiling modules implicitly.
Definition ModuleCache.h:40
virtual const ModuleCacheDirectory * getDirectoryPtr(StringRef Path)
Returns an opaque pointer representing the module cache directory.
static std::string getTimestampFilename(StringRef FileName)
Definition ModuleFile.h:188
@ OS
Indicates that the tracking object is a descendant of a referenced-counted OSObject,...
Top level wrappers for InstallAPI frontend operations.
Expected< std::unique_ptr< llvm::MemoryBuffer > > readImpl(StringRef FileName, off_t &Size, time_t &ModTime)
Shared implementation of ModuleCache::read().
std::shared_ptr< ModuleCache > createCrossProcessModuleCache()
Creates new ModuleCache backed by a file system directory that may be operated on by multiple process...
std::error_code writeImpl(StringRef Path, llvm::MemoryBufferRef Buffer, off_t &Size, time_t &ModTime)
Shared implementation of ModuleCache::write().
void maybePruneImpl(StringRef Path, time_t PruneInterval, time_t PruneAfter, bool PruneTopLevel=false, llvm::function_ref< void(StringRef)> OnPrune={})
Shared implementation of ModuleCache::maybePrune().