https://github.com/artemcm updated https://github.com/llvm/llvm-project/pull/199680
>From 73e486c8e67a6272cbbe7100608c55baafe6f9a4 Mon Sep 17 00:00:00 2001 From: Artem Chikin <[email protected]> Date: Thu, 28 May 2026 11:28:31 +0100 Subject: [PATCH 1/2] [Support][VFS] Make `TracingFileSystem` a template on its counter type Templating `TracingFileSystem` on the counter type lets the same proxy serve single-threaded use (with `std::size_t` counters) and concurrent use (with `std::atomic<std::size_t>`) without duplicating the class. Introduce `TracingFileSystemImpl<CounterT>` as the underlying class template and provide two aliases: using TracingFileSystem = TracingFileSystemImpl<std::size_t>; using AtomicTracingFileSystem = TracingFileSystemImpl<std::atomic<std::size_t>>; Existing callers continue to use `TracingFileSystem` unchanged. A new `TracingFileSystemTest.AtomicCountersUnderConcurrency` test exercises the atomic instantiation with eight threads. --- llvm/include/llvm/Support/VirtualFileSystem.h | 69 +++++++++++++++---- llvm/lib/Support/VirtualFileSystem.cpp | 26 ------- .../Support/VirtualFileSystemTest.cpp | 30 ++++++++ 3 files changed, 86 insertions(+), 39 deletions(-) diff --git a/llvm/include/llvm/Support/VirtualFileSystem.h b/llvm/include/llvm/Support/VirtualFileSystem.h index 5b8871b8f3db5..d22c534228331 100644 --- a/llvm/include/llvm/Support/VirtualFileSystem.h +++ b/llvm/include/llvm/Support/VirtualFileSystem.h @@ -27,6 +27,7 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/SourceMgr.h" +#include <atomic> #include <cassert> #include <cstdint> #include <ctime> @@ -1156,20 +1157,28 @@ class YAMLVFSWriter { /// File system that tracks the number of calls to the underlying file system. /// This is particularly useful when wrapped around \c RealFileSystem to add /// lightweight tracking of expensive syscalls. -class LLVM_ABI TracingFileSystem - : public llvm::RTTIExtends<TracingFileSystem, ProxyFileSystem> { +/// +/// Templated on the counter type so callers can choose between non-atomic +/// counters (suitable for single-threaded tracing) and atomic counters +/// (suitable for tracing under concurrent access). Use the +/// \c TracingFileSystem and \c AtomicTracingFileSystem aliases below. +template <typename CounterT> +class TracingFileSystemImpl + : public llvm::RTTIExtends<TracingFileSystemImpl<CounterT>, + ProxyFileSystem> { public: - static const char ID; + inline static const char ID = 0; - std::size_t NumStatusCalls = 0; - std::size_t NumOpenFileForReadCalls = 0; - std::size_t NumDirBeginCalls = 0; - std::size_t NumGetRealPathCalls = 0; - std::size_t NumExistsCalls = 0; - std::size_t NumIsLocalCalls = 0; + CounterT NumStatusCalls = 0; + CounterT NumOpenFileForReadCalls = 0; + CounterT NumDirBeginCalls = 0; + CounterT NumGetRealPathCalls = 0; + CounterT NumExistsCalls = 0; + CounterT NumIsLocalCalls = 0; - TracingFileSystem(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) - : RTTIExtends(std::move(FS)) {} + TracingFileSystemImpl(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) + : llvm::RTTIExtends<TracingFileSystemImpl<CounterT>, ProxyFileSystem>( + std::move(FS)) {} ErrorOr<Status> status(const Twine &Path) override { ++NumStatusCalls; @@ -1203,10 +1212,44 @@ class LLVM_ABI TracingFileSystem } protected: - void printImpl(raw_ostream &OS, PrintType Type, - unsigned IndentLevel) const override; + void printImpl(raw_ostream &OS, FileSystem::PrintType Type, + unsigned IndentLevel) const override { + FileSystem::printIndent(OS, IndentLevel); + OS << "TracingFileSystem\n"; + if (Type == FileSystem::PrintType::Summary) + return; + + FileSystem::printIndent(OS, IndentLevel); + OS << "NumStatusCalls=" << static_cast<std::size_t>(NumStatusCalls) << "\n"; + FileSystem::printIndent(OS, IndentLevel); + OS << "NumOpenFileForReadCalls=" + << static_cast<std::size_t>(NumOpenFileForReadCalls) << "\n"; + FileSystem::printIndent(OS, IndentLevel); + OS << "NumDirBeginCalls=" << static_cast<std::size_t>(NumDirBeginCalls) + << "\n"; + FileSystem::printIndent(OS, IndentLevel); + OS << "NumGetRealPathCalls=" + << static_cast<std::size_t>(NumGetRealPathCalls) << "\n"; + FileSystem::printIndent(OS, IndentLevel); + OS << "NumExistsCalls=" << static_cast<std::size_t>(NumExistsCalls) << "\n"; + FileSystem::printIndent(OS, IndentLevel); + OS << "NumIsLocalCalls=" << static_cast<std::size_t>(NumIsLocalCalls) + << "\n"; + + if (Type == FileSystem::PrintType::Contents) + Type = FileSystem::PrintType::Summary; + this->getUnderlyingFS().print(OS, Type, IndentLevel + 1); + } }; +/// Single-threaded tracing filesystem. Counters are plain \c std::size_t and +/// must not be incremented concurrently. +using TracingFileSystem = TracingFileSystemImpl<std::size_t>; + +/// Concurrent-safe tracing filesystem. Counters are \c std::atomic<std::size_t> +/// so the proxy can be shared across threads. +using AtomicTracingFileSystem = TracingFileSystemImpl<std::atomic<std::size_t>>; + } // namespace vfs } // namespace llvm diff --git a/llvm/lib/Support/VirtualFileSystem.cpp b/llvm/lib/Support/VirtualFileSystem.cpp index 69f3bb8582b87..42e8bb4f9958e 100644 --- a/llvm/lib/Support/VirtualFileSystem.cpp +++ b/llvm/lib/Support/VirtualFileSystem.cpp @@ -2991,34 +2991,8 @@ recursive_directory_iterator::increment(std::error_code &EC) { return *this; } -void TracingFileSystem::printImpl(raw_ostream &OS, PrintType Type, - unsigned IndentLevel) const { - printIndent(OS, IndentLevel); - OS << "TracingFileSystem\n"; - if (Type == PrintType::Summary) - return; - - printIndent(OS, IndentLevel); - OS << "NumStatusCalls=" << NumStatusCalls << "\n"; - printIndent(OS, IndentLevel); - OS << "NumOpenFileForReadCalls=" << NumOpenFileForReadCalls << "\n"; - printIndent(OS, IndentLevel); - OS << "NumDirBeginCalls=" << NumDirBeginCalls << "\n"; - printIndent(OS, IndentLevel); - OS << "NumGetRealPathCalls=" << NumGetRealPathCalls << "\n"; - printIndent(OS, IndentLevel); - OS << "NumExistsCalls=" << NumExistsCalls << "\n"; - printIndent(OS, IndentLevel); - OS << "NumIsLocalCalls=" << NumIsLocalCalls << "\n"; - - if (Type == PrintType::Contents) - Type = PrintType::Summary; - getUnderlyingFS().print(OS, Type, IndentLevel + 1); -} - const char FileSystem::ID = 0; const char OverlayFileSystem::ID = 0; const char ProxyFileSystem::ID = 0; const char InMemoryFileSystem::ID = 0; const char RedirectingFileSystem::ID = 0; -const char TracingFileSystem::ID = 0; diff --git a/llvm/unittests/Support/VirtualFileSystemTest.cpp b/llvm/unittests/Support/VirtualFileSystemTest.cpp index bb0172b28e373..507a0e4108a79 100644 --- a/llvm/unittests/Support/VirtualFileSystemTest.cpp +++ b/llvm/unittests/Support/VirtualFileSystemTest.cpp @@ -22,6 +22,8 @@ #include "gtest/gtest.h" #include <map> #include <string> +#include <thread> +#include <vector> using namespace llvm; using llvm::sys::fs::UniqueID; @@ -3732,3 +3734,31 @@ TEST(TracingFileSystemTest, PrintOutput) { " InMemoryFileSystem\n", Output); } + +TEST(TracingFileSystemTest, AtomicCountersUnderConcurrency) { + auto InMemoryFS = makeIntrusiveRefCnt<vfs::InMemoryFileSystem>(); + InMemoryFS->setCurrentWorkingDirectory("/"); + InMemoryFS->addFile("/foo", 0, MemoryBuffer::getMemBuffer("hello")); + + auto TracingFS = + makeIntrusiveRefCnt<vfs::AtomicTracingFileSystem>(std::move(InMemoryFS)); + + constexpr unsigned NumThreads = 8; + constexpr unsigned CallsPerThread = 100; + std::vector<std::thread> Threads; + Threads.reserve(NumThreads); + for (unsigned I = 0; I < NumThreads; ++I) { + Threads.emplace_back([&] { + for (unsigned J = 0; J < CallsPerThread; ++J) { + (void)TracingFS->status("/foo"); + (void)TracingFS->openFileForRead("/foo"); + } + }); + } + for (auto &T : Threads) + T.join(); + + EXPECT_EQ(TracingFS->NumStatusCalls.load(), NumThreads * CallsPerThread); + EXPECT_EQ(TracingFS->NumOpenFileForReadCalls.load(), + NumThreads * CallsPerThread); +} >From d37034867dd53b370c785910c71abc6f0e7b7a98 Mon Sep 17 00:00:00 2001 From: Artem Chikin <[email protected]> Date: Thu, 28 May 2026 11:29:31 +0100 Subject: [PATCH 2/2] [clang][deps] Add in-flight query caching to `DependencyScanningFilesystemSharedCache` The shared cache deduplicates results only after the underlying filesystem trip completes, so concurrent workers querying the same filename or UID each pay their own `stat` and `open`. Track in-flight queries per key: the first arrival produces the result, late arrivals wait on a condition variable and adopt it. `CacheByFilename`'s value gains a `std::shared_ptr<InProgressEntry>` field alongside the resolved entry and real path; `EntriesByUID` does the same. The in-progress entry is populated for the duration of a producer's filesystem trip and reset on publish. Four new shard methods manage the slot lifecycle: - `acquireFilenameSlot` / `acquireUIDSlot` collapse three outcomes (resolved hit, in-progress wait, fresh producer) into one critical section. Waiters block on the slot's condition variable, which atomically releases the shard lock for the duration of the wait. - `fulfilFilenameSlot` / `fulfilUIDSlot` publish the produced entry, set `Done`, clear the slot, and `notify_all` waiters outside the lock. --- .../DependencyScanningFilesystem.h | 215 +++++-------- .../DependencyScanningFilesystem.cpp | 287 +++++++++--------- .../DependencyScanning/CMakeLists.txt | 2 + .../DependencyScanningFilesystemTest.cpp | 211 +++++++++++++ 4 files changed, 427 insertions(+), 288 deletions(-) diff --git a/clang/include/clang/DependencyScanning/DependencyScanningFilesystem.h b/clang/include/clang/DependencyScanning/DependencyScanningFilesystem.h index c9ffc202ad694..ec95c5b38f102 100644 --- a/clang/include/clang/DependencyScanning/DependencyScanningFilesystem.h +++ b/clang/include/clang/DependencyScanning/DependencyScanningFilesystem.h @@ -16,6 +16,8 @@ #include "llvm/Support/Allocator.h" #include "llvm/Support/ErrorOr.h" #include "llvm/Support/VirtualFileSystem.h" +#include <condition_variable> +#include <memory> #include <mutex> #include <optional> #include <variant> @@ -154,19 +156,53 @@ using CachedRealPath = llvm::ErrorOr<std::string>; /// the worker threads. class DependencyScanningFilesystemSharedCache { public: + /// In-flight slot used to dedup concurrent producers for the same key. + /// The producer publishes via \c publish(); waiters block on \c Mutex/CV + /// until \c Done is set. \c Result holds the resolved entry, or an + /// uncached-negative error shared with overlapping waiters but not + /// persisted in the shard. + struct InProgressEntry { + std::mutex Mutex; + std::condition_variable CondVar; + bool Done = false; + llvm::ErrorOr<const CachedFileSystemEntry *> Result = std::error_code{}; + + /// Publishes the producer's outcome to this slot and wakes all waiters. + void publish(llvm::ErrorOr<const CachedFileSystemEntry *> R) { + { + std::lock_guard<std::mutex> EntryLock(Mutex); + assert(!Done && "slot already published"); + Result = R; + Done = true; + } + CondVar.notify_all(); + } + }; + struct CacheShard { /// The mutex that needs to be locked before mutation of any member. mutable std::mutex CacheLock; - /// Map from filenames to cached entries and real paths. - llvm::StringMap< - std::pair<const CachedFileSystemEntry *, const CachedRealPath *>, - llvm::BumpPtrAllocator> - CacheByFilename; + /// Cache state per filename: resolved entry, real path, and an in-flight + /// slot (if any). \c InProgress is reset on publish. + struct FilenameCacheState { + const CachedFileSystemEntry *Entry = nullptr; + const CachedRealPath *RealPath = nullptr; + std::shared_ptr<InProgressEntry> InProgress; + }; + + /// Cache state stored per unique ID; similar to + /// \c FilenameCacheState. + struct UIDCacheState { + const CachedFileSystemEntry *Entry = nullptr; + std::shared_ptr<InProgressEntry> InProgress; + }; + + /// Map from filenames to their cached state. + llvm::StringMap<FilenameCacheState, llvm::BumpPtrAllocator> CacheByFilename; - /// Map from unique IDs to cached entries. - llvm::DenseMap<llvm::sys::fs::UniqueID, const CachedFileSystemEntry *> - EntriesByUID; + /// Map from unique IDs to their cached state. + llvm::DenseMap<llvm::sys::fs::UniqueID, UIDCacheState> EntriesByUID; /// The backing storage for cached entries. llvm::SpecificBumpPtrAllocator<CachedFileSystemEntry> EntryStorage; @@ -177,33 +213,6 @@ class DependencyScanningFilesystemSharedCache { /// The backing storage for cached real paths. llvm::SpecificBumpPtrAllocator<CachedRealPath> RealPathStorage; - /// Returns entry associated with the filename or nullptr if none is found. - const CachedFileSystemEntry *findEntryByFilename(StringRef Filename) const; - - /// Returns entry associated with the unique ID or nullptr if none is found. - const CachedFileSystemEntry * - findEntryByUID(llvm::sys::fs::UniqueID UID) const; - - /// Returns entry associated with the filename if there is some. Otherwise, - /// constructs new one with the given status, associates it with the - /// filename and returns the result. - const CachedFileSystemEntry & - getOrEmplaceEntryForFilename(StringRef Filename, - llvm::ErrorOr<llvm::vfs::Status> Stat); - - /// Returns entry associated with the unique ID if there is some. Otherwise, - /// constructs new one with the given status and contents, associates it - /// with the unique ID and returns the result. - const CachedFileSystemEntry & - getOrEmplaceEntryForUID(llvm::sys::fs::UniqueID UID, llvm::vfs::Status Stat, - std::unique_ptr<llvm::MemoryBuffer> Contents); - - /// Returns entry associated with the filename if there is some. Otherwise, - /// associates the given entry with the filename and returns it. - const CachedFileSystemEntry & - getOrInsertEntryForFilename(StringRef Filename, - const CachedFileSystemEntry &Entry); - /// Returns the real path associated with the filename or nullptr if none is /// found. const CachedRealPath *findRealPathByFilename(StringRef Filename) const; @@ -256,65 +265,6 @@ class DependencyScanningFilesystemSharedCache { unsigned NumShards; }; -/// This class is a local cache, that caches the 'stat' and 'open' calls to the -/// underlying real file system. -class DependencyScanningFilesystemLocalCache { - llvm::StringMap< - std::pair<const CachedFileSystemEntry *, const CachedRealPath *>, - llvm::BumpPtrAllocator> - Cache; - -public: - /// Returns entry associated with the filename or nullptr if none is found. - const CachedFileSystemEntry *findEntryByFilename(StringRef Filename) const { - assert(llvm::sys::path::is_absolute_gnu(Filename)); - auto It = Cache.find(Filename); - return It == Cache.end() ? nullptr : It->getValue().first; - } - - /// Associates the given entry with the filename and returns the given entry - /// pointer (for convenience). - const CachedFileSystemEntry & - insertEntryForFilename(StringRef Filename, - const CachedFileSystemEntry &Entry) { - assert(llvm::sys::path::is_absolute_gnu(Filename)); - auto [It, Inserted] = Cache.insert({Filename, {&Entry, nullptr}}); - auto &[CachedEntry, CachedRealPath] = It->getValue(); - if (!Inserted) { - // The file is already present in the local cache. If we got here, it only - // contains the real path. Let's make sure the entry is populated too. - assert((!CachedEntry && CachedRealPath) && "entry already present"); - CachedEntry = &Entry; - } - return *CachedEntry; - } - - /// Returns real path associated with the filename or nullptr if none is - /// found. - const CachedRealPath *findRealPathByFilename(StringRef Filename) const { - assert(llvm::sys::path::is_absolute_gnu(Filename)); - auto It = Cache.find(Filename); - return It == Cache.end() ? nullptr : It->getValue().second; - } - - /// Associates the given real path with the filename and returns the given - /// entry pointer (for convenience). - const CachedRealPath & - insertRealPathForFilename(StringRef Filename, - const CachedRealPath &RealPath) { - assert(llvm::sys::path::is_absolute_gnu(Filename)); - auto [It, Inserted] = Cache.insert({Filename, {nullptr, &RealPath}}); - auto &[CachedEntry, CachedRealPath] = It->getValue(); - if (!Inserted) { - // The file is already present in the local cache. If we got here, it only - // contains the entry. Let's make sure the real path is populated too. - assert((!CachedRealPath && CachedEntry) && "real path already present"); - CachedRealPath = &RealPath; - } - return *CachedRealPath; - } -}; - /// Reference to a CachedFileSystemEntry. /// If the underlying entry is an opened file, this wrapper returns the file /// contents and the scanned preprocessor directives. @@ -411,14 +361,23 @@ class DependencyScanningWorkerFilesystem bool exists(const Twine &Path) override; private: - /// For a filename that's not yet associated with any entry in the caches, - /// uses the underlying filesystem to either look up the entry based in the - /// shared cache indexed by unique ID, or creates new entry from scratch. - /// \p FilenameForLookup will always be an absolute path, and different than - /// \p OriginalFilename if \p OriginalFilename is relative. - llvm::ErrorOr<const CachedFileSystemEntry &> - computeAndStoreResult(StringRef OriginalFilename, - StringRef FilenameForLookup); + /// Resolves the cache entry for \p FilenameForLookup through the shared + /// cache: returns an entry already produced by another worker (a cache hit + /// or the result of an in-flight wait), or claims the producer slot and + /// computes the entry via the underlying filesystem. + /// \p FilenameForLookup is always absolute, and may differ from + /// \p OriginalFilename if the latter is relative. + llvm::ErrorOr<const CachedFileSystemEntry *> + resolveFilenameThroughSharedCache(StringRef OriginalFilename, + StringRef FilenameForLookup); + + /// Resolves the cache entry for the on-disk file identified by \p Stat + /// through the UID-keyed shared cache. Reads the file's contents on the + /// producer path. Always returns a non-null entry (which may carry a + /// readFile error). + const CachedFileSystemEntry * + resolveUIDThroughSharedCache(StringRef OriginalFilename, + const llvm::vfs::Status &Stat); /// Represents a filesystem entry that has been stat-ed (and potentially read) /// and that's about to be inserted into the cache as `CachedFileSystemEntry`. @@ -435,46 +394,6 @@ class DependencyScanningWorkerFilesystem /// in status and size of read contents. llvm::ErrorOr<TentativeEntry> readFile(StringRef Filename); - /// Returns entry associated with the unique ID of the given tentative entry - /// if there is some in the shared cache. Otherwise, constructs new one, - /// associates it with the unique ID and returns the result. - const CachedFileSystemEntry & - getOrEmplaceSharedEntryForUID(TentativeEntry TEntry); - - /// Returns entry associated with the filename or nullptr if none is found. - /// - /// Returns entry from local cache if there is some. Otherwise, if the entry - /// is found in the shared cache, writes it through the local cache and - /// returns it. Otherwise returns nullptr. - const CachedFileSystemEntry * - findEntryByFilenameWithWriteThrough(StringRef Filename); - - /// Returns entry associated with the unique ID in the shared cache or nullptr - /// if none is found. - const CachedFileSystemEntry * - findSharedEntryByUID(llvm::vfs::Status Stat) const; - - /// Associates the given entry with the filename in the local cache and - /// returns it. - const CachedFileSystemEntry & - insertLocalEntryForFilename(StringRef Filename, - const CachedFileSystemEntry &Entry) { - return LocalCache.insertEntryForFilename(Filename, Entry); - } - - /// Returns entry associated with the filename in the shared cache if there is - /// some. Otherwise, constructs new one with the given error code, associates - /// it with the filename and returns the result. - const CachedFileSystemEntry & - getOrEmplaceSharedEntryForFilename(StringRef Filename, std::error_code EC); - - /// Returns entry associated with the filename in the shared cache if there is - /// some. Otherwise, associates the given entry with the filename and returns - /// it. - const CachedFileSystemEntry & - getOrInsertSharedEntryForFilename(StringRef Filename, - const CachedFileSystemEntry &Entry); - void printImpl(raw_ostream &OS, PrintType Type, unsigned IndentLevel) const override { printIndent(OS, IndentLevel); @@ -484,9 +403,19 @@ class DependencyScanningWorkerFilesystem /// The service associated with this VFS. DependencyScanningService &Service; + + /// Per-filename state cached locally by this worker thread, so repeated + /// queries can be served without touching the shared cache. The entry and + /// real path are arena-owned by the shared cache and outlive this worker, so + /// borrowing raw pointers here is safe. + struct LocalEntry { + const CachedFileSystemEntry *File = nullptr; + const CachedRealPath *RealPath = nullptr; + }; + /// The local cache is used by the worker thread to cache file system queries /// locally instead of querying the global cache every time. - DependencyScanningFilesystemLocalCache LocalCache; + llvm::StringMap<LocalEntry, llvm::BumpPtrAllocator> LocalCache; /// The working directory to use for making relative paths absolute before /// using them for cache lookups. diff --git a/clang/lib/DependencyScanning/DependencyScanningFilesystem.cpp b/clang/lib/DependencyScanning/DependencyScanningFilesystem.cpp index 6ff6cd9040665..3b00411b7d2db 100644 --- a/clang/lib/DependencyScanning/DependencyScanningFilesystem.cpp +++ b/clang/lib/DependencyScanning/DependencyScanningFilesystem.cpp @@ -115,8 +115,13 @@ DependencyScanningFilesystemSharedCache::getOutOfDateEntries( for (unsigned i = 0; i < NumShards; i++) { const CacheShard &Shard = CacheShards[i]; std::lock_guard<std::mutex> LockGuard(Shard.CacheLock); - for (const auto &[Path, CachedPair] : Shard.CacheByFilename) { - const CachedFileSystemEntry *Entry = CachedPair.first; + for (const auto &[Path, State] : Shard.CacheByFilename) { + const CachedFileSystemEntry *Entry = State.Entry; + // Skip slots without a resolved entry: real-path-only entries from + // getRealPath, or uncached negative stats. Runs post-scan, so no + // in-progress slots remain. + if (!Entry) + continue; llvm::ErrorOr<llvm::vfs::Status> Status = UnderlyingFS.status(Path); if (Status) { if (Entry->getError()) { @@ -151,69 +156,43 @@ DependencyScanningFilesystemSharedCache::getOutOfDateEntries( return InvalidDiagInfo; } -const CachedFileSystemEntry * -DependencyScanningFilesystemSharedCache::CacheShard::findEntryByFilename( - StringRef Filename) const { - assert(llvm::sys::path::is_absolute_gnu(Filename)); - std::lock_guard<std::mutex> LockGuard(CacheLock); - auto It = CacheByFilename.find(Filename); - return It == CacheByFilename.end() ? nullptr : It->getValue().first; -} - -const CachedFileSystemEntry * -DependencyScanningFilesystemSharedCache::CacheShard::findEntryByUID( - llvm::sys::fs::UniqueID UID) const { - std::lock_guard<std::mutex> LockGuard(CacheLock); - auto It = EntriesByUID.find(UID); - return It == EntriesByUID.end() ? nullptr : It->getSecond(); -} - -const CachedFileSystemEntry & -DependencyScanningFilesystemSharedCache::CacheShard:: - getOrEmplaceEntryForFilename(StringRef Filename, - llvm::ErrorOr<llvm::vfs::Status> Stat) { - std::lock_guard<std::mutex> LockGuard(CacheLock); - auto [It, Inserted] = CacheByFilename.insert({Filename, {nullptr, nullptr}}); - auto &[CachedEntry, CachedRealPath] = It->getValue(); - if (!CachedEntry) { - // The entry is not present in the shared cache. Either the cache doesn't - // know about the file at all, or it only knows about its real path. - assert((Inserted || CachedRealPath) && "existing file with empty pair"); - CachedEntry = - new (EntryStorage.Allocate()) CachedFileSystemEntry(std::move(Stat)); - } - return *CachedEntry; -} +namespace { +using InProgressEntry = + DependencyScanningFilesystemSharedCache::InProgressEntry; +using SlotResolved = llvm::ErrorOr<const CachedFileSystemEntry *>; +using SlotProducer = std::shared_ptr<InProgressEntry>; +using SlotAcquisitionResult = std::variant<SlotResolved, SlotProducer>; + +/// Returns a resolved entry if one is already present or in-flight under +/// \p K; otherwise installs a fresh \c InProgressEntry and returns it as a +/// producer slot. +template <typename Map, typename Key> +SlotAcquisitionResult acquireSlot(std::mutex &CacheLock, Map &M, const Key &K) { + std::shared_ptr<InProgressEntry> Pending; + { + std::lock_guard<std::mutex> ShardLock(CacheLock); + auto &State = M[K]; + + // Cache hit. + if (State.Entry) + return SlotResolved{State.Entry}; + + if (!State.InProgress) { + State.InProgress = std::make_shared<InProgressEntry>(); + return SlotProducer{State.InProgress}; + } -const CachedFileSystemEntry & -DependencyScanningFilesystemSharedCache::CacheShard::getOrEmplaceEntryForUID( - llvm::sys::fs::UniqueID UID, llvm::vfs::Status Stat, - std::unique_ptr<llvm::MemoryBuffer> Contents) { - std::lock_guard<std::mutex> LockGuard(CacheLock); - auto [It, Inserted] = EntriesByUID.try_emplace(UID); - auto &CachedEntry = It->getSecond(); - if (Inserted) { - CachedFileContents *StoredContents = nullptr; - if (Contents) - StoredContents = new (ContentsStorage.Allocate()) - CachedFileContents(std::move(Contents)); - CachedEntry = new (EntryStorage.Allocate()) - CachedFileSystemEntry(std::move(Stat), StoredContents); + // Copy the shared_ptr so the slot survives our wait once the shard lock + // is released and the producer resets State.InProgress on publish. + Pending = State.InProgress; } - return *CachedEntry; -} -const CachedFileSystemEntry & -DependencyScanningFilesystemSharedCache::CacheShard:: - getOrInsertEntryForFilename(StringRef Filename, - const CachedFileSystemEntry &Entry) { - std::lock_guard<std::mutex> LockGuard(CacheLock); - auto [It, Inserted] = CacheByFilename.insert({Filename, {&Entry, nullptr}}); - auto &[CachedEntry, CachedRealPath] = It->getValue(); - if (!Inserted || !CachedEntry) - CachedEntry = &Entry; - return *CachedEntry; + // Wait off the shard lock so unrelated keys in this shard aren't blocked. + std::unique_lock<std::mutex> EntryLock(Pending->Mutex); + Pending->CondVar.wait(EntryLock, [&] { return Pending->Done; }); + return SlotResolved{Pending->Result}; } +} // namespace const CachedRealPath * DependencyScanningFilesystemSharedCache::CacheShard::findRealPathByFilename( @@ -221,7 +200,7 @@ DependencyScanningFilesystemSharedCache::CacheShard::findRealPathByFilename( assert(llvm::sys::path::is_absolute_gnu(Filename)); std::lock_guard<std::mutex> LockGuard(CacheLock); auto It = CacheByFilename.find(Filename); - return It == CacheByFilename.end() ? nullptr : It->getValue().second; + return It == CacheByFilename.end() ? nullptr : It->getValue().RealPath; } const CachedRealPath &DependencyScanningFilesystemSharedCache::CacheShard:: @@ -229,7 +208,7 @@ const CachedRealPath &DependencyScanningFilesystemSharedCache::CacheShard:: llvm::ErrorOr<llvm::StringRef> RealPath) { std::lock_guard<std::mutex> LockGuard(CacheLock); - const CachedRealPath *&StoredRealPath = CacheByFilename[Filename].second; + const CachedRealPath *&StoredRealPath = CacheByFilename[Filename].RealPath; if (!StoredRealPath) { auto OwnedRealPath = [&]() -> CachedRealPath { if (!RealPath) @@ -253,82 +232,98 @@ DependencyScanningWorkerFilesystem::DependencyScanningWorkerFilesystem( updateWorkingDirForCacheLookup(); } -const CachedFileSystemEntry & -DependencyScanningWorkerFilesystem::getOrEmplaceSharedEntryForUID( - TentativeEntry TEntry) { - auto &Shard = - Service.getSharedCache().getShardForUID(TEntry.Status.getUniqueID()); - return Shard.getOrEmplaceEntryForUID(TEntry.Status.getUniqueID(), - std::move(TEntry.Status), - std::move(TEntry.Contents)); -} - -const CachedFileSystemEntry * -DependencyScanningWorkerFilesystem::findEntryByFilenameWithWriteThrough( - StringRef Filename) { - if (const auto *Entry = LocalCache.findEntryByFilename(Filename)) - return Entry; - auto &Shard = Service.getSharedCache().getShardForFilename(Filename); - if (const auto *Entry = Shard.findEntryByFilename(Filename)) - return &LocalCache.insertEntryForFilename(Filename, *Entry); - return nullptr; -} - const CachedFileSystemEntry * -DependencyScanningWorkerFilesystem::findSharedEntryByUID( - llvm::vfs::Status Stat) const { - return Service.getSharedCache() - .getShardForUID(Stat.getUniqueID()) - .findEntryByUID(Stat.getUniqueID()); -} - -const CachedFileSystemEntry & -DependencyScanningWorkerFilesystem::getOrEmplaceSharedEntryForFilename( - StringRef Filename, std::error_code EC) { - return Service.getSharedCache() - .getShardForFilename(Filename) - .getOrEmplaceEntryForFilename(Filename, EC); -} - -const CachedFileSystemEntry & -DependencyScanningWorkerFilesystem::getOrInsertSharedEntryForFilename( - StringRef Filename, const CachedFileSystemEntry &Entry) { - return Service.getSharedCache() - .getShardForFilename(Filename) - .getOrInsertEntryForFilename(Filename, Entry); -} - -llvm::ErrorOr<const CachedFileSystemEntry &> -DependencyScanningWorkerFilesystem::computeAndStoreResult( - StringRef OriginalFilename, StringRef FilenameForLookup) { - llvm::ErrorOr<llvm::vfs::Status> Stat = - getUnderlyingFS().status(OriginalFilename); - if (!Stat) { - if (!Service.getOpts().CacheNegativeStats || - !shouldCacheNegativeStatsForPath(OriginalFilename)) - return Stat.getError(); - - const auto &Entry = - getOrEmplaceSharedEntryForFilename(FilenameForLookup, Stat.getError()); - return insertLocalEntryForFilename(FilenameForLookup, Entry); +DependencyScanningWorkerFilesystem::resolveUIDThroughSharedCache( + StringRef OriginalFilename, const llvm::vfs::Status &Stat) { + auto &UIDShard = Service.getSharedCache().getShardForUID(Stat.getUniqueID()); + auto UIDSlot = acquireSlot(UIDShard.CacheLock, UIDShard.EntriesByUID, + Stat.getUniqueID()); + if (auto *Resolved = std::get_if<SlotResolved>(&UIDSlot)) { + assert(*Resolved && **Resolved && + "in-progress UID slot fulfilled without an entry"); + return **Resolved; } - - if (const auto *Entry = findSharedEntryByUID(*Stat)) - return insertLocalEntryForFilename(FilenameForLookup, *Entry); + auto UIDProducer = std::move(std::get<SlotProducer>(UIDSlot)); auto TEntry = - Stat->isDirectory() ? TentativeEntry(*Stat) : readFile(OriginalFilename); - - const CachedFileSystemEntry *SharedEntry = [&]() { + Stat.isDirectory() ? TentativeEntry(Stat) : readFile(OriginalFilename); + + // Allocate the entry and bind the UID slot under one shard-lock acquisition + // (BumpPtrAllocator isn't thread-safe). On read-failure, the entry wraps + // the open error so concurrent UID waiters surface it rather than racing + // to retry the open. + const CachedFileSystemEntry *SharedEntry; + { + std::lock_guard<std::mutex> ShardLock(UIDShard.CacheLock); + auto &State = UIDShard.EntriesByUID[Stat.getUniqueID()]; + assert(!State.Entry && "UID slot already published an entry"); if (TEntry) { - const auto &UIDEntry = getOrEmplaceSharedEntryForUID(std::move(*TEntry)); - return &getOrInsertSharedEntryForFilename(FilenameForLookup, UIDEntry); + CachedFileContents *StoredContents = nullptr; + if (TEntry->Contents) + StoredContents = new (UIDShard.ContentsStorage.Allocate()) + CachedFileContents(std::move(TEntry->Contents)); + SharedEntry = new (UIDShard.EntryStorage.Allocate()) + CachedFileSystemEntry(std::move(TEntry->Status), StoredContents); + } else { + SharedEntry = new (UIDShard.EntryStorage.Allocate()) + CachedFileSystemEntry(TEntry.getError()); } - return &getOrEmplaceSharedEntryForFilename(FilenameForLookup, - TEntry.getError()); - }(); + State.Entry = SharedEntry; + State.InProgress.reset(); + } + UIDProducer->publish(SharedEntry); + return SharedEntry; +} - return insertLocalEntryForFilename(FilenameForLookup, *SharedEntry); +llvm::ErrorOr<const CachedFileSystemEntry *> +DependencyScanningWorkerFilesystem::resolveFilenameThroughSharedCache( + StringRef OriginalFilename, StringRef FilenameForLookup) { + assert(llvm::sys::path::is_absolute_gnu(FilenameForLookup)); + auto &FilenameShard = + Service.getSharedCache().getShardForFilename(FilenameForLookup); + auto FilenameSlot = + acquireSlot(FilenameShard.CacheLock, FilenameShard.CacheByFilename, + FilenameForLookup); + if (auto *Resolved = std::get_if<SlotResolved>(&FilenameSlot)) + return *Resolved; + auto FilenameProducer = std::move(std::get<SlotProducer>(FilenameSlot)); + + // Compute the outcome. Three cases: + // - Stat succeeded: delegate to the UID resolver. + // - Stat failed, cacheable: defer error-entry allocation to the critical + // section below so allocate+bind+reset share one shard-lock acquisition. + // - Stat failed, not cacheable: publish the error to current waiters but + // don't persist; a later separate query re-runs the stat (so a file + // created mid-scan becomes visible). + auto Stat = getUnderlyingFS().status(OriginalFilename); + const bool ShouldCacheNegativeStat = + !Stat && Service.getOpts().CacheNegativeStats && + shouldCacheNegativeStatsForPath(OriginalFilename); + llvm::ErrorOr<const CachedFileSystemEntry *> Result = std::error_code{}; + if (Stat) + Result = resolveUIDThroughSharedCache(OriginalFilename, *Stat); + else if (!ShouldCacheNegativeStat) + Result = Stat.getError(); + + // Bind the result and reset the in-flight slot under a single critical + // section. The cached-negative case allocates here so allocate+bind+reset + // share one shard-lock acquisition. + { + std::lock_guard<std::mutex> ShardLock(FilenameShard.CacheLock); + auto &State = FilenameShard.CacheByFilename[FilenameForLookup]; + assert(!State.Entry && "filename slot already published an entry"); + if (ShouldCacheNegativeStat) { + auto *Entry = new (FilenameShard.EntryStorage.Allocate()) + CachedFileSystemEntry(Stat.getError()); + State.Entry = Entry; + Result = Entry; + } else if (Result) { + State.Entry = *Result; + } + State.InProgress.reset(); + } + FilenameProducer->publish(Result); + return Result; } llvm::ErrorOr<EntryRef> @@ -339,13 +334,16 @@ DependencyScanningWorkerFilesystem::getOrCreateFileSystemEntry( if (!FilenameForLookup) return FilenameForLookup.getError(); - if (const auto *Entry = - findEntryByFilenameWithWriteThrough(*FilenameForLookup)) - return EntryRef(OriginalFilename, *Entry).unwrapError(); - auto MaybeEntry = computeAndStoreResult(OriginalFilename, *FilenameForLookup); + auto &Local = LocalCache[*FilenameForLookup]; + if (Local.File) + return EntryRef(OriginalFilename, *Local.File).unwrapError(); + + auto MaybeEntry = + resolveFilenameThroughSharedCache(OriginalFilename, *FilenameForLookup); if (!MaybeEntry) return MaybeEntry.getError(); - return EntryRef(OriginalFilename, *MaybeEntry).unwrapError(); + Local.File = *MaybeEntry; + return EntryRef(OriginalFilename, **MaybeEntry).unwrapError(); } llvm::ErrorOr<llvm::vfs::Status> @@ -448,18 +446,17 @@ DependencyScanningWorkerFilesystem::getRealPath(const Twine &Path, }; // If we already have the result in local cache, no work required. - if (const auto *RealPath = - LocalCache.findRealPathByFilename(*FilenameForLookup)) - return HandleCachedRealPath(*RealPath); + auto &Local = LocalCache[*FilenameForLookup]; + if (Local.RealPath) + return HandleCachedRealPath(*Local.RealPath); // If we have the result in the shared cache, cache it locally. auto &Shard = Service.getSharedCache().getShardForFilename(*FilenameForLookup); if (const auto *ShardRealPath = Shard.findRealPathByFilename(*FilenameForLookup)) { - const auto &RealPath = LocalCache.insertRealPathForFilename( - *FilenameForLookup, *ShardRealPath); - return HandleCachedRealPath(RealPath); + Local.RealPath = ShardRealPath; + return HandleCachedRealPath(*Local.RealPath); } // If we don't know the real path, compute it... @@ -473,8 +470,8 @@ DependencyScanningWorkerFilesystem::getRealPath(const Twine &Path, // whatever is in the shared cache into the local one. const auto &RealPath = Shard.getOrEmplaceRealPathForFilename( *FilenameForLookup, ComputedRealPath); - return HandleCachedRealPath( - LocalCache.insertRealPathForFilename(*FilenameForLookup, RealPath)); + Local.RealPath = &RealPath; + return HandleCachedRealPath(*Local.RealPath); } std::error_code DependencyScanningWorkerFilesystem::setCurrentWorkingDirectory( diff --git a/clang/unittests/DependencyScanning/CMakeLists.txt b/clang/unittests/DependencyScanning/CMakeLists.txt index 4f2a36067209c..53d257a76784d 100644 --- a/clang/unittests/DependencyScanning/CMakeLists.txt +++ b/clang/unittests/DependencyScanning/CMakeLists.txt @@ -4,6 +4,8 @@ add_clang_unittest(ClangDependencyScanningTests CLANG_LIBS clangDependencyScanning clangFrontend # For TextDiagnosticPrinter. + LINK_LIBS + LLVMTestingSupport LLVM_COMPONENTS ${LLVM_TARGETS_TO_BUILD} Option diff --git a/clang/unittests/DependencyScanning/DependencyScanningFilesystemTest.cpp b/clang/unittests/DependencyScanning/DependencyScanningFilesystemTest.cpp index 85525ba5324ca..70e359e379e60 100644 --- a/clang/unittests/DependencyScanning/DependencyScanningFilesystemTest.cpp +++ b/clang/unittests/DependencyScanning/DependencyScanningFilesystemTest.cpp @@ -10,10 +10,70 @@ #include "clang/DependencyScanning/DependencyScanningService.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/VirtualFileSystem.h" +#include "llvm/Testing/Support/SupportHelpers.h" #include "gtest/gtest.h" +#include <condition_variable> +#include <mutex> +#include <thread> using namespace clang::dependencies; +namespace { + +/// Releases all waiting threads simultaneously so that the worker logic can +/// be observed under maximal concurrency rather than a thread-spawn cascade. +struct StartBarrier { + std::mutex M; + std::condition_variable CV; + bool Released = false; + + void wait() { + std::unique_lock<std::mutex> Lock(M); + CV.wait(Lock, [&] { return Released; }); + } + + void release() { + { + std::lock_guard<std::mutex> Lock(M); + Released = true; + } + CV.notify_all(); + } +}; + +/// Build \p NumWorkers DependencyScanningWorkerFilesystems sharing \p Service +/// and \p FS, fan out one thread per worker waiting on a common barrier, then +/// release the barrier and join. Returns each worker's result (initialized to +/// \p Default for slots a thread did not write to). +template <typename R, typename Fn> +std::vector<R> +runConcurrentWorkers(DependencyScanningService &Service, + llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS, + unsigned NumWorkers, R Default, Fn &&PerWorker) { + std::vector<std::unique_ptr<DependencyScanningWorkerFilesystem>> Workers; + Workers.reserve(NumWorkers); + for (unsigned I = 0; I < NumWorkers; ++I) + Workers.push_back( + std::make_unique<DependencyScanningWorkerFilesystem>(Service, FS)); + + StartBarrier Barrier; + std::vector<R> Results(NumWorkers, std::move(Default)); + std::vector<std::thread> Threads; + Threads.reserve(NumWorkers); + for (unsigned I = 0; I < NumWorkers; ++I) { + Threads.emplace_back([&, I] { + Barrier.wait(); + Results[I] = PerWorker(*Workers[I], I); + }); + } + Barrier.release(); + for (auto &T : Threads) + T.join(); + return Results; +} + +} // namespace + TEST(DependencyScanningFilesystem, OpenFileAndGetBufferRepeatedly) { auto InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); InMemoryFS->setCurrentWorkingDirectory("/"); @@ -334,3 +394,154 @@ TEST(DependencyScanningFilesystem, DoNotDiagnoseDirSizeChange) { auto InvalidEntries = Service.getSharedCache().getOutOfDateEntries(*FS); EXPECT_EQ(InvalidEntries.size(), 0u); } + +TEST(DependencyScanningWorkerFilesystem, ConcurrentSameFilenameDeduplicates) { + llvm::unittest::TempDir TmpDir("dswf-same-filename", /*Unique=*/true); + llvm::unittest::TempFile TmpFile(TmpDir.path("foo.c"), /*Suffix=*/"", + /*Contents=*/"hello"); + + auto TracingFS = + llvm::makeIntrusiveRefCnt<llvm::vfs::AtomicTracingFileSystem>( + llvm::vfs::getRealFileSystem()); + DependencyScanningService Service({}); + + constexpr unsigned NumWorkers = 16; + auto Results = runConcurrentWorkers<llvm::ErrorOr<EntryRef>>( + Service, TracingFS, NumWorkers, std::error_code{}, + [&](DependencyScanningWorkerFilesystem &W, unsigned) { + return W.getOrCreateFileSystemEntry(TmpFile.path()); + }); + + EXPECT_EQ(TracingFS->NumStatusCalls.load(), 1u); + EXPECT_EQ(TracingFS->NumOpenFileForReadCalls.load(), 1u); + + // All workers must have observed the same underlying entry. + ASSERT_TRUE(Results[0]); + llvm::sys::fs::UniqueID FirstUID = Results[0]->getStatus().getUniqueID(); + const char *FirstContents = Results[0]->getContents().data(); + for (unsigned I = 0; I < NumWorkers; ++I) { + ASSERT_TRUE(Results[I]) << "worker " << I << " failed"; + EXPECT_EQ(Results[I]->getStatus().getUniqueID(), FirstUID); + EXPECT_EQ(Results[I]->getContents().data(), FirstContents); + } +} + +TEST(DependencyScanningWorkerFilesystem, + ConcurrentSameUIDDifferentFilenamesDeduplicatesOpen) { + // Use a real on-disk file plus a hard link so the two filenames share a + // UniqueID, exercising the per-UID slot. + llvm::unittest::TempDir TmpDir("dswf-same-uid", /*Unique=*/true); + llvm::SmallString<128> RealPath = TmpDir.path("real.c"); + llvm::SmallString<128> AliasPath = TmpDir.path("alias.c"); + llvm::unittest::TempFile TmpFile(RealPath, /*Suffix=*/"", /*Contents=*/"hi"); + llvm::unittest::TempLink TmpLink(RealPath, AliasPath); + + auto TracingFS = + llvm::makeIntrusiveRefCnt<llvm::vfs::AtomicTracingFileSystem>( + llvm::vfs::getRealFileSystem()); + DependencyScanningService Service({}); + + constexpr unsigned NumWorkers = 16; + auto Results = runConcurrentWorkers<llvm::ErrorOr<EntryRef>>( + Service, TracingFS, NumWorkers, std::error_code{}, + [&](DependencyScanningWorkerFilesystem &W, unsigned I) { + llvm::StringRef Path = (I % 2 == 0) ? llvm::StringRef(RealPath) + : llvm::StringRef(AliasPath); + return W.getOrCreateFileSystemEntry(Path); + }); + + // Each filename's slot dedupes its own stats; with two filenames we expect + // at most two stats. The UID slot then collapses the actual reads. + EXPECT_LE(TracingFS->NumStatusCalls.load(), 2u); + EXPECT_EQ(TracingFS->NumOpenFileForReadCalls.load(), 1u); + + ASSERT_TRUE(Results[0]); + llvm::sys::fs::UniqueID FirstUID = Results[0]->getStatus().getUniqueID(); + for (unsigned I = 0; I < NumWorkers; ++I) { + ASSERT_TRUE(Results[I]) << "worker " << I << " failed"; + EXPECT_EQ(Results[I]->getStatus().getUniqueID(), FirstUID); + } +} + +TEST(DependencyScanningWorkerFilesystem, ConcurrentNegativeStatDeduplicates) { + // Construct a path inside a temporary directory but never create the + // file, so concurrent stat() calls land on the negative-stat path through + // the real filesystem. + llvm::unittest::TempDir TmpDir("dswf-negative", /*Unique=*/true); + llvm::SmallString<128> MissingPath = TmpDir.path("missing.h"); + + auto TracingFS = + llvm::makeIntrusiveRefCnt<llvm::vfs::AtomicTracingFileSystem>( + llvm::vfs::getRealFileSystem()); + // The dedup under test only applies when negative stats are cached; enable + // caching and use a path whose extension is eligible for it. + DependencyScanningServiceOptions Opts; + Opts.CacheNegativeStats = true; + DependencyScanningService Service(std::move(Opts)); + + constexpr unsigned NumWorkers = 16; + auto Results = runConcurrentWorkers<llvm::ErrorOr<llvm::vfs::Status>>( + Service, TracingFS, NumWorkers, std::error_code{}, + [&](DependencyScanningWorkerFilesystem &W, unsigned) { + return W.status(MissingPath); + }); + + EXPECT_EQ(TracingFS->NumStatusCalls.load(), 1u); + EXPECT_EQ(TracingFS->NumOpenFileForReadCalls.load(), 0u); + + // Every worker observed the same negative result. + ASSERT_FALSE(Results[0]); + std::error_code FirstError = Results[0].getError(); + for (unsigned I = 0; I < NumWorkers; ++I) { + ASSERT_FALSE(Results[I]) << "worker " << I << " unexpectedly succeeded"; + EXPECT_EQ(Results[I].getError(), FirstError); + } +} + +TEST(DependencyScanningWorkerFilesystem, + ConcurrentUncachedNegativeStatIsSharedButNotPersisted) { + // Same as above, but with negative-stat caching disabled. Concurrent queries + // that overlap the producer's in-flight stat still adopt its negative + // result (sharing an answer for overlapping queries is not "caching"), so no + // worker opens the missing file. Because the result is not persisted, a + // later, separate query must re-run the stat. + llvm::unittest::TempDir TmpDir("dswf-uncached-negative", /*Unique=*/true); + llvm::SmallString<128> MissingPath = TmpDir.path("missing.h"); + + auto TracingFS = + llvm::makeIntrusiveRefCnt<llvm::vfs::AtomicTracingFileSystem>( + llvm::vfs::getRealFileSystem()); + DependencyScanningServiceOptions Opts; + Opts.CacheNegativeStats = false; + DependencyScanningService Service(std::move(Opts)); + + constexpr unsigned NumWorkers = 16; + auto Results = runConcurrentWorkers<llvm::ErrorOr<llvm::vfs::Status>>( + Service, TracingFS, NumWorkers, std::error_code{}, + [&](DependencyScanningWorkerFilesystem &W, unsigned) { + return W.status(MissingPath); + }); + + // A negative stat never opens the file, regardless of how many workers + // produced versus adopted. Producers are bounded by the worker count; + // adopters add none. With caching off the result is not persisted, so the + // count is not pinned to one, but it can never exceed one stat per worker. + EXPECT_EQ(TracingFS->NumOpenFileForReadCalls.load(), 0u); + unsigned StatsAfterBurst = TracingFS->NumStatusCalls.load(); + EXPECT_GE(StatsAfterBurst, 1u); + EXPECT_LE(StatsAfterBurst, NumWorkers); + + // Every worker observed the same negative result. + ASSERT_FALSE(Results[0]); + std::error_code FirstError = Results[0].getError(); + for (unsigned I = 0; I < NumWorkers; ++I) { + ASSERT_FALSE(Results[I]) << "worker " << I << " unexpectedly succeeded"; + EXPECT_EQ(Results[I].getError(), FirstError); + } + + // The uncached negative was shared, not cached: a later, separate query + // re-runs the stat rather than adopting a persisted miss. + DependencyScanningWorkerFilesystem PostWorker(Service, TracingFS); + EXPECT_FALSE(PostWorker.status(MissingPath)); + EXPECT_EQ(TracingFS->NumStatusCalls.load(), StatsAfterBurst + 1); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
