From 75f1dd9b986c063af253b791e3edb33857d02dc5 Mon Sep 17 00:00:00 2001 From: Ilya Biryukov Date: Wed, 31 Jan 2018 08:51:16 +0000 Subject: [PATCH] [clangd] Refactored threading in ClangdServer Summary: We now provide an abstraction of Scheduler that abstracts threading and resource management in ClangdServer. No changes to behavior are intended with an exception of changed error messages. This patch is preliminary work to allow a revamped threading implementation that will move the threading code out of CppFile. Reviewers: sammccall, bkramer, jkorous-apple Reviewed By: sammccall Subscribers: hokein, mgorny, hintonda, ioeric, jkorous-apple, cfe-commits, klimek Differential Revision: https://reviews.llvm.org/D42174 llvm-svn: 323851 --- clang-tools-extra/clangd/CMakeLists.txt | 2 + clang-tools-extra/clangd/ClangdServer.cpp | 474 +++++++----------- clang-tools-extra/clangd/ClangdServer.h | 85 +--- clang-tools-extra/clangd/ClangdUnitStore.h | 16 +- clang-tools-extra/clangd/TUScheduler.cpp | 124 +++++ clang-tools-extra/clangd/TUScheduler.h | 94 ++++ clang-tools-extra/clangd/Threading.cpp | 61 +++ clang-tools-extra/clangd/Threading.h | 83 +++ .../unittests/clangd/CMakeLists.txt | 1 + .../unittests/clangd/TUSchedulerTests.cpp | 176 +++++++ 10 files changed, 749 insertions(+), 367 deletions(-) create mode 100644 clang-tools-extra/clangd/TUScheduler.cpp create mode 100644 clang-tools-extra/clangd/TUScheduler.h create mode 100644 clang-tools-extra/clangd/Threading.cpp create mode 100644 clang-tools-extra/clangd/Threading.h create mode 100644 clang-tools-extra/unittests/clangd/TUSchedulerTests.cpp diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index 9216a1aebd95..9c424391dd9b 100644 --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -21,7 +21,9 @@ add_clang_library(clangDaemon Protocol.cpp ProtocolHandlers.cpp SourceCode.cpp + Threading.cpp Trace.cpp + TUScheduler.cpp URI.cpp XRefs.cpp index/FileIndex.cpp diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index 46c9204149cf..6b33c2a26c07 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -22,8 +22,6 @@ #include "llvm/ADT/ScopeExit.h" #include "llvm/Support/Errc.h" #include "llvm/Support/FileSystem.h" -#include "llvm/Support/FormatProviders.h" -#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include @@ -33,6 +31,30 @@ using namespace clang::clangd; namespace { +// Issues an async read of AST and waits for results. +template +Ret blockingRunWithAST(TUScheduler &S, PathRef File, Func &&F) { + std::packaged_task)> Task( + std::forward(F)); + auto Future = Task.get_future(); + S.runWithAST(File, std::move(Task)); + return Future.get(); +} + +// Issues an async read of preamble and waits for results. +template +Ret blockingRunWithPreamble(TUScheduler &S, PathRef File, Func &&F) { + std::packaged_task)> Task( + std::forward(F)); + auto Future = Task.get_future(); + S.runWithPreamble(File, std::move(Task)); + return Future.get(); +} + +void ignoreError(llvm::Error Err) { + handleAllErrors(std::move(Err), [](const llvm::ErrorInfoBase &) {}); +} + std::string getStandardResourceDir() { static int Dummy; // Just an address in this process. return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy); @@ -67,70 +89,6 @@ RealFileSystemProvider::getTaggedFileSystem(PathRef File) { return make_tagged(vfs::getRealFileSystem(), VFSTag()); } -unsigned clangd::getDefaultAsyncThreadsCount() { - unsigned HardwareConcurrency = std::thread::hardware_concurrency(); - // C++ standard says that hardware_concurrency() - // may return 0, fallback to 1 worker thread in - // that case. - if (HardwareConcurrency == 0) - return 1; - return HardwareConcurrency; -} - -ClangdScheduler::ClangdScheduler(unsigned AsyncThreadsCount) - : RunSynchronously(AsyncThreadsCount == 0) { - if (RunSynchronously) { - // Don't start the worker thread if we're running synchronously - return; - } - - Workers.reserve(AsyncThreadsCount); - for (unsigned I = 0; I < AsyncThreadsCount; ++I) { - Workers.push_back(std::thread([this, I]() { - llvm::set_thread_name(llvm::formatv("scheduler/{0}", I)); - while (true) { - UniqueFunction Request; - - // Pick request from the queue - { - std::unique_lock Lock(Mutex); - // Wait for more requests. - RequestCV.wait(Lock, - [this] { return !RequestQueue.empty() || Done; }); - if (Done) - return; - - assert(!RequestQueue.empty() && "RequestQueue was empty"); - - // We process requests starting from the front of the queue. Users of - // ClangdScheduler have a way to prioritise their requests by putting - // them to the either side of the queue (using either addToEnd or - // addToFront). - Request = std::move(RequestQueue.front()); - RequestQueue.pop_front(); - } // unlock Mutex - - Request(); - } - })); - } -} - -ClangdScheduler::~ClangdScheduler() { - if (RunSynchronously) - return; // no worker thread is running in that case - - { - std::lock_guard Lock(Mutex); - // Wake up the worker thread - Done = true; - } // unlock Mutex - RequestCV.notify_all(); - - for (auto &Worker : Workers) - Worker.join(); -} - ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB, DiagnosticsConsumer &DiagConsumer, FileSystemProvider &FSProvider, @@ -142,18 +100,17 @@ ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB, ResourceDir ? ResourceDir->str() : getStandardResourceDir()), DiagConsumer(DiagConsumer), FSProvider(FSProvider), FileIdx(BuildDynamicSymbolIndex ? new FileIndex() : nullptr), - // Pass a callback into `Units` to extract symbols from a newly parsed - // file and rebuild the file index synchronously each time an AST is - // parsed. + PCHs(std::make_shared()), + // Pass a callback into `WorkScheduler` to extract symbols from a newly + // parsed file and rebuild the file index synchronously each time an AST + // is parsed. // FIXME(ioeric): this can be slow and we may be able to index on less // critical paths. - Units(FileIdx - ? [this](const Context &Ctx, PathRef Path, - ParsedAST *AST) { FileIdx->update(Ctx, Path, AST); } - : ASTParsedCallback()), - PCHs(std::make_shared()), - StorePreamblesInMemory(StorePreamblesInMemory), - WorkScheduler(AsyncThreadsCount) { + WorkScheduler( + AsyncThreadsCount, StorePreamblesInMemory, + FileIdx ? [this](const Context &Ctx, PathRef Path, + ParsedAST *AST) { FileIdx->update(Ctx, Path, AST); } + : ASTParsedCallback()) { if (FileIdx && StaticIdx) { MergedIndex = mergeIndex(FileIdx.get(), StaticIdx); Index = MergedIndex.get(); @@ -175,21 +132,29 @@ void ClangdServer::setRootPath(PathRef RootPath) { std::future ClangdServer::addDocument(Context Ctx, PathRef File, StringRef Contents) { DocVersion Version = DraftMgr.updateDraft(File, Contents); - auto TaggedFS = FSProvider.getTaggedFileSystem(File); - std::shared_ptr Resources = - Units.getOrCreateFile(File, StorePreamblesInMemory, PCHs); return scheduleReparseAndDiags(std::move(Ctx), File, VersionedDraft{Version, Contents.str()}, - std::move(Resources), std::move(TaggedFS)); + std::move(TaggedFS)); } std::future ClangdServer::removeDocument(Context Ctx, PathRef File) { DraftMgr.removeDraft(File); CompileArgs.invalidate(File); - std::shared_ptr Resources = Units.removeIfPresent(File); - return scheduleCancelRebuild(std::move(Ctx), std::move(Resources)); + std::promise DonePromise; + std::future DoneFuture = DonePromise.get_future(); + + auto Callback = BindWithForward( + [](Context Ctx, std::promise DonePromise, llvm::Error Err) { + if (Err) + ignoreError(std::move(Err)); + DonePromise.set_value(std::move(Ctx)); + }, + std::move(Ctx), std::move(DonePromise)); + + WorkScheduler.remove(File, std::move(Callback)); + return DoneFuture; } std::future ClangdServer::forceReparse(Context Ctx, PathRef File) { @@ -202,10 +167,8 @@ std::future ClangdServer::forceReparse(Context Ctx, PathRef File) { CompileArgs.invalidate(File); auto TaggedFS = FSProvider.getTaggedFileSystem(File); - std::shared_ptr Resources = - Units.getOrCreateFile(File, StorePreamblesInMemory, PCHs); - return scheduleReparseAndDiags(std::move(Ctx), File, FileContents, - std::move(Resources), std::move(TaggedFS)); + return scheduleReparseAndDiags(std::move(Ctx), File, std::move(FileContents), + std::move(TaggedFS)); } std::future>> @@ -237,97 +200,83 @@ void ClangdServer::codeComplete( IntrusiveRefCntPtr *UsedFS) { using CallbackType = UniqueFunction)>; - std::string Contents; - if (OverridenContents) { - Contents = *OverridenContents; - } else { - auto FileContents = DraftMgr.getDraft(File); - assert(FileContents.Draft && - "codeComplete is called for non-added document"); - - Contents = std::move(*FileContents.Draft); - } - auto TaggedFS = FSProvider.getTaggedFileSystem(File); if (UsedFS) *UsedFS = TaggedFS.Value; - std::shared_ptr Resources = Units.getFile(File); - assert(Resources && "Calling completion on non-added file"); - - // Remember the current Preamble and use it when async task starts executing. - // At the point when async task starts executing, we may have a different - // Preamble in Resources. However, we assume the Preamble that we obtain here - // is reusable in completion more often. - std::shared_ptr Preamble = - Resources->getPossiblyStalePreamble(); // Copy completion options for passing them to async task handler. auto CodeCompleteOpts = Opts; if (!CodeCompleteOpts.Index) // Respect overridden index. CodeCompleteOpts.Index = Index; - // Copy File, as it is a PathRef that will go out of scope before Task is - // executed. - Path FileStr = File; + std::string Contents; + if (OverridenContents) { + Contents = OverridenContents->str(); + } else { + VersionedDraft Latest = DraftMgr.getDraft(File); + assert(Latest.Draft && "codeComplete called for non-added document"); + Contents = *Latest.Draft; + } + // Copy PCHs to avoid accessing this->PCHs concurrently std::shared_ptr PCHs = this->PCHs; - tooling::CompileCommand CompileCommand = CompileArgs.getCompileCommand(File); - // A task that will be run asynchronously. - auto Task = - // 'mutable' to reassign Preamble variable. - [FileStr, Preamble, Resources, Contents, Pos, CodeCompleteOpts, TaggedFS, - PCHs, CompileCommand](Context Ctx, CallbackType Callback) mutable { - if (!Preamble) { - // Maybe we built some preamble before processing this request. - Preamble = Resources->getPossiblyStalePreamble(); - } - // FIXME(ibiryukov): even if Preamble is non-null, we may want to check - // both the old and the new version in case only one of them matches. - CompletionList Result = clangd::codeComplete( - Ctx, FileStr, CompileCommand, - Preamble ? &Preamble->Preamble : nullptr, Contents, Pos, - TaggedFS.Value, PCHs, CodeCompleteOpts); + auto Task = [PCHs, Pos, TaggedFS, CodeCompleteOpts]( + Context Ctx, std::string Contents, Path File, + CallbackType Callback, llvm::Expected IP) { + assert(IP && "error when trying to read preamble for codeComplete"); + auto PreambleData = IP->Preamble; + auto &Command = IP->Inputs.CompileCommand; - Callback(std::move(Ctx), - make_tagged(std::move(Result), std::move(TaggedFS.Tag))); - }; + // FIXME(ibiryukov): even if Preamble is non-null, we may want to check + // both the old and the new version in case only one of them matches. + CompletionList Result = clangd::codeComplete( + Ctx, File, Command, PreambleData ? &PreambleData->Preamble : nullptr, + Contents, Pos, TaggedFS.Value, PCHs, CodeCompleteOpts); - WorkScheduler.addToFront(std::move(Task), std::move(Ctx), - std::move(Callback)); + Callback(std::move(Ctx), + make_tagged(std::move(Result), std::move(TaggedFS.Tag))); + }; + + WorkScheduler.runWithPreamble( + File, BindWithForward(Task, std::move(Ctx), std::move(Contents), + File.str(), std::move(Callback))); } llvm::Expected> ClangdServer::signatureHelp(const Context &Ctx, PathRef File, Position Pos, llvm::Optional OverridenContents, IntrusiveRefCntPtr *UsedFS) { - std::string DraftStorage; - if (!OverridenContents) { - auto FileContents = DraftMgr.getDraft(File); - if (!FileContents.Draft) - return llvm::make_error( - "signatureHelp is called for non-added document", - llvm::errc::invalid_argument); - - DraftStorage = std::move(*FileContents.Draft); - OverridenContents = DraftStorage; - } - auto TaggedFS = FSProvider.getTaggedFileSystem(File); if (UsedFS) *UsedFS = TaggedFS.Value; - std::shared_ptr Resources = Units.getFile(File); - if (!Resources) - return llvm::make_error( - "signatureHelp is called for non-added document", - llvm::errc::invalid_argument); + std::string Contents; + if (OverridenContents) { + Contents = OverridenContents->str(); + } else { + VersionedDraft Latest = DraftMgr.getDraft(File); + if (!Latest.Draft) + return llvm::make_error( + "signatureHelp is called for non-added document", + llvm::errc::invalid_argument); + Contents = std::move(*Latest.Draft); + } - auto Preamble = Resources->getPossiblyStalePreamble(); - auto Result = - clangd::signatureHelp(Ctx, File, CompileArgs.getCompileCommand(File), - Preamble ? &Preamble->Preamble : nullptr, - *OverridenContents, Pos, TaggedFS.Value, PCHs); - return make_tagged(std::move(Result), TaggedFS.Tag); + auto Action = [=, &Ctx](llvm::Expected IP) + -> Expected> { + if (!IP) + return IP.takeError(); + auto PreambleData = IP->Preamble; + auto &Command = IP->Inputs.CompileCommand; + + return make_tagged( + clangd::signatureHelp(Ctx, File, Command, + PreambleData ? &PreambleData->Preamble : nullptr, + Contents, Pos, TaggedFS.Value, PCHs), + TaggedFS.Tag); + }; + return blockingRunWithPreamble>>(WorkScheduler, + File, Action); } llvm::Expected @@ -359,49 +308,55 @@ ClangdServer::formatOnType(StringRef Code, PathRef File, Position Pos) { Expected> ClangdServer::rename(const Context &Ctx, PathRef File, Position Pos, llvm::StringRef NewName) { - std::shared_ptr Resources = Units.getFile(File); - RefactoringResultCollector ResultCollector; - Resources->getAST().get()->runUnderLock([&](ParsedAST *AST) { - const SourceManager &SourceMgr = AST->getASTContext().getSourceManager(); + using RetType = Expected>; + auto Action = [=](Expected InpAST) -> RetType { + if (!InpAST) + return InpAST.takeError(); + auto &AST = InpAST->AST; + + RefactoringResultCollector ResultCollector; + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); if (!FE) - return; + return llvm::make_error( + "rename called for non-added document", llvm::errc::invalid_argument); SourceLocation SourceLocationBeg = - clangd::getBeginningOfIdentifier(*AST, Pos, FE); + clangd::getBeginningOfIdentifier(AST, Pos, FE); tooling::RefactoringRuleContext Context( - AST->getASTContext().getSourceManager()); - Context.setASTContext(AST->getASTContext()); + AST.getASTContext().getSourceManager()); + Context.setASTContext(AST.getASTContext()); auto Rename = clang::tooling::RenameOccurrences::initiate( Context, SourceRange(SourceLocationBeg), NewName.str()); - if (!Rename) { - ResultCollector.Result = Rename.takeError(); - return; - } - Rename->invoke(ResultCollector, Context); - }); - assert(ResultCollector.Result.hasValue()); - if (!ResultCollector.Result.getValue()) - return ResultCollector.Result->takeError(); + if (!Rename) + return Rename.takeError(); - std::vector Replacements; - for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) { - tooling::Replacements ChangeReps = Change.getReplacements(); - for (const auto &Rep : ChangeReps) { - // FIXME: Right now we only support renaming the main file, so we drop - // replacements not for the main file. In the future, we might consider to - // support: - // * rename in any included header - // * rename only in the "main" header - // * provide an error if there are symbols we won't rename (e.g. - // std::vector) - // * rename globally in project - // * rename in open files - if (Rep.getFilePath() == File) - Replacements.push_back(Rep); + Rename->invoke(ResultCollector, Context); + + assert(ResultCollector.Result.hasValue()); + if (!ResultCollector.Result.getValue()) + return ResultCollector.Result->takeError(); + + std::vector Replacements; + for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) { + tooling::Replacements ChangeReps = Change.getReplacements(); + for (const auto &Rep : ChangeReps) { + // FIXME: Right now we only support renaming the main file, so we + // drop replacements not for the main file. In the future, we might + // consider to support: + // * rename in any included header + // * rename only in the "main" header + // * provide an error if there are symbols we won't rename (e.g. + // std::vector) + // * rename globally in project + // * rename in open files + if (Rep.getFilePath() == File) + Replacements.push_back(Rep); + } } - } - return Replacements; + return Replacements; + }; + return blockingRunWithAST(WorkScheduler, File, std::move(Action)); } llvm::Optional ClangdServer::getDocument(PathRef File) { @@ -412,40 +367,36 @@ llvm::Optional ClangdServer::getDocument(PathRef File) { } std::string ClangdServer::dumpAST(PathRef File) { - std::shared_ptr Resources = Units.getFile(File); - if (!Resources) - return ""; - - std::string Result; - Resources->getAST().get()->runUnderLock([&Result](ParsedAST *AST) { - llvm::raw_string_ostream ResultOS(Result); - if (AST) { - clangd::dumpAST(*AST, ResultOS); - } else { - ResultOS << ""; + auto Action = [](llvm::Expected InpAST) -> std::string { + if (!InpAST) { + ignoreError(InpAST.takeError()); + return ""; } + + std::string Result; + + llvm::raw_string_ostream ResultOS(Result); + clangd::dumpAST(InpAST->AST, ResultOS); ResultOS.flush(); - }); - return Result; + + return Result; + }; + return blockingRunWithAST(WorkScheduler, File, + std::move(Action)); } llvm::Expected>> ClangdServer::findDefinitions(const Context &Ctx, PathRef File, Position Pos) { auto TaggedFS = FSProvider.getTaggedFileSystem(File); - std::shared_ptr Resources = Units.getFile(File); - if (!Resources) - return llvm::make_error( - "findDefinitions called on non-added file", - llvm::errc::invalid_argument); - - std::vector Result; - Resources->getAST().get()->runUnderLock([Pos, &Result, &Ctx](ParsedAST *AST) { - if (!AST) - return; - Result = clangd::findDefinitions(Ctx, *AST, Pos); - }); - return make_tagged(std::move(Result), TaggedFS.Tag); + using RetType = llvm::Expected>>; + auto Action = [=, &Ctx](llvm::Expected InpAST) -> RetType { + if (!InpAST) + return InpAST.takeError(); + auto Result = clangd::findDefinitions(Ctx, InpAST->AST, Pos); + return make_tagged(std::move(Result), TaggedFS.Tag); + }; + return blockingRunWithAST(WorkScheduler, File, Action); } llvm::Optional ClangdServer::switchSourceHeader(PathRef Path) { @@ -532,59 +483,35 @@ ClangdServer::findDocumentHighlights(const Context &Ctx, PathRef File, auto TaggedFS = FSProvider.getTaggedFileSystem(File); - std::shared_ptr Resources = Units.getFile(File); - if (!Resources) - return llvm::make_error( - "findDocumentHighlights called on non-added file", - llvm::errc::invalid_argument); - - std::vector Result; - llvm::Optional Err; - Resources->getAST().get()->runUnderLock([Pos, &Ctx, &Err, - &Result](ParsedAST *AST) { - if (!AST) { - Err = llvm::make_error("Invalid AST", - llvm::errc::invalid_argument); - return; - } - Result = clangd::findDocumentHighlights(Ctx, *AST, Pos); - }); - - if (Err) - return std::move(*Err); - return make_tagged(Result, TaggedFS.Tag); + using RetType = llvm::Expected>>; + auto Action = [=, &Ctx](llvm::Expected InpAST) -> RetType { + if (!InpAST) + return InpAST.takeError(); + auto Result = clangd::findDocumentHighlights(Ctx, InpAST->AST, Pos); + return make_tagged(std::move(Result), TaggedFS.Tag); + }; + return blockingRunWithAST(WorkScheduler, File, Action); } std::future ClangdServer::scheduleReparseAndDiags( Context Ctx, PathRef File, VersionedDraft Contents, - std::shared_ptr Resources, Tagged> TaggedFS) { - assert(Contents.Draft && "Draft must have contents"); - ParseInputs Inputs = {CompileArgs.getCompileCommand(File), - std::move(TaggedFS.Value), *std::move(Contents.Draft)}; + tooling::CompileCommand Command = CompileArgs.getCompileCommand(File); + + using OptDiags = llvm::Optional>; + + DocVersion Version = Contents.Version; + Path FileStr = File.str(); + VFSTag Tag = std::move(TaggedFS.Tag); - UniqueFunction>(const Context &)> - DeferredRebuild = Resources->deferRebuild(std::move(Inputs)); std::promise DonePromise; std::future DoneFuture = DonePromise.get_future(); - DocVersion Version = Contents.Version; - Path FileStr = File; - VFSTag Tag = TaggedFS.Tag; - auto ReparseAndPublishDiags = - [this, FileStr, Version, - Tag](UniqueFunction>( - const Context &)> - DeferredRebuild, - std::promise DonePromise, Context Ctx) -> void { + auto Callback = [this, Version, FileStr, + Tag](std::promise DonePromise, Context Ctx, + OptDiags Diags) { auto Guard = llvm::make_scope_exit([&]() { DonePromise.set_value(std::move(Ctx)); }); - - auto CurrentVersion = DraftMgr.getVersion(FileStr); - if (CurrentVersion != Version) - return; // This request is outdated - - auto Diags = DeferredRebuild(Ctx); if (!Diags) return; // A new reparse was requested before this one completed. @@ -600,36 +527,15 @@ std::future ClangdServer::scheduleReparseAndDiags( return; LastReportedDiagsVersion = Version; - DiagConsumer.onDiagnosticsReady(Ctx, FileStr, - make_tagged(std::move(*Diags), Tag)); + DiagConsumer.onDiagnosticsReady( + Ctx, FileStr, make_tagged(std::move(*Diags), std::move(Tag))); }; - WorkScheduler.addToFront(std::move(ReparseAndPublishDiags), - std::move(DeferredRebuild), std::move(DonePromise), - std::move(Ctx)); - return DoneFuture; -} - -std::future -ClangdServer::scheduleCancelRebuild(Context Ctx, - std::shared_ptr Resources) { - std::promise DonePromise; - std::future DoneFuture = DonePromise.get_future(); - if (!Resources) { - // No need to schedule any cleanup. - DonePromise.set_value(std::move(Ctx)); - return DoneFuture; - } - - UniqueFunction DeferredCancel = Resources->deferCancelRebuild(); - auto CancelReparses = [Resources](std::promise DonePromise, - UniqueFunction DeferredCancel, - Context Ctx) { - DeferredCancel(); - DonePromise.set_value(std::move(Ctx)); - }; - WorkScheduler.addToFront(std::move(CancelReparses), std::move(DonePromise), - std::move(DeferredCancel), std::move(Ctx)); + WorkScheduler.update(std::move(Ctx), File, + ParseInputs{std::move(Command), + std::move(TaggedFS.Value), + std::move(*Contents.Draft)}, + BindWithForward(Callback, std::move(DonePromise))); return DoneFuture; } @@ -640,5 +546,5 @@ void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { std::vector> ClangdServer::getUsedBytesPerFile() const { - return Units.getUsedBytesPerFile(); + return WorkScheduler.getUsedBytesPerFile(); } diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index 613927115eec..c4fb38dfd444 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -18,17 +18,15 @@ #include "Function.h" #include "GlobalCompilationDatabase.h" #include "Protocol.h" +#include "TUScheduler.h" #include "index/FileIndex.h" #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/Core/Replacement.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/StringRef.h" -#include #include -#include #include -#include #include #include @@ -99,71 +97,6 @@ public: class ClangdServer; -/// Returns a number of a default async threads to use for ClangdScheduler. -/// Returned value is always >= 1 (i.e. will not cause requests to be processed -/// synchronously). -unsigned getDefaultAsyncThreadsCount(); - -/// Handles running WorkerRequests of ClangdServer on a number of worker -/// threads. -class ClangdScheduler { -public: - /// If \p AsyncThreadsCount is 0, requests added using addToFront and addToEnd - /// will be processed synchronously on the calling thread. - // Otherwise, \p AsyncThreadsCount threads will be created to schedule the - // requests. - ClangdScheduler(unsigned AsyncThreadsCount); - ~ClangdScheduler(); - - /// Add a new request to run function \p F with args \p As to the start of the - /// queue. The request will be run on a separate thread. - template - void addToFront(Func &&F, Args &&... As) { - if (RunSynchronously) { - std::forward(F)(std::forward(As)...); - return; - } - - { - std::lock_guard Lock(Mutex); - RequestQueue.push_front( - BindWithForward(std::forward(F), std::forward(As)...)); - } - RequestCV.notify_one(); - } - - /// Add a new request to run function \p F with args \p As to the end of the - /// queue. The request will be run on a separate thread. - template void addToEnd(Func &&F, Args &&... As) { - if (RunSynchronously) { - std::forward(F)(std::forward(As)...); - return; - } - - { - std::lock_guard Lock(Mutex); - RequestQueue.push_back( - BindWithForward(std::forward(F), std::forward(As)...)); - } - RequestCV.notify_one(); - } - -private: - bool RunSynchronously; - std::mutex Mutex; - /// We run some tasks on separate threads(parsing, CppFile cleanup). - /// These threads looks into RequestQueue to find requests to handle and - /// terminate when Done is set to true. - std::vector Workers; - /// Setting Done to true will make the worker threads terminate. - bool Done = false; - /// A queue of requests. Elements of this vector are async computations (i.e. - /// results of calling std::async(std::launch::deferred, ...)). - std::deque> RequestQueue; - /// Condition variable to wake up worker threads. - std::condition_variable RequestCV; -}; - /// Provides API to manage ASTs for a collection of C++ files and request /// various language features. /// Currently supports async diagnostics, code completion, formatting and goto @@ -221,17 +154,23 @@ public: /// constructor will receive onDiagnosticsReady callback. /// \return A future that will become ready when the rebuild (including /// diagnostics) is finished. + /// FIXME: don't return futures here, LSP does not require a response for this + /// request. std::future addDocument(Context Ctx, PathRef File, StringRef Contents); /// Remove \p File from list of tracked files, schedule a request to free /// resources associated with it. /// \return A future that will become ready when the file is removed and all /// associated resources are freed. + /// FIXME: don't return futures here, LSP does not require a response for this + /// request. std::future removeDocument(Context Ctx, PathRef File); /// Force \p File to be reparsed using the latest contents. /// Will also check if CompileCommand, provided by GlobalCompilationDatabase /// for \p File has changed. If it has, will remove currently stored Preamble /// and AST and rebuild them from scratch. + /// FIXME: don't return futures here, LSP does not require a response for this + /// request. std::future forceReparse(Context Ctx, PathRef File); /// DEPRECATED. Please use a callback-based version, this API is deprecated @@ -340,12 +279,8 @@ private: std::future scheduleReparseAndDiags(Context Ctx, PathRef File, VersionedDraft Contents, - std::shared_ptr Resources, Tagged> TaggedFS); - std::future - scheduleCancelRebuild(Context Ctx, std::shared_ptr Resources); - CompileArgsCache CompileArgs; DiagnosticsConsumer &DiagConsumer; FileSystemProvider &FSProvider; @@ -360,11 +295,9 @@ private: std::unique_ptr FileIdx; // If present, a merged view of FileIdx and an external index. Read via Index. std::unique_ptr MergedIndex; - CppFileCollection Units; // If set, this represents the workspace path. llvm::Optional RootPath; std::shared_ptr PCHs; - bool StorePreamblesInMemory; /// Used to serialize diagnostic callbacks. /// FIXME(ibiryukov): get rid of an extra map and put all version counters /// into CppFile. @@ -373,8 +306,8 @@ private: llvm::StringMap ReportedDiagnosticVersions; // WorkScheduler has to be the last member, because its destructor has to be // called before all other members to stop the worker thread that references - // ClangdServer - ClangdScheduler WorkScheduler; + // ClangdServer. + TUScheduler WorkScheduler; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/ClangdUnitStore.h b/clang-tools-extra/clangd/ClangdUnitStore.h index e28f33431c58..6ec03023299f 100644 --- a/clang-tools-extra/clangd/ClangdUnitStore.h +++ b/clang-tools-extra/clangd/ClangdUnitStore.h @@ -27,18 +27,19 @@ class CppFileCollection { public: /// \p ASTCallback is called when a file is parsed synchronously. This should /// not be expensive since it blocks diagnostics. - explicit CppFileCollection(ASTParsedCallback ASTCallback) - : ASTCallback(std::move(ASTCallback)) {} + explicit CppFileCollection(bool StorePreamblesInMemory, + std::shared_ptr PCHs, + ASTParsedCallback ASTCallback) + : ASTCallback(std::move(ASTCallback)), PCHs(std::move(PCHs)), + StorePreamblesInMemory(StorePreamblesInMemory) {} - std::shared_ptr - getOrCreateFile(PathRef File, bool StorePreamblesInMemory, - std::shared_ptr PCHs) { + std::shared_ptr getOrCreateFile(PathRef File) { std::lock_guard Lock(Mutex); auto It = OpenedFiles.find(File); if (It == OpenedFiles.end()) { It = OpenedFiles .try_emplace(File, CppFile::Create(File, StorePreamblesInMemory, - std::move(PCHs), ASTCallback)) + PCHs, ASTCallback)) .first; } return It->second; @@ -46,7 +47,6 @@ public: std::shared_ptr getFile(PathRef File) const { std::lock_guard Lock(Mutex); - auto It = OpenedFiles.find(File); if (It == OpenedFiles.end()) return nullptr; @@ -64,6 +64,8 @@ private: mutable std::mutex Mutex; llvm::StringMap> OpenedFiles; ASTParsedCallback ASTCallback; + std::shared_ptr PCHs; + bool StorePreamblesInMemory; }; } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/TUScheduler.cpp b/clang-tools-extra/clangd/TUScheduler.cpp new file mode 100644 index 000000000000..96b29f12fdd1 --- /dev/null +++ b/clang-tools-extra/clangd/TUScheduler.cpp @@ -0,0 +1,124 @@ +#include "TUScheduler.h" +#include "clang/Frontend/PCHContainerOperations.h" +#include "llvm/Support/Errc.h" + +namespace clang { +namespace clangd { +unsigned getDefaultAsyncThreadsCount() { + unsigned HardwareConcurrency = std::thread::hardware_concurrency(); + // C++ standard says that hardware_concurrency() + // may return 0, fallback to 1 worker thread in + // that case. + if (HardwareConcurrency == 0) + return 1; + return HardwareConcurrency; +} + +TUScheduler::TUScheduler(unsigned AsyncThreadsCount, + bool StorePreamblesInMemory, + ASTParsedCallback ASTCallback) + + : Files(StorePreamblesInMemory, std::make_shared(), + std::move(ASTCallback)), + Threads(AsyncThreadsCount) {} + +void TUScheduler::update( + Context Ctx, PathRef File, ParseInputs Inputs, + UniqueFunction>)> + OnUpdated) { + CachedInputs[File] = Inputs; + + auto Resources = Files.getOrCreateFile(File); + auto DeferredRebuild = Resources->deferRebuild(std::move(Inputs)); + + Threads.addToFront( + [](Context Ctx, decltype(OnUpdated) OnUpdated, + decltype(DeferredRebuild) DeferredRebuild) { + auto Diags = DeferredRebuild(Ctx); + OnUpdated(std::move(Ctx), Diags); + }, + std::move(Ctx), std::move(OnUpdated), std::move(DeferredRebuild)); +} + +void TUScheduler::remove(PathRef File, + UniqueFunction Action) { + CachedInputs.erase(File); + + auto Resources = Files.removeIfPresent(File); + if (!Resources) { + Action(llvm::make_error( + "trying to remove non-added document", llvm::errc::invalid_argument)); + return; + } + + auto DeferredCancel = Resources->deferCancelRebuild(); + Threads.addToFront( + [](decltype(Action) Action, decltype(DeferredCancel) DeferredCancel) { + DeferredCancel(); + Action(llvm::Error::success()); + }, + std::move(Action), std::move(DeferredCancel)); +} + +void TUScheduler::runWithAST( + PathRef File, UniqueFunction)> Action) { + auto Resources = Files.getFile(File); + if (!Resources) { + Action(llvm::make_error( + "trying to get AST for non-added document", + llvm::errc::invalid_argument)); + return; + } + + const ParseInputs &Inputs = getInputs(File); + // We currently block the calling thread until AST is available and run the + // action on the calling thread to avoid inconsistent states coming from + // subsequent updates. + // FIXME(ibiryukov): this should be moved to the worker threads. + Resources->getAST().get()->runUnderLock([&](ParsedAST *AST) { + if (AST) + Action(InputsAndAST{Inputs, *AST}); + else + Action(llvm::make_error( + "Could not build AST for the latest file update", + llvm::errc::invalid_argument)); + }); +} + +void TUScheduler::runWithPreamble( + PathRef File, + UniqueFunction)> Action) { + std::shared_ptr Resources = Files.getFile(File); + if (!Resources) { + Action(llvm::make_error( + "trying to get preamble for non-added document", + llvm::errc::invalid_argument)); + return; + } + + const ParseInputs &Inputs = getInputs(File); + std::shared_ptr Preamble = + Resources->getPossiblyStalePreamble(); + Threads.addToFront( + [Resources, Preamble, Inputs](decltype(Action) Action) mutable { + if (!Preamble) + Preamble = Resources->getPossiblyStalePreamble(); + + Action(InputsAndPreamble{Inputs, Preamble.get()}); + }, + std::move(Action)); +} + +const ParseInputs &TUScheduler::getInputs(PathRef File) { + auto It = CachedInputs.find(File); + assert(It != CachedInputs.end()); + return It->second; +} + +std::vector> +TUScheduler::getUsedBytesPerFile() const { + return Files.getUsedBytesPerFile(); +} +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/TUScheduler.h b/clang-tools-extra/clangd/TUScheduler.h new file mode 100644 index 000000000000..c145658ead77 --- /dev/null +++ b/clang-tools-extra/clangd/TUScheduler.h @@ -0,0 +1,94 @@ +//===--- TUScheduler.h -------------------------------------------*-C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_TUSCHEDULER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_TUSCHEDULER_H + +#include "ClangdUnit.h" +#include "ClangdUnitStore.h" +#include "Function.h" +#include "Threading.h" + +namespace clang { +namespace clangd { +/// Returns a number of a default async threads to use for TUScheduler. +/// Returned value is always >= 1 (i.e. will not cause requests to be processed +/// synchronously). +unsigned getDefaultAsyncThreadsCount(); + +struct InputsAndAST { + const ParseInputs &Inputs; + ParsedAST &AST; +}; + +struct InputsAndPreamble { + const ParseInputs &Inputs; + const PreambleData *Preamble; +}; + +/// 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 +/// and scheduling tasks. +/// Callbacks are run on a threadpool and it's appropriate to do slow work in +/// them. +class TUScheduler { +public: + TUScheduler(unsigned AsyncThreadsCount, bool StorePreamblesInMemory, + ASTParsedCallback ASTCallback); + + /// Returns estimated memory usage for each of the currently open files. + /// The order of results is unspecified. + std::vector> getUsedBytesPerFile() 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. + void update( + Context Ctx, PathRef File, ParseInputs Inputs, + UniqueFunction>)> + OnUpdated); + + /// Remove \p File from the list of tracked files and schedule removal of its + /// resources. \p Action will be called when resources are freed. + /// If an error occurs during processing, it is forwarded to the \p Action + /// callback. + /// FIXME(ibiryukov): the callback passed to this function is not used, we + /// should remove it. + void remove(PathRef File, UniqueFunction Action); + + /// Schedule an async read of the AST. \p Action will be called when AST is + /// ready. The AST passed to \p Action refers to the version of \p File + /// tracked at the time of the call, even if new updates are received before + /// \p Action is executed. + /// If an error occurs during processing, it is forwarded to the \p Action + /// callback. + void runWithAST(PathRef File, + UniqueFunction)> Action); + + /// Schedule an async read of the Preamble. Preamble passed to \p Action may + /// be built for any version of the file, callers must not rely on it being + /// consistent with the current version of the file. + /// If an error occurs during processing, it is forwarded to the \p Action + /// callback. + void runWithPreamble( + PathRef File, + UniqueFunction)> Action); + +private: + const ParseInputs &getInputs(PathRef File); + + llvm::StringMap CachedInputs; + CppFileCollection Files; + ThreadPool Threads; +}; +} // namespace clangd +} // namespace clang + +#endif diff --git a/clang-tools-extra/clangd/Threading.cpp b/clang-tools-extra/clangd/Threading.cpp new file mode 100644 index 000000000000..377c9a05f9fa --- /dev/null +++ b/clang-tools-extra/clangd/Threading.cpp @@ -0,0 +1,61 @@ +#include "Threading.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Threading.h" + +namespace clang { +namespace clangd { +ThreadPool::ThreadPool(unsigned AsyncThreadsCount) + : RunSynchronously(AsyncThreadsCount == 0) { + if (RunSynchronously) { + // Don't start the worker thread if we're running synchronously + return; + } + + Workers.reserve(AsyncThreadsCount); + for (unsigned I = 0; I < AsyncThreadsCount; ++I) { + Workers.push_back(std::thread([this, I]() { + llvm::set_thread_name(llvm::formatv("scheduler/{0}", I)); + while (true) { + UniqueFunction Request; + + // Pick request from the queue + { + std::unique_lock Lock(Mutex); + // Wait for more requests. + RequestCV.wait(Lock, + [this] { return !RequestQueue.empty() || Done; }); + if (RequestQueue.empty()) { + assert(Done); + return; + } + + // We process requests starting from the front of the queue. Users of + // ThreadPool have a way to prioritise their requests by putting + // them to the either side of the queue (using either addToEnd or + // addToFront). + Request = std::move(RequestQueue.front()); + RequestQueue.pop_front(); + } // unlock Mutex + + Request(); + } + })); + } +} + +ThreadPool::~ThreadPool() { + if (RunSynchronously) + return; // no worker thread is running in that case + + { + std::lock_guard Lock(Mutex); + // Wake up the worker thread + Done = true; + } // unlock Mutex + RequestCV.notify_all(); + + for (auto &Worker : Workers) + Worker.join(); +} +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/Threading.h b/clang-tools-extra/clangd/Threading.h new file mode 100644 index 000000000000..7a157b8cb0d0 --- /dev/null +++ b/clang-tools-extra/clangd/Threading.h @@ -0,0 +1,83 @@ +//===--- ThreadPool.h --------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_THREADING_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_THREADING_H + +#include "Function.h" +#include +#include +#include +#include +#include + +namespace clang { +namespace clangd { +/// A simple fixed-size thread pool implementation. +class ThreadPool { +public: + /// If \p AsyncThreadsCount is 0, requests added using addToFront and addToEnd + /// will be processed synchronously on the calling thread. + // Otherwise, \p AsyncThreadsCount threads will be created to schedule the + // requests. + ThreadPool(unsigned AsyncThreadsCount); + /// Destructor blocks until all requests are processed and worker threads are + /// terminated. + ~ThreadPool(); + + /// Add a new request to run function \p F with args \p As to the start of the + /// queue. The request will be run on a separate thread. + template + void addToFront(Func &&F, Args &&... As) { + if (RunSynchronously) { + std::forward(F)(std::forward(As)...); + return; + } + + { + std::lock_guard Lock(Mutex); + RequestQueue.push_front( + BindWithForward(std::forward(F), std::forward(As)...)); + } + RequestCV.notify_one(); + } + + /// Add a new request to run function \p F with args \p As to the end of the + /// queue. The request will be run on a separate thread. + template void addToEnd(Func &&F, Args &&... As) { + if (RunSynchronously) { + std::forward(F)(std::forward(As)...); + return; + } + + { + std::lock_guard Lock(Mutex); + RequestQueue.push_back( + BindWithForward(std::forward(F), std::forward(As)...)); + } + RequestCV.notify_one(); + } + +private: + bool RunSynchronously; + mutable std::mutex Mutex; + /// We run some tasks on separate threads(parsing, CppFile cleanup). + /// These threads looks into RequestQueue to find requests to handle and + /// terminate when Done is set to true. + std::vector Workers; + /// Setting Done to true will make the worker threads terminate. + bool Done = false; + /// A queue of requests. + std::deque> RequestQueue; + /// Condition variable to wake up worker threads. + std::condition_variable RequestCV; +}; +} // namespace clangd +} // namespace clang +#endif diff --git a/clang-tools-extra/unittests/clangd/CMakeLists.txt b/clang-tools-extra/unittests/clangd/CMakeLists.txt index e6edb4f531ff..8f6125e192d1 100644 --- a/clang-tools-extra/unittests/clangd/CMakeLists.txt +++ b/clang-tools-extra/unittests/clangd/CMakeLists.txt @@ -22,6 +22,7 @@ add_extra_unittest(ClangdTests URITests.cpp TestFS.cpp TraceTests.cpp + TUSchedulerTests.cpp SourceCodeTests.cpp SymbolCollectorTests.cpp XRefsTests.cpp diff --git a/clang-tools-extra/unittests/clangd/TUSchedulerTests.cpp b/clang-tools-extra/unittests/clangd/TUSchedulerTests.cpp new file mode 100644 index 000000000000..2cf6ef562a2d --- /dev/null +++ b/clang-tools-extra/unittests/clangd/TUSchedulerTests.cpp @@ -0,0 +1,176 @@ +//===-- TUSchedulerTests.cpp ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TUScheduler.h" +#include "TestFS.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include + +namespace clang { +namespace clangd { + +using ::testing::Pair; +using ::testing::Pointee; + +void ignoreUpdate(Context, llvm::Optional>) {} +void ignoreError(llvm::Error Err) { + handleAllErrors(std::move(Err), [](const llvm::ErrorInfoBase &) {}); +} + +class TUSchedulerTests : public ::testing::Test { +protected: + ParseInputs getInputs(PathRef File, std::string Contents) { + return ParseInputs{*CDB.getCompileCommand(File), buildTestFS(Files), + std::move(Contents)}; + } + + void changeFile(PathRef File, std::string Contents) { + Files[File] = Contents; + } + +private: + llvm::StringMap Files; + MockCompilationDatabase CDB; +}; + +TEST_F(TUSchedulerTests, MissingFiles) { + TUScheduler S(getDefaultAsyncThreadsCount(), + /*StorePreamblesInMemory=*/true, + /*ASTParsedCallback=*/nullptr); + + auto Added = getVirtualTestFilePath("added.cpp"); + changeFile(Added, ""); + + auto Missing = getVirtualTestFilePath("missing.cpp"); + changeFile(Missing, ""); + + S.update(Context::empty(), Added, getInputs(Added, ""), ignoreUpdate); + + // Assert each operation for missing file is an error (even if it's available + // in VFS). + S.runWithAST(Missing, [&](llvm::Expected AST) { + ASSERT_FALSE(bool(AST)); + ignoreError(AST.takeError()); + }); + S.runWithPreamble(Missing, [&](llvm::Expected Preamble) { + ASSERT_FALSE(bool(Preamble)); + ignoreError(Preamble.takeError()); + }); + S.remove(Missing, [&](llvm::Error Err) { + EXPECT_TRUE(bool(Err)); + ignoreError(std::move(Err)); + }); + + // Assert there aren't any errors for added file. + S.runWithAST( + Added, [&](llvm::Expected AST) { EXPECT_TRUE(bool(AST)); }); + S.runWithPreamble(Added, [&](llvm::Expected Preamble) { + EXPECT_TRUE(bool(Preamble)); + }); + S.remove(Added, [&](llvm::Error Err) { EXPECT_FALSE(bool(Err)); }); + + // Assert that all operations fail after removing the file. + S.runWithAST(Added, [&](llvm::Expected AST) { + ASSERT_FALSE(bool(AST)); + ignoreError(AST.takeError()); + }); + S.runWithPreamble(Added, [&](llvm::Expected Preamble) { + ASSERT_FALSE(bool(Preamble)); + ignoreError(Preamble.takeError()); + }); + S.remove(Added, [&](llvm::Error Err) { + EXPECT_TRUE(bool(Err)); + ignoreError(std::move(Err)); + }); +} + +TEST_F(TUSchedulerTests, ManyUpdates) { + const int FilesCount = 3; + const int UpdatesPerFile = 10; + + std::mutex Mut; + int TotalASTReads = 0; + int TotalPreambleReads = 0; + int TotalUpdates = 0; + + // Run TUScheduler and collect some stats. + { + TUScheduler S(getDefaultAsyncThreadsCount(), + /*StorePreamblesInMemory=*/true, + /*ASTParsedCallback=*/nullptr); + + std::vector Files; + for (int I = 0; I < FilesCount; ++I) { + Files.push_back( + getVirtualTestFilePath("foo" + std::to_string(I) + ".cpp").str()); + changeFile(Files.back(), ""); + } + + llvm::StringRef Contents1 = R"cpp(int a;)cpp"; + llvm::StringRef Contents2 = R"cpp(int main() { return 1; })cpp"; + llvm::StringRef Contents3 = + R"cpp(int a; int b; int sum() { return a + b; })cpp"; + + llvm::StringRef AllContents[] = {Contents1, Contents2, Contents3}; + const int AllContentsSize = 3; + + for (int FileI = 0; FileI < FilesCount; ++FileI) { + for (int UpdateI = 0; UpdateI < UpdatesPerFile; ++UpdateI) { + auto Contents = AllContents[(FileI + UpdateI) % AllContentsSize]; + + auto File = Files[FileI]; + auto Inputs = getInputs(File, Contents.str()); + static Key> FileAndUpdateKey; + auto Ctx = Context::empty().derive(FileAndUpdateKey, + std::make_pair(FileI, UpdateI)); + S.update(std::move(Ctx), File, Inputs, + [FileI, UpdateI, &Mut, &TotalUpdates]( + Context Ctx, + llvm::Optional> Diags) { + EXPECT_THAT(Ctx.get(FileAndUpdateKey), + Pointee(Pair(FileI, UpdateI))); + + std::lock_guard Lock(Mut); + ++TotalUpdates; + }); + + S.runWithAST(File, [Inputs, &Mut, + &TotalASTReads](llvm::Expected AST) { + ASSERT_TRUE((bool)AST); + EXPECT_EQ(AST->Inputs.FS, Inputs.FS); + EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents); + + std::lock_guard Lock(Mut); + ++TotalASTReads; + }); + + S.runWithPreamble( + File, [Inputs, &Mut, &TotalPreambleReads]( + llvm::Expected Preamble) { + ASSERT_TRUE((bool)Preamble); + EXPECT_EQ(Preamble->Inputs.FS, Inputs.FS); + EXPECT_EQ(Preamble->Inputs.Contents, Inputs.Contents); + + std::lock_guard Lock(Mut); + ++TotalPreambleReads; + }); + } + } + } // TUScheduler destructor waits for all operations to finish. + + std::lock_guard Lock(Mut); + EXPECT_EQ(TotalUpdates, FilesCount * UpdatesPerFile); + EXPECT_EQ(TotalASTReads, FilesCount * UpdatesPerFile); + EXPECT_EQ(TotalPreambleReads, FilesCount * UpdatesPerFile); +} + +} // namespace clangd +} // namespace clang