This broke buildbots. Sorry about that. r333742 should fix them. On Fri, Jun 1, 2018 at 12:12 PM Ilya Biryukov via cfe-commits < cfe-commits@lists.llvm.org> wrote:
> Author: ibiryukov > Date: Fri Jun 1 03:08:43 2018 > New Revision: 333737 > > URL: http://llvm.org/viewvc/llvm-project?rev=333737&view=rev > Log: > [clangd] Keep only a limited number of idle ASTs in memory > > Summary: > After this commit, clangd will only keep the last 3 accessed ASTs in > memory. Preambles for each of the opened files are still kept in > memory to make completion and AST rebuilds fast. > > AST rebuilds are usually fast enough, but having the last ASTs in > memory still considerably improves latency of operations like > findDefinition and documeneHighlight, which are often sent multiple > times a second when moving around the code. So keeping some of the last > accessed ASTs in memory seems like a reasonable tradeoff. > > Reviewers: sammccall > > Reviewed By: sammccall > > Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, > jkorous, cfe-commits > > Differential Revision: https://reviews.llvm.org/D47063 > > Modified: > clang-tools-extra/trunk/clangd/ClangdServer.cpp > clang-tools-extra/trunk/clangd/ClangdServer.h > clang-tools-extra/trunk/clangd/ClangdUnit.cpp > clang-tools-extra/trunk/clangd/ClangdUnit.h > clang-tools-extra/trunk/clangd/TUScheduler.cpp > clang-tools-extra/trunk/clangd/TUScheduler.h > clang-tools-extra/trunk/test/clangd/trace.test > clang-tools-extra/trunk/unittests/clangd/FileIndexTests.cpp > clang-tools-extra/trunk/unittests/clangd/TUSchedulerTests.cpp > > Modified: clang-tools-extra/trunk/clangd/ClangdServer.cpp > URL: > http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.cpp?rev=333737&r1=333736&r2=333737&view=diff > > ============================================================================== > --- clang-tools-extra/trunk/clangd/ClangdServer.cpp (original) > +++ clang-tools-extra/trunk/clangd/ClangdServer.cpp Fri Jun 1 03:08:43 > 2018 > @@ -100,7 +100,7 @@ ClangdServer::ClangdServer(GlobalCompila > std::shared_ptr<Preprocessor> > PP) { FileIdx->update(Path, &AST, > std::move(PP)); } > : PreambleParsedCallback(), > - Opts.UpdateDebounce) { > + Opts.UpdateDebounce, Opts.RetentionPolicy) { > if (FileIdx && Opts.StaticIndex) { > MergedIndex = mergeIndex(FileIdx.get(), Opts.StaticIndex); > Index = MergedIndex.get(); > > Modified: clang-tools-extra/trunk/clangd/ClangdServer.h > URL: > http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.h?rev=333737&r1=333736&r2=333737&view=diff > > ============================================================================== > --- clang-tools-extra/trunk/clangd/ClangdServer.h (original) > +++ clang-tools-extra/trunk/clangd/ClangdServer.h Fri Jun 1 03:08:43 2018 > @@ -70,6 +70,9 @@ public: > /// If 0, all requests are processed on the calling thread. > unsigned AsyncThreadsCount = getDefaultAsyncThreadsCount(); > > + /// AST caching policy. The default is to keep up to 3 ASTs in memory. > + ASTRetentionPolicy RetentionPolicy; > + > /// Cached preambles are potentially large. If false, store them on > disk. > bool StorePreamblesInMemory = true; > > > Modified: clang-tools-extra/trunk/clangd/ClangdUnit.cpp > URL: > http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdUnit.cpp?rev=333737&r1=333736&r2=333737&view=diff > > ============================================================================== > --- clang-tools-extra/trunk/clangd/ClangdUnit.cpp (original) > +++ clang-tools-extra/trunk/clangd/ClangdUnit.cpp Fri Jun 1 03:08:43 2018 > @@ -175,8 +175,12 @@ ParsedAST::Build(std::unique_ptr<clang:: > ASTDiags.EndSourceFile(); > > std::vector<const Decl *> ParsedDecls = Action->takeTopLevelDecls(); > + std::vector<Diag> Diags = ASTDiags.take(); > + // Add diagnostics from the preamble, if any. > + if (Preamble) > + Diags.insert(Diags.begin(), Preamble->Diags.begin(), > Preamble->Diags.end()); > return ParsedAST(std::move(Preamble), std::move(Clang), > std::move(Action), > - std::move(ParsedDecls), ASTDiags.take(), > + std::move(ParsedDecls), std::move(Diags), > std::move(Inclusions)); > } > > @@ -243,120 +247,57 @@ ParsedAST::ParsedAST(std::shared_ptr<con > assert(this->Action); > } > > -CppFile::CppFile(PathRef FileName, bool StorePreamblesInMemory, > - std::shared_ptr<PCHContainerOperations> PCHs, > - PreambleParsedCallback PreambleCallback) > - : FileName(FileName), StorePreamblesInMemory(StorePreamblesInMemory), > - PCHs(std::move(PCHs)), > PreambleCallback(std::move(PreambleCallback)) { > - log("Created CppFile for " + FileName); > -} > - > -llvm::Optional<std::vector<Diag>> CppFile::rebuild(ParseInputs &&Inputs) { > - log("Rebuilding file " + FileName + " with command [" + > - Inputs.CompileCommand.Directory + "] " + > - llvm::join(Inputs.CompileCommand.CommandLine, " ")); > - > +std::unique_ptr<CompilerInvocation> > +clangd::buildCompilerInvocation(const ParseInputs &Inputs) { > std::vector<const char *> ArgStrs; > for (const auto &S : Inputs.CompileCommand.CommandLine) > ArgStrs.push_back(S.c_str()); > > if > (Inputs.FS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory)) { > - log("Couldn't set working directory"); > - // We run parsing anyway, our lit-tests rely on results for > non-existing > - // working dirs. > - } > - > - // Prepare CompilerInvocation. > - std::unique_ptr<CompilerInvocation> CI; > - { > - // FIXME(ibiryukov): store diagnostics from CommandLine when we start > - // reporting them. > - IgnoreDiagnostics IgnoreDiagnostics; > - IntrusiveRefCntPtr<DiagnosticsEngine> CommandLineDiagsEngine = > - CompilerInstance::createDiagnostics(new DiagnosticOptions, > - &IgnoreDiagnostics, false); > - CI = createInvocationFromCommandLine(ArgStrs, CommandLineDiagsEngine, > - Inputs.FS); > - if (!CI) { > - log("Could not build CompilerInvocation for file " + FileName); > - AST = llvm::None; > - Preamble = nullptr; > - return llvm::None; > - } > - // createInvocationFromCommandLine sets DisableFree. > - CI->getFrontendOpts().DisableFree = false; > - CI->getLangOpts()->CommentOpts.ParseAllComments = true; > - } > - > - std::unique_ptr<llvm::MemoryBuffer> ContentsBuffer = > - llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, FileName); > - > - // Compute updated Preamble. > - std::shared_ptr<const PreambleData> NewPreamble = > - rebuildPreamble(*CI, Inputs.CompileCommand, Inputs.FS, > *ContentsBuffer); > - > - // Remove current AST to avoid wasting memory. > - AST = llvm::None; > - // Compute updated AST. > - llvm::Optional<ParsedAST> NewAST; > - { > - trace::Span Tracer("Build"); > - SPAN_ATTACH(Tracer, "File", FileName); > - NewAST = ParsedAST::Build(std::move(CI), NewPreamble, > - std::move(ContentsBuffer), PCHs, Inputs.FS); > - } > - > - std::vector<Diag> Diagnostics; > - if (NewAST) { > - // Collect diagnostics from both the preamble and the AST. > - if (NewPreamble) > - Diagnostics = NewPreamble->Diags; > - Diagnostics.insert(Diagnostics.end(), > NewAST->getDiagnostics().begin(), > - NewAST->getDiagnostics().end()); > - } > - > - // Write the results of rebuild into class fields. > - Command = std::move(Inputs.CompileCommand); > - Preamble = std::move(NewPreamble); > - AST = std::move(NewAST); > - return Diagnostics; > -} > - > -const std::shared_ptr<const PreambleData> &CppFile::getPreamble() const { > - return Preamble; > -} > - > -ParsedAST *CppFile::getAST() const { > - // We could add mutable to AST instead of const_cast here, but that > would also > - // allow writing to AST from const methods. > - return AST ? const_cast<ParsedAST *>(AST.getPointer()) : nullptr; > -} > - > -std::size_t CppFile::getUsedBytes() const { > - std::size_t Total = 0; > - if (AST) > - Total += AST->getUsedBytes(); > - if (StorePreamblesInMemory && Preamble) > - Total += Preamble->Preamble.getSize(); > - return Total; > -} > - > -std::shared_ptr<const PreambleData> > -CppFile::rebuildPreamble(CompilerInvocation &CI, > - const tooling::CompileCommand &Command, > - IntrusiveRefCntPtr<vfs::FileSystem> FS, > - llvm::MemoryBuffer &ContentsBuffer) const { > - const auto &OldPreamble = this->Preamble; > - auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), &ContentsBuffer, > 0); > - if (OldPreamble && compileCommandsAreEqual(this->Command, Command) && > - OldPreamble->Preamble.CanReuse(CI, &ContentsBuffer, Bounds, > FS.get())) { > + log("Couldn't set working directory when creating compiler > invocation."); > + // We proceed anyway, our lit-tests rely on results for non-existing > working > + // dirs. > + } > + > + // FIXME(ibiryukov): store diagnostics from CommandLine when we start > + // reporting them. > + IgnoreDiagnostics IgnoreDiagnostics; > + IntrusiveRefCntPtr<DiagnosticsEngine> CommandLineDiagsEngine = > + CompilerInstance::createDiagnostics(new DiagnosticOptions, > + &IgnoreDiagnostics, false); > + std::unique_ptr<CompilerInvocation> CI = > createInvocationFromCommandLine( > + ArgStrs, CommandLineDiagsEngine, Inputs.FS); > + if (!CI) > + return nullptr; > + // createInvocationFromCommandLine sets DisableFree. > + CI->getFrontendOpts().DisableFree = false; > + CI->getLangOpts()->CommentOpts.ParseAllComments = true; > + return CI; > +} > + > +std::shared_ptr<const PreambleData> clangd::buildPreamble( > + PathRef FileName, CompilerInvocation &CI, > + std::shared_ptr<const PreambleData> OldPreamble, > + const tooling::CompileCommand &OldCompileCommand, const ParseInputs > &Inputs, > + std::shared_ptr<PCHContainerOperations> PCHs, bool StoreInMemory, > + PreambleParsedCallback PreambleCallback) { > + // Note that we don't need to copy the input contents, preamble can live > + // without those. > + auto ContentsBuffer = llvm::MemoryBuffer::getMemBuffer(Inputs.Contents); > + auto Bounds = > + ComputePreambleBounds(*CI.getLangOpts(), ContentsBuffer.get(), 0); > + > + if (OldPreamble && > + compileCommandsAreEqual(Inputs.CompileCommand, OldCompileCommand) && > + OldPreamble->Preamble.CanReuse(CI, ContentsBuffer.get(), Bounds, > + Inputs.FS.get())) { > log("Reusing preamble for file " + Twine(FileName)); > return OldPreamble; > } > log("Preamble for file " + Twine(FileName) + > " cannot be reused. Attempting to rebuild it."); > > - trace::Span Tracer("Preamble"); > + trace::Span Tracer("BuildPreamble"); > SPAN_ATTACH(Tracer, "File", FileName); > StoreDiags PreambleDiagnostics; > IntrusiveRefCntPtr<DiagnosticsEngine> PreambleDiagsEngine = > @@ -369,9 +310,14 @@ CppFile::rebuildPreamble(CompilerInvocat > CI.getFrontendOpts().SkipFunctionBodies = true; > > CppFilePreambleCallbacks SerializedDeclsCollector(FileName, > PreambleCallback); > + if > (Inputs.FS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory)) { > + log("Couldn't set working directory when building the preamble."); > + // We proceed anyway, our lit-tests rely on results for non-existing > working > + // dirs. > + } > auto BuiltPreamble = PrecompiledPreamble::Build( > - CI, &ContentsBuffer, Bounds, *PreambleDiagsEngine, FS, PCHs, > - /*StoreInMemory=*/StorePreamblesInMemory, SerializedDeclsCollector); > + CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine, Inputs.FS, > PCHs, > + StoreInMemory, SerializedDeclsCollector); > > // When building the AST for the main file, we do want the function > // bodies. > @@ -380,7 +326,6 @@ CppFile::rebuildPreamble(CompilerInvocat > if (BuiltPreamble) { > log("Built preamble of size " + Twine(BuiltPreamble->getSize()) + > " for file " + Twine(FileName)); > - > return std::make_shared<PreambleData>( > std::move(*BuiltPreamble), PreambleDiagnostics.take(), > SerializedDeclsCollector.takeInclusions()); > @@ -390,6 +335,24 @@ CppFile::rebuildPreamble(CompilerInvocat > } > } > > +llvm::Optional<ParsedAST> clangd::buildAST( > + PathRef FileName, std::unique_ptr<CompilerInvocation> Invocation, > + const ParseInputs &Inputs, std::shared_ptr<const PreambleData> > Preamble, > + std::shared_ptr<PCHContainerOperations> PCHs) { > + trace::Span Tracer("BuildAST"); > + SPAN_ATTACH(Tracer, "File", FileName); > + > + if > (Inputs.FS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory)) { > + log("Couldn't set working directory when building the preamble."); > + // We proceed anyway, our lit-tests rely on results for non-existing > working > + // dirs. > + } > + > + return ParsedAST::Build( > + llvm::make_unique<CompilerInvocation>(*Invocation), Preamble, > + llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents), PCHs, > Inputs.FS); > +} > + > SourceLocation clangd::getBeginningOfIdentifier(ParsedAST &Unit, > const Position &Pos, > const FileID FID) { > > Modified: clang-tools-extra/trunk/clangd/ClangdUnit.h > URL: > http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdUnit.h?rev=333737&r1=333736&r2=333737&view=diff > > ============================================================================== > --- clang-tools-extra/trunk/clangd/ClangdUnit.h (original) > +++ clang-tools-extra/trunk/clangd/ClangdUnit.h Fri Jun 1 03:08:43 2018 > @@ -47,6 +47,7 @@ struct PreambleData { > PreambleData(PrecompiledPreamble Preamble, std::vector<Diag> Diags, > std::vector<Inclusion> Inclusions); > > + tooling::CompileCommand CompileCommand; > PrecompiledPreamble Preamble; > std::vector<Diag> Diags; > // Processes like code completions and go-to-definitions will need > #include > @@ -128,50 +129,32 @@ private: > using PreambleParsedCallback = std::function<void( > PathRef Path, ASTContext &, std::shared_ptr<clang::Preprocessor>)>; > > -/// Manages resources, required by clangd. Allows to rebuild file with new > -/// contents, and provides AST and Preamble for it. > -class CppFile { > -public: > - CppFile(PathRef FileName, bool StorePreamblesInMemory, > - std::shared_ptr<PCHContainerOperations> PCHs, > - PreambleParsedCallback PreambleCallback); > - > - /// Rebuild the AST and the preamble. > - /// Returns a list of diagnostics or llvm::None, if an error occured. > - llvm::Optional<std::vector<Diag>> rebuild(ParseInputs &&Inputs); > - /// Returns the last built preamble. > - const std::shared_ptr<const PreambleData> &getPreamble() const; > - /// Returns the last built AST. > - ParsedAST *getAST() const; > - /// Returns an estimated size, in bytes, currently occupied by the AST > and the > - /// Preamble. > - std::size_t getUsedBytes() const; > - > -private: > - /// Build a new preamble for \p Inputs. If the current preamble can be > reused, > - /// it is returned instead. > - /// This method is const to ensure we don't incidentally modify any > fields. > - std::shared_ptr<const PreambleData> > - rebuildPreamble(CompilerInvocation &CI, > - const tooling::CompileCommand &Command, > - IntrusiveRefCntPtr<vfs::FileSystem> FS, > - llvm::MemoryBuffer &ContentsBuffer) const; > - > - const Path FileName; > - const bool StorePreamblesInMemory; > - > - /// The last CompileCommand used to build AST and Preamble. > - tooling::CompileCommand Command; > - /// The last parsed AST. > - llvm::Optional<ParsedAST> AST; > - /// The last built Preamble. > - std::shared_ptr<const PreambleData> Preamble; > - /// Utility class required by clang > - std::shared_ptr<PCHContainerOperations> PCHs; > - /// This is called after the file is parsed. This can be nullptr if > there is > - /// no callback. > - PreambleParsedCallback PreambleCallback; > -}; > +/// Builds compiler invocation that could be used to build AST or > preamble. > +std::unique_ptr<CompilerInvocation> > +buildCompilerInvocation(const ParseInputs &Inputs); > + > +/// Rebuild the preamble for the new inputs unless the old one can be > reused. > +/// If \p OldPreamble can be reused, it is returned unchanged. > +/// If \p OldPreamble is null, always builds the preamble. > +/// If \p PreambleCallback is set, it will be run on top of the AST while > +/// building the preamble. Note that if the old preamble was reused, no > AST is > +/// built and, therefore, the callback will not be executed. > +std::shared_ptr<const PreambleData> > +buildPreamble(PathRef FileName, CompilerInvocation &CI, > + std::shared_ptr<const PreambleData> OldPreamble, > + const tooling::CompileCommand &OldCompileCommand, > + const ParseInputs &Inputs, > + std::shared_ptr<PCHContainerOperations> PCHs, bool > StoreInMemory, > + PreambleParsedCallback PreambleCallback); > + > +/// Build an AST from provided user inputs. This function does not check > if > +/// preamble can be reused, as this function expects that \p Preamble is > the > +/// result of calling buildPreamble. > +llvm::Optional<ParsedAST> > +buildAST(PathRef FileName, std::unique_ptr<CompilerInvocation> Invocation, > + const ParseInputs &Inputs, > + std::shared_ptr<const PreambleData> Preamble, > + std::shared_ptr<PCHContainerOperations> PCHs); > > /// Get the beginning SourceLocation at a specified \p Pos. > /// May be invalid if Pos is, or if there's no identifier. > > Modified: clang-tools-extra/trunk/clangd/TUScheduler.cpp > URL: > http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/TUScheduler.cpp?rev=333737&r1=333736&r2=333737&view=diff > > ============================================================================== > --- clang-tools-extra/trunk/clangd/TUScheduler.cpp (original) > +++ clang-tools-extra/trunk/clangd/TUScheduler.cpp Fri Jun 1 03:08:43 2018 > @@ -45,9 +45,12 @@ > #include "TUScheduler.h" > #include "Logger.h" > #include "Trace.h" > +#include "clang/Frontend/CompilerInvocation.h" > #include "clang/Frontend/PCHContainerOperations.h" > +#include "llvm/ADT/ScopeExit.h" > #include "llvm/Support/Errc.h" > #include "llvm/Support/Path.h" > +#include <algorithm> > #include <memory> > #include <queue> > #include <thread> > @@ -55,6 +58,76 @@ > namespace clang { > namespace clangd { > using std::chrono::steady_clock; > + > +namespace { > +class ASTWorker; > +} > + > +/// An LRU cache of idle ASTs. > +/// Because we want to limit the overall number of these we retain, the > cache > +/// owns ASTs (and may evict them) while their workers are idle. > +/// Workers borrow ASTs when active, and return them when done. > +class TUScheduler::ASTCache { > +public: > + using Key = const ASTWorker *; > + > + ASTCache(unsigned MaxRetainedASTs) : MaxRetainedASTs(MaxRetainedASTs) {} > + > + /// Returns result of getUsedBytes() for the AST cached by \p K. > + /// If no AST is cached, 0 is returned. > + bool getUsedBytes(Key K) { > + std::lock_guard<std::mutex> Lock(Mut); > + auto It = findByKey(K); > + if (It == LRU.end() || !It->second) > + return 0; > + return It->second->getUsedBytes(); > + } > + > + /// Store the value in the pool, possibly removing the last used AST. > + /// The value should not be in the pool when this function is called. > + void put(Key K, std::unique_ptr<ParsedAST> V) { > + std::unique_lock<std::mutex> Lock(Mut); > + assert(findByKey(K) == LRU.end()); > + > + LRU.insert(LRU.begin(), {K, std::move(V)}); > + if (LRU.size() <= MaxRetainedASTs) > + return; > + // We're past the limit, remove the last element. > + std::unique_ptr<ParsedAST> ForCleanup = std::move(LRU.back().second); > + LRU.pop_back(); > + // Run the expensive destructor outside the lock. > + Lock.unlock(); > + ForCleanup.reset(); > + } > + > + /// Returns the cached value for \p K, or llvm::None if the value is > not in > + /// the cache anymore. If nullptr was cached for \p K, this function > will > + /// return a null unique_ptr wrapped into an optional. > + llvm::Optional<std::unique_ptr<ParsedAST>> take(Key K) { > + std::unique_lock<std::mutex> Lock(Mut); > + auto Existing = findByKey(K); > + if (Existing == LRU.end()) > + return llvm::None; > + std::unique_ptr<ParsedAST> V = std::move(Existing->second); > + LRU.erase(Existing); > + return V; > + } > + > +private: > + using KVPair = std::pair<Key, std::unique_ptr<ParsedAST>>; > + > + std::vector<KVPair>::iterator findByKey(Key K) { > + return std::find_if(LRU.begin(), LRU.end(), > + [K](const KVPair &P) { return P.first == K; }); > + } > + > + std::mutex Mut; > + unsigned MaxRetainedASTs; > + /// Items sorted in LRU order, i.e. first item is the most recently > accessed > + /// one. > + std::vector<KVPair> LRU; /* GUARDED_BY(Mut) */ > +}; > + > namespace { > class ASTWorkerHandle; > > @@ -70,8 +143,12 @@ class ASTWorkerHandle; > /// worker. > class ASTWorker { > friend class ASTWorkerHandle; > - ASTWorker(llvm::StringRef File, Semaphore &Barrier, CppFile AST, bool > RunSync, > - steady_clock::duration UpdateDebounce); > + ASTWorker(PathRef FileName, TUScheduler::ASTCache &LRUCache, > + Semaphore &Barrier, bool RunSync, > + steady_clock::duration UpdateDebounce, > + std::shared_ptr<PCHContainerOperations> PCHs, > + bool StorePreamblesInMemory, > + PreambleParsedCallback PreambleCallback); > > public: > /// Create a new ASTWorker and return a handle to it. > @@ -79,9 +156,13 @@ public: > /// is null, all requests will be processed on the calling thread > /// synchronously instead. \p Barrier is acquired when processing each > /// request, it is be used to limit the number of actively running > threads. > - static ASTWorkerHandle Create(llvm::StringRef File, AsyncTaskRunner > *Tasks, > - Semaphore &Barrier, CppFile AST, > - steady_clock::duration UpdateDebounce); > + static ASTWorkerHandle Create(PathRef FileName, > + TUScheduler::ASTCache &IdleASTs, > + AsyncTaskRunner *Tasks, Semaphore > &Barrier, > + steady_clock::duration UpdateDebounce, > + std::shared_ptr<PCHContainerOperations> > PCHs, > + bool StorePreamblesInMemory, > + PreambleParsedCallback PreambleCallback); > ~ASTWorker(); > > void update(ParseInputs Inputs, WantDiagnostics, > @@ -92,6 +173,7 @@ public: > > std::shared_ptr<const PreambleData> getPossiblyStalePreamble() const; > std::size_t getUsedBytes() const; > + bool isASTCached() const; > > private: > // Must be called exactly once on processing thread. Will return after > @@ -119,22 +201,30 @@ private: > llvm::Optional<WantDiagnostics> UpdateType; > }; > > - const std::string File; > + /// Handles retention of ASTs. > + TUScheduler::ASTCache &IdleASTs; > const bool RunSync; > - // Time to wait after an update to see whether another update obsoletes > it. > + /// Time to wait after an update to see whether another update > obsoletes it. > const steady_clock::duration UpdateDebounce; > + /// File that ASTWorker is reponsible for. > + const Path FileName; > + /// Whether to keep the built preambles in memory or on disk. > + const bool StorePreambleInMemory; > + /// Callback, passed to the preamble builder. > + const PreambleParsedCallback PreambleCallback; > + /// Helper class required to build the ASTs. > + const std::shared_ptr<PCHContainerOperations> PCHs; > > Semaphore &Barrier; > - // AST and FileInputs are only accessed on the processing thread from > run(). > - CppFile AST; > - // Inputs, corresponding to the current state of AST. > + /// Inputs, corresponding to the current state of AST. > ParseInputs FileInputs; > - // Guards members used by both TUScheduler and the worker thread. > + /// CompilerInvocation used for FileInputs. > + std::unique_ptr<CompilerInvocation> Invocation; > + /// Size of the last AST > + /// Guards members used by both TUScheduler and the worker thread. > mutable std::mutex Mutex; > std::shared_ptr<const PreambleData> LastBuiltPreamble; /* > GUARDED_BY(Mutex) */ > - // Result of getUsedBytes() after the last rebuild or read of AST. > - std::size_t LastASTSize; /* GUARDED_BY(Mutex) */ > - // Set to true to signal run() to finish processing. > + /// Set to true to signal run() to finish processing. > bool Done; /* GUARDED_BY(Mutex) */ > std::deque<Request> Requests; /* GUARDED_BY(Mutex) */ > mutable std::condition_variable RequestsCV; > @@ -182,27 +272,37 @@ private: > std::shared_ptr<ASTWorker> Worker; > }; > > -ASTWorkerHandle ASTWorker::Create(llvm::StringRef File, AsyncTaskRunner > *Tasks, > - Semaphore &Barrier, CppFile AST, > - steady_clock::duration UpdateDebounce) { > +ASTWorkerHandle ASTWorker::Create(PathRef FileName, > + TUScheduler::ASTCache &IdleASTs, > + AsyncTaskRunner *Tasks, Semaphore > &Barrier, > + steady_clock::duration UpdateDebounce, > + std::shared_ptr<PCHContainerOperations> > PCHs, > + bool StorePreamblesInMemory, > + PreambleParsedCallback > PreambleCallback) { > std::shared_ptr<ASTWorker> Worker(new ASTWorker( > - File, Barrier, std::move(AST), /*RunSync=*/!Tasks, UpdateDebounce)); > + FileName, IdleASTs, Barrier, /*RunSync=*/!Tasks, UpdateDebounce, > + std::move(PCHs), StorePreamblesInMemory, > std::move(PreambleCallback))); > if (Tasks) > - Tasks->runAsync("worker:" + llvm::sys::path::filename(File), > + Tasks->runAsync("worker:" + llvm::sys::path::filename(FileName), > [Worker]() { Worker->run(); }); > > return ASTWorkerHandle(std::move(Worker)); > } > > -ASTWorker::ASTWorker(llvm::StringRef File, Semaphore &Barrier, CppFile > AST, > - bool RunSync, steady_clock::duration UpdateDebounce) > - : File(File), RunSync(RunSync), UpdateDebounce(UpdateDebounce), > - Barrier(Barrier), AST(std::move(AST)), Done(false) { > - if (RunSync) > - return; > -} > +ASTWorker::ASTWorker(PathRef FileName, TUScheduler::ASTCache &LRUCache, > + Semaphore &Barrier, bool RunSync, > + steady_clock::duration UpdateDebounce, > + std::shared_ptr<PCHContainerOperations> PCHs, > + bool StorePreamblesInMemory, > + PreambleParsedCallback PreambleCallback) > + : IdleASTs(LRUCache), RunSync(RunSync), > UpdateDebounce(UpdateDebounce), > + FileName(FileName), StorePreambleInMemory(StorePreamblesInMemory), > + PreambleCallback(std::move(PreambleCallback)), > PCHs(std::move(PCHs)), > + Barrier(Barrier), Done(false) {} > > ASTWorker::~ASTWorker() { > + // Make sure we remove the cached AST, if any. > + IdleASTs.take(this); > #ifndef NDEBUG > std::lock_guard<std::mutex> Lock(Mutex); > assert(Done && "handle was not destroyed"); > @@ -213,20 +313,41 @@ ASTWorker::~ASTWorker() { > void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags, > UniqueFunction<void(std::vector<Diag>)> OnUpdated) > { > auto Task = [=](decltype(OnUpdated) OnUpdated) mutable { > + tooling::CompileCommand OldCommand = > std::move(FileInputs.CompileCommand); > FileInputs = Inputs; > - auto Diags = AST.rebuild(std::move(Inputs)); > + // Remove the old AST if it's still in cache. > + IdleASTs.take(this); > + > + log("Updating file " + FileName + " with command [" + > + Inputs.CompileCommand.Directory + "] " + > + llvm::join(Inputs.CompileCommand.CommandLine, " ")); > + // Rebuild the preamble and the AST. > + Invocation = buildCompilerInvocation(Inputs); > + if (!Invocation) { > + log("Could not build CompilerInvocation for file " + FileName); > + return; > + } > > + std::shared_ptr<const PreambleData> NewPreamble = buildPreamble( > + FileName, *Invocation, getPossiblyStalePreamble(), OldCommand, > Inputs, > + PCHs, StorePreambleInMemory, PreambleCallback); > { > std::lock_guard<std::mutex> Lock(Mutex); > - if (AST.getPreamble()) > - LastBuiltPreamble = AST.getPreamble(); > - LastASTSize = AST.getUsedBytes(); > + if (NewPreamble) > + LastBuiltPreamble = NewPreamble; > } > + // Build the AST for diagnostics. > + llvm::Optional<ParsedAST> AST = > + buildAST(FileName, > llvm::make_unique<CompilerInvocation>(*Invocation), > + Inputs, NewPreamble, PCHs); > // We want to report the diagnostics even if this update was > cancelled. > // It seems more useful than making the clients wait indefinitely if > they > // spam us with updates. > - if (Diags && WantDiags != WantDiagnostics::No) > - OnUpdated(std::move(*Diags)); > + if (WantDiags != WantDiagnostics::No && AST) > + OnUpdated(AST->getDiagnostics()); > + // Stash the AST in the cache for further use. > + IdleASTs.put(this, > + AST ? llvm::make_unique<ParsedAST>(std::move(*AST)) : > nullptr); > }; > > startTask("Update", Bind(Task, std::move(OnUpdated)), WantDiags); > @@ -236,20 +357,26 @@ void ASTWorker::runWithAST( > llvm::StringRef Name, > UniqueFunction<void(llvm::Expected<InputsAndAST>)> Action) { > auto Task = [=](decltype(Action) Action) { > - ParsedAST *ActualAST = AST.getAST(); > - if (!ActualAST) { > - Action(llvm::make_error<llvm::StringError>("invalid AST", > - > llvm::errc::invalid_argument)); > - return; > + llvm::Optional<std::unique_ptr<ParsedAST>> AST = IdleASTs.take(this); > + if (!AST) { > + // Try rebuilding the AST. > + llvm::Optional<ParsedAST> NewAST = > + Invocation > + ? buildAST(FileName, > + > llvm::make_unique<CompilerInvocation>(*Invocation), > + FileInputs, getPossiblyStalePreamble(), PCHs) > + : llvm::None; > + AST = NewAST ? llvm::make_unique<ParsedAST>(std::move(*NewAST)) : > nullptr; > } > - Action(InputsAndAST{FileInputs, *ActualAST}); > - > - // Size of the AST might have changed after reads too, e.g. if some > decls > - // were deserialized from preamble. > - std::lock_guard<std::mutex> Lock(Mutex); > - LastASTSize = ActualAST->getUsedBytes(); > + // Make sure we put the AST back into the LRU cache. > + auto _ = llvm::make_scope_exit( > + [&AST, this]() { IdleASTs.put(this, std::move(*AST)); }); > + // Run the user-provided action. > + if (!*AST) > + return Action(llvm::make_error<llvm::StringError>( > + "invalid AST", llvm::errc::invalid_argument)); > + Action(InputsAndAST{FileInputs, **AST}); > }; > - > startTask(Name, Bind(Task, std::move(Action)), > /*UpdateType=*/llvm::None); > } > @@ -261,10 +388,17 @@ ASTWorker::getPossiblyStalePreamble() co > } > > std::size_t ASTWorker::getUsedBytes() const { > - std::lock_guard<std::mutex> Lock(Mutex); > - return LastASTSize; > + // Note that we don't report the size of ASTs currently used for > processing > + // the in-flight requests. We used this information for debugging > purposes > + // only, so this should be fine. > + std::size_t Result = IdleASTs.getUsedBytes(this); > + if (auto Preamble = getPossiblyStalePreamble()) > + Result += Preamble->Preamble.getSize(); > + return Result; > } > > +bool ASTWorker::isASTCached() const { return IdleASTs.getUsedBytes(this) > != 0; } > + > void ASTWorker::stop() { > { > std::lock_guard<std::mutex> Lock(Mutex); > @@ -278,7 +412,7 @@ void ASTWorker::startTask(llvm::StringRe > llvm::Optional<WantDiagnostics> UpdateType) { > if (RunSync) { > assert(!Done && "running a task after stop()"); > - trace::Span Tracer(Name + ":" + llvm::sys::path::filename(File)); > + trace::Span Tracer(Name + ":" + llvm::sys::path::filename(FileName)); > Task(); > return; > } > @@ -415,10 +549,12 @@ struct TUScheduler::FileData { > TUScheduler::TUScheduler(unsigned AsyncThreadsCount, > bool StorePreamblesInMemory, > PreambleParsedCallback PreambleCallback, > - steady_clock::duration UpdateDebounce) > + std::chrono::steady_clock::duration > UpdateDebounce, > + ASTRetentionPolicy RetentionPolicy) > : StorePreamblesInMemory(StorePreamblesInMemory), > PCHOps(std::make_shared<PCHContainerOperations>()), > PreambleCallback(std::move(PreambleCallback)), > Barrier(AsyncThreadsCount), > + > IdleASTs(llvm::make_unique<ASTCache>(RetentionPolicy.MaxRetainedASTs)), > UpdateDebounce(UpdateDebounce) { > if (0 < AsyncThreadsCount) { > PreambleTasks.emplace(); > @@ -454,9 +590,9 @@ void TUScheduler::update(PathRef File, P > if (!FD) { > // Create a new worker to process the AST-related tasks. > ASTWorkerHandle Worker = ASTWorker::Create( > - File, WorkerThreads ? WorkerThreads.getPointer() : nullptr, > Barrier, > - CppFile(File, StorePreamblesInMemory, PCHOps, PreambleCallback), > - UpdateDebounce); > + File, *IdleASTs, WorkerThreads ? WorkerThreads.getPointer() : > nullptr, > + Barrier, UpdateDebounce, PCHOps, StorePreamblesInMemory, > + PreambleCallback); > FD = std::unique_ptr<FileData>(new FileData{ > Inputs.Contents, Inputs.CompileCommand, std::move(Worker)}); > } else { > @@ -538,5 +674,15 @@ TUScheduler::getUsedBytesPerFile() const > return Result; > } > > +std::vector<Path> TUScheduler::getFilesWithCachedAST() const { > + std::vector<Path> Result; > + for (auto &&PathAndFile : Files) { > + if (!PathAndFile.second->Worker->isASTCached()) > + continue; > + Result.push_back(PathAndFile.first()); > + } > + return Result; > +} > + > } // namespace clangd > } // namespace clang > > Modified: clang-tools-extra/trunk/clangd/TUScheduler.h > URL: > http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/TUScheduler.h?rev=333737&r1=333736&r2=333737&view=diff > > ============================================================================== > --- clang-tools-extra/trunk/clangd/TUScheduler.h (original) > +++ clang-tools-extra/trunk/clangd/TUScheduler.h Fri Jun 1 03:08:43 2018 > @@ -42,6 +42,15 @@ enum class WantDiagnostics { > /// within a bounded amount of time. > }; > > +/// Configuration of the AST retention policy. This only covers retention > of > +/// *idle* ASTs. If queue has operations requiring the AST, they might be > +/// kept in memory. > +struct ASTRetentionPolicy { > + /// Maximum number of ASTs to be retained in memory when there are no > pending > + /// requests for them. > + unsigned MaxRetainedASTs = 3; > +}; > + > /// Handles running tasks for ClangdServer and managing the resources > (e.g., > /// preambles and ASTs) for opened files. > /// TUScheduler is not thread-safe, only one thread should be providing > updates > @@ -53,13 +62,19 @@ class TUScheduler { > public: > TUScheduler(unsigned AsyncThreadsCount, bool StorePreamblesInMemory, > PreambleParsedCallback PreambleCallback, > - std::chrono::steady_clock::duration UpdateDebounce); > + std::chrono::steady_clock::duration UpdateDebounce, > + ASTRetentionPolicy RetentionPolicy); > ~TUScheduler(); > > /// Returns estimated memory usage for each of the currently open files. > /// The order of results is unspecified. > std::vector<std::pair<Path, std::size_t>> getUsedBytesPerFile() const; > > + /// Returns a list of files with ASTs currently stored in memory. This > method > + /// is not very reliable and is only used for test. E.g., the results > will not > + /// contain files that currently run something over their AST. > + std::vector<Path> getFilesWithCachedAST() const; > + > /// Schedule an update for \p File. Adds \p File to a list of tracked > files if > /// \p File was not part of it before. > /// FIXME(ibiryukov): remove the callback from this function. > @@ -99,11 +114,18 @@ private: > /// This class stores per-file data in the Files map. > struct FileData; > > +public: > + /// Responsible for retaining and rebuilding idle ASTs. An > implementation is > + /// an LRU cache. > + class ASTCache; > + > +private: > const bool StorePreamblesInMemory; > const std::shared_ptr<PCHContainerOperations> PCHOps; > const PreambleParsedCallback PreambleCallback; > Semaphore Barrier; > llvm::StringMap<std::unique_ptr<FileData>> Files; > + std::unique_ptr<ASTCache> IdleASTs; > // None when running tasks synchronously and non-None when running tasks > // asynchronously. > llvm::Optional<AsyncTaskRunner> PreambleTasks; > > Modified: clang-tools-extra/trunk/test/clangd/trace.test > URL: > http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/trace.test?rev=333737&r1=333736&r2=333737&view=diff > > ============================================================================== > --- clang-tools-extra/trunk/test/clangd/trace.test (original) > +++ clang-tools-extra/trunk/test/clangd/trace.test Fri Jun 1 03:08:43 2018 > @@ -8,14 +8,14 @@ > # CHECK: "args": { > # CHECK: "File": "{{.*(/|\\)}}foo.c" > # CHECK: }, > -# CHECK: "name": "Preamble", > +# CHECK: "name": "BuildPreamble", > # CHECK: "ph": "X", > # CHECK: } > # CHECK: { > # CHECK: "args": { > # CHECK: "File": "{{.*(/|\\)}}foo.c" > # CHECK: }, > -# CHECK: "name": "Build", > +# CHECK: "name": "BuildAST", > # CHECK: "ph": "X", > # CHECK: } > # CHECK: }, > > Modified: clang-tools-extra/trunk/unittests/clangd/FileIndexTests.cpp > URL: > http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/FileIndexTests.cpp?rev=333737&r1=333736&r2=333737&view=diff > > ============================================================================== > --- clang-tools-extra/trunk/unittests/clangd/FileIndexTests.cpp (original) > +++ clang-tools-extra/trunk/unittests/clangd/FileIndexTests.cpp Fri Jun 1 > 03:08:43 2018 > @@ -11,6 +11,7 @@ > #include "TestFS.h" > #include "TestTU.h" > #include "index/FileIndex.h" > +#include "clang/Frontend/CompilerInvocation.h" > #include "clang/Frontend/PCHContainerOperations.h" > #include "clang/Lex/Preprocessor.h" > #include "clang/Tooling/CompilationDatabase.h" > @@ -208,18 +209,6 @@ vector<Ty> make_vector(Arg A) {} > TEST(FileIndexTest, RebuildWithPreamble) { > auto FooCpp = testPath("foo.cpp"); > auto FooH = testPath("foo.h"); > - FileIndex Index; > - bool IndexUpdated = false; > - CppFile File("foo.cpp", /*StorePreambleInMemory=*/true, > - std::make_shared<PCHContainerOperations>(), > - [&Index, &IndexUpdated](PathRef FilePath, ASTContext &Ctx, > - std::shared_ptr<Preprocessor> PP) { > - EXPECT_FALSE(IndexUpdated) > - << "Expected only a single index update"; > - IndexUpdated = true; > - Index.update(FilePath, &Ctx, std::move(PP)); > - }); > - > // Preparse ParseInputs. > ParseInputs PI; > PI.CompileCommand.Directory = testRoot(); > @@ -243,7 +232,19 @@ TEST(FileIndexTest, RebuildWithPreamble) > )cpp"; > > // Rebuild the file. > - File.rebuild(std::move(PI)); > + auto CI = buildCompilerInvocation(PI); > + > + FileIndex Index; > + bool IndexUpdated = false; > + buildPreamble( > + FooCpp, *CI, /*OldPreamble=*/nullptr, tooling::CompileCommand(), PI, > + std::make_shared<PCHContainerOperations>(), /*StoreInMemory=*/true, > + [&Index, &IndexUpdated](PathRef FilePath, ASTContext &Ctx, > + std::shared_ptr<Preprocessor> PP) { > + EXPECT_FALSE(IndexUpdated) << "Expected only a single index > update"; > + IndexUpdated = true; > + Index.update(FilePath, &Ctx, std::move(PP)); > + }); > ASSERT_TRUE(IndexUpdated); > > // Check the index contains symbols from the preamble, but not from the > main > > Modified: clang-tools-extra/trunk/unittests/clangd/TUSchedulerTests.cpp > URL: > http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/TUSchedulerTests.cpp?rev=333737&r1=333736&r2=333737&view=diff > > ============================================================================== > --- clang-tools-extra/trunk/unittests/clangd/TUSchedulerTests.cpp > (original) > +++ clang-tools-extra/trunk/unittests/clangd/TUSchedulerTests.cpp Fri Jun > 1 03:08:43 2018 > @@ -18,8 +18,11 @@ > namespace clang { > namespace clangd { > > +using ::testing::_; > +using ::testing::AnyOf; > using ::testing::Pair; > using ::testing::Pointee; > +using ::testing::UnorderedElementsAre; > > void ignoreUpdate(llvm::Optional<std::vector<Diag>>) {} > void ignoreError(llvm::Error Err) { > @@ -43,7 +46,8 @@ TEST_F(TUSchedulerTests, MissingFiles) { > TUScheduler S(getDefaultAsyncThreadsCount(), > /*StorePreamblesInMemory=*/true, > /*PreambleParsedCallback=*/nullptr, > - > /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero()); > + > /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), > + ASTRetentionPolicy()); > > auto Added = testPath("added.cpp"); > Files[Added] = ""; > @@ -99,7 +103,8 @@ TEST_F(TUSchedulerTests, WantDiagnostics > getDefaultAsyncThreadsCount(), > /*StorePreamblesInMemory=*/true, > /*PreambleParsedCallback=*/nullptr, > - /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero()); > + /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), > + ASTRetentionPolicy()); > auto Path = testPath("foo.cpp"); > S.update(Path, getInputs(Path, ""), WantDiagnostics::Yes, > [&](std::vector<Diag>) { Ready.wait(); }); > @@ -127,7 +132,8 @@ TEST_F(TUSchedulerTests, Debounce) { > TUScheduler S(getDefaultAsyncThreadsCount(), > /*StorePreamblesInMemory=*/true, > /*PreambleParsedCallback=*/nullptr, > - /*UpdateDebounce=*/std::chrono::seconds(1)); > + /*UpdateDebounce=*/std::chrono::seconds(1), > + ASTRetentionPolicy()); > // FIXME: we could probably use timeouts lower than 1 second here. > auto Path = testPath("foo.cpp"); > S.update(Path, getInputs(Path, "auto (debounced)"), > WantDiagnostics::Auto, > @@ -158,7 +164,8 @@ TEST_F(TUSchedulerTests, ManyUpdates) { > TUScheduler S(getDefaultAsyncThreadsCount(), > /*StorePreamblesInMemory=*/true, > /*PreambleParsedCallback=*/nullptr, > - /*UpdateDebounce=*/std::chrono::milliseconds(50)); > + /*UpdateDebounce=*/std::chrono::milliseconds(50), > + ASTRetentionPolicy()); > > std::vector<std::string> Files; > for (int I = 0; I < FilesCount; ++I) { > @@ -219,18 +226,18 @@ TEST_F(TUSchedulerTests, ManyUpdates) { > > { > WithContextValue WithNonce(NonceKey, ++Nonce); > - S.runWithPreamble( > - "CheckPreamble", File, > - [Inputs, Nonce, &Mut, &TotalPreambleReads]( > - llvm::Expected<InputsAndPreamble> Preamble) { > - EXPECT_THAT(Context::current().get(NonceKey), > Pointee(Nonce)); > - > - ASSERT_TRUE((bool)Preamble); > - EXPECT_EQ(Preamble->Contents, Inputs.Contents); > - > - std::lock_guard<std::mutex> Lock(Mut); > - ++TotalPreambleReads; > - }); > + S.runWithPreamble("CheckPreamble", File, > + [Inputs, Nonce, &Mut, &TotalPreambleReads]( > + llvm::Expected<InputsAndPreamble> > Preamble) { > + > EXPECT_THAT(Context::current().get(NonceKey), > + Pointee(Nonce)); > + > + ASSERT_TRUE((bool)Preamble); > + EXPECT_EQ(Preamble->Contents, > Inputs.Contents); > + > + std::lock_guard<std::mutex> Lock(Mut); > + ++TotalPreambleReads; > + }); > } > } > } > @@ -242,5 +249,55 @@ TEST_F(TUSchedulerTests, ManyUpdates) { > EXPECT_EQ(TotalPreambleReads, FilesCount * UpdatesPerFile); > } > > +TEST_F(TUSchedulerTests, EvictedAST) { > + ASTRetentionPolicy Policy; > + Policy.MaxRetainedASTs = 2; > + TUScheduler S( > + /*AsyncThreadsCount=*/1, /*StorePreambleInMemory=*/true, > + PreambleParsedCallback(), > + /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), > Policy); > + > + llvm::StringLiteral SourceContents = R"cpp( > + int* a; > + double* b = a; > + )cpp"; > + > + auto Foo = testPath("foo.cpp"); > + auto Bar = testPath("bar.cpp"); > + auto Baz = testPath("baz.cpp"); > + > + std::atomic<int> BuiltASTCounter; > + BuiltASTCounter = false; > + // Build one file in advance. We will not access it later, so it will > be the > + // one that the cache will evict. > + S.update(Foo, getInputs(Foo, SourceContents), WantDiagnostics::Yes, > + [&BuiltASTCounter](std::vector<Diag> Diags) { > ++BuiltASTCounter; }); > + ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1))); > + ASSERT_EQ(BuiltASTCounter.load(), 1); > + > + // Build two more files. Since we can retain only 2 ASTs, these should > be the > + // ones we see in the cache later. > + S.update(Bar, getInputs(Bar, SourceContents), WantDiagnostics::Yes, > + [&BuiltASTCounter](std::vector<Diag> Diags) { > ++BuiltASTCounter; }); > + S.update(Baz, getInputs(Baz, SourceContents), WantDiagnostics::Yes, > + [&BuiltASTCounter](std::vector<Diag> Diags) { > ++BuiltASTCounter; }); > + ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1))); > + ASSERT_EQ(BuiltASTCounter.load(), 3); > + > + // Check only the last two ASTs are retained. > + ASSERT_THAT(S.getFilesWithCachedAST(), UnorderedElementsAre(Bar, Baz)); > + > + // Access the old file again. > + S.update(Foo, getInputs(Foo, SourceContents), WantDiagnostics::Yes, > + [&BuiltASTCounter](std::vector<Diag> Diags) { > ++BuiltASTCounter; }); > + ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1))); > + ASSERT_EQ(BuiltASTCounter.load(), 4); > + > + // Check the AST for foo.cpp is retained now and one of the others got > + // evicted. > + EXPECT_THAT(S.getFilesWithCachedAST(), > + UnorderedElementsAre(Foo, AnyOf(Bar, Baz))); > +} > + > } // namespace clangd > } // namespace clang > > > _______________________________________________ > cfe-commits mailing list > cfe-commits@lists.llvm.org > http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits > -- Regards, Ilya Biryukov
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits