diff --git a/clang-tools-extra/clangd/Compiler.cpp b/clang-tools-extra/clangd/Compiler.cpp --- a/clang-tools-extra/clangd/Compiler.cpp +++ b/clang-tools-extra/clangd/Compiler.cpp @@ -41,8 +41,7 @@ } std::unique_ptr -buildCompilerInvocation(const ParseInputs &Inputs, - clang::DiagnosticConsumer &D, +buildCompilerInvocation(const ParseInputs &Inputs, clang::DiagnosticConsumer &D, std::vector *CC1Args) { std::vector ArgStrs; for (const auto &S : Inputs.CompileCommand.CommandLine) diff --git a/clang-tools-extra/clangd/Preamble.h b/clang-tools-extra/clangd/Preamble.h --- a/clang-tools-extra/clangd/Preamble.h +++ b/clang-tools-extra/clangd/Preamble.h @@ -78,13 +78,12 @@ /// 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 -buildPreamble(PathRef FileName, CompilerInvocation &CI, +buildPreamble(PathRef FileName, CompilerInvocation CI, std::shared_ptr OldPreamble, const tooling::CompileCommand &OldCompileCommand, const ParseInputs &Inputs, bool StoreInMemory, PreambleParsedCallback PreambleCallback); - } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp --- a/clang-tools-extra/clangd/Preamble.cpp +++ b/clang-tools-extra/clangd/Preamble.cpp @@ -87,7 +87,7 @@ } std::shared_ptr -buildPreamble(PathRef FileName, CompilerInvocation &CI, +buildPreamble(PathRef FileName, CompilerInvocation CI, std::shared_ptr OldPreamble, const tooling::CompileCommand &OldCompileCommand, const ParseInputs &Inputs, bool StoreInMemory, diff --git a/clang-tools-extra/clangd/TUScheduler.cpp b/clang-tools-extra/clangd/TUScheduler.cpp --- a/clang-tools-extra/clangd/TUScheduler.cpp +++ b/clang-tools-extra/clangd/TUScheduler.cpp @@ -49,28 +49,41 @@ #include "GlobalCompilationDatabase.h" #include "Logger.h" #include "ParsedAST.h" +#include "Path.h" #include "Preamble.h" +#include "Threading.h" #include "Trace.h" #include "index/CanonicalIncludes.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/FunctionExtras.h" +#include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" #include "llvm/Support/Path.h" #include "llvm/Support/Threading.h" #include +#include +#include #include #include #include +#include #include +#include +#include namespace clang { namespace clangd { using std::chrono::steady_clock; namespace { -class ASTWorker; +class MainFileWorker; } // namespace static clang::clangd::Key kFileBeingProcessed; @@ -87,7 +100,7 @@ /// Workers borrow ASTs when active, and return them when done. class TUScheduler::ASTCache { public: - using Key = const ASTWorker *; + using Key = const MainFileWorker *; ASTCache(unsigned MaxRetainedASTs) : MaxRetainedASTs(MaxRetainedASTs) {} @@ -149,363 +162,389 @@ }; namespace { -class ASTWorkerHandle; - -/// Owns one instance of the AST, schedules updates and reads of it. -/// Also responsible for building and providing access to the preamble. -/// Each ASTWorker processes the async requests sent to it on a separate -/// dedicated thread. -/// The ASTWorker that manages the AST is shared by both the processing thread -/// and the TUScheduler. The TUScheduler should discard an ASTWorker when -/// remove() is called, but its thread may be busy and we don't want to block. -/// So the workers are accessed via an ASTWorkerHandle. Destroying the handle -/// signals the worker to exit its run loop and gives up shared ownership of the -/// worker. -class ASTWorker { - friend class ASTWorkerHandle; - ASTWorker(PathRef FileName, const GlobalCompilationDatabase &CDB, - TUScheduler::ASTCache &LRUCache, Semaphore &Barrier, bool RunSync, - DebouncePolicy UpdateDebounce, bool StorePreamblesInMemory, - ParsingCallbacks &Callbacks); +/// Common base class for MainFile and Preamble workers. Holds information +/// needed for both and supports triggering a stop. +class StoppableTUWorker { public: - /// Create a new ASTWorker and return a handle to it. - /// The processing thread is spawned using \p Tasks. However, when \p Tasks - /// is null, all requests will be processed on the calling thread - /// synchronously instead. \p Barrier is acquired when processing each - /// request, it is used to limit the number of actively running threads. - static ASTWorkerHandle - create(PathRef FileName, const GlobalCompilationDatabase &CDB, - TUScheduler::ASTCache &IdleASTs, AsyncTaskRunner *Tasks, - Semaphore &Barrier, DebouncePolicy UpdateDebounce, - bool StorePreamblesInMemory, ParsingCallbacks &Callbacks); - ~ASTWorker(); - - void update(ParseInputs Inputs, WantDiagnostics); - void - runWithAST(llvm::StringRef Name, - llvm::unique_function)> Action, - TUScheduler::ASTActionInvalidation); - bool blockUntilIdle(Deadline Timeout) const; - - std::shared_ptr getPossiblyStalePreamble() const; - - /// Obtain a preamble reflecting all updates so far. Threadsafe. - /// It may be delivered immediately, or later on the worker thread. - void getCurrentPreamble( - llvm::unique_function)>); - /// Returns compile command from the current file inputs. - tooling::CompileCommand getCurrentCompileCommand() const; + StoppableTUWorker(ParsingCallbacks &Callbacks, PathRef FileName, bool RunSync) + : Callbacks(Callbacks), FileName(FileName), RunSync(RunSync), + Done(false) {} + bool isDone() { return Done.load(); } + /// Must be called exactly once on processing thread. Should return in a + /// bounded time after stop() is called on a separate thread. + virtual void run() = 0; + virtual void stop() { + assert(!Done.load() && "Already stopped"); + Done.store(true); + } + virtual ~StoppableTUWorker() {} - /// Wait for the first build of preamble to finish. Preamble itself can be - /// accessed via getPossiblyStalePreamble(). Note that this function will - /// return after an unsuccessful build of the preamble too, i.e. result of - /// getPossiblyStalePreamble() can be null even after this function returns. - void waitForFirstPreamble() const; + virtual size_t getUsedBytes() const = 0; - std::size_t getUsedBytes() const; - bool isASTCached() const; + /// Should only be used by tests. Blocks until deadline is reached or worker + /// went idle. Returns true for the latter. + virtual bool blockUntilIdle(Deadline Timeout) const = 0; -private: - // Must be called exactly once on processing thread. Will return after - // stop() is called on a separate thread and all pending requests are - // processed. - void run(); - /// Signal that run() should finish processing pending requests and exit. - void stop(); - /// Adds a new task to the end of the request queue. - void startTask(llvm::StringRef Name, llvm::unique_function Task, - llvm::Optional UpdateType, - TUScheduler::ASTActionInvalidation); +protected: /// Updates the TUStatus and emits it. Only called in the worker thread. - void emitTUStatus(TUAction FAction, - const TUStatus::BuildDetails *Detail = nullptr); - - /// Determines the next action to perform. - /// All actions that should never run are discarded. - /// Returns a deadline for the next action. If it's expired, run now. - /// scheduleLocked() is called again at the deadline, or if requests arrive. - Deadline scheduleLocked(); - /// Should the first task in the queue be skipped instead of run? - bool shouldSkipHeadLocked() const; - /// This is private because `FileInputs.FS` is not thread-safe and thus not - /// safe to share. Callers should make sure not to expose `FS` via a public - /// interface. - std::shared_ptr getCurrentFileInputs() const; - - struct Request { - llvm::unique_function Action; - std::string Name; - steady_clock::time_point AddTime; - Context Ctx; - llvm::Optional UpdateType; - TUScheduler::ASTActionInvalidation InvalidationPolicy; - Canceler Invalidate; - }; + void emitTUStatus(TUAction Action, + const TUStatus::BuildDetails *Details = nullptr) { + TUStatus Status({std::move(Action), {}}); + if (Details) + Status.Details = *Details; + // Do not emit TU statuses when the ASTWorker is shutting down. + if (!isDone()) + Callbacks.onFileUpdated(FileName, Status); + } - /// Handles retention of ASTs. - TUScheduler::ASTCache &IdleASTs; - const bool RunSync; - /// Time to wait after an update to see whether another update obsoletes it. - const DebouncePolicy UpdateDebounce; - /// File that ASTWorker is responsible for. - const Path FileName; - const GlobalCompilationDatabase &CDB; - /// Whether to keep the built preambles in memory or on disk. - const bool StorePreambleInMemory; - /// Callback invoked when preamble or main file AST is built. + /// Callbacks invoked to report state of the worker. ParsingCallbacks &Callbacks; - /// Only accessed by the worker thread. - TUStatus Status; + /// Root of the translation unit. + llvm::StringRef FileName; + const bool RunSync; - Semaphore &Barrier; - /// Whether the 'onMainAST' callback ran for the current FileInputs. - bool RanASTCallback = false; - /// Guards members used by both TUScheduler and the worker thread. - mutable std::mutex Mutex; - /// File inputs, currently being used by the worker. - /// Inputs are written and read by the worker thread, compile command can also - /// be consumed by clients of ASTWorker. - std::shared_ptr FileInputs; /* GUARDED_BY(Mutex) */ - std::shared_ptr LastBuiltPreamble; /* GUARDED_BY(Mutex) */ - /// Times of recent AST rebuilds, used for UpdateDebounce computation. - llvm::SmallVector - RebuildTimes; /* GUARDED_BY(Mutex) */ - /// Becomes ready when the first preamble build finishes. - Notification PreambleWasBuilt; - /// Set to true to signal run() to finish processing. - bool Done; /* GUARDED_BY(Mutex) */ - std::deque Requests; /* GUARDED_BY(Mutex) */ - llvm::Optional CurrentRequest; /* GUARDED_BY(Mutex) */ - mutable std::condition_variable RequestsCV; - /// Guards the callback that publishes results of AST-related computations - /// (diagnostics, highlightings) and file statuses. - std::mutex PublishMu; - // Used to prevent remove document + add document races that lead to - // out-of-order callbacks for publishing results of onMainAST callback. - // - // The lifetime of the old/new ASTWorkers will overlap, but their handles - // don't. When the old handle is destroyed, the old worker will stop reporting - // any results to the user. - bool CanPublishResults = true; /* GUARDED_BY(PublishMu) */ +private: + std::atomic Done; }; -/// A smart-pointer-like class that points to an active ASTWorker. -/// In destructor, signals to the underlying ASTWorker that no new requests will -/// be sent and the processing loop may exit (after running all pending -/// requests). -class ASTWorkerHandle { - friend class ASTWorker; - ASTWorkerHandle(std::shared_ptr Worker) - : Worker(std::move(Worker)) { - assert(this->Worker); +/// Responsible for building and providing access to the preamble of a TU. This +/// worker doesn't guarantee that each preamble request will be built, in case +/// of multiple preamble requests, it will only build the latest one. Once stop +/// is called it will build any remaining request and will exit the run loop. +class PreambleWorker : public StoppableTUWorker { +public: + PreambleWorker(ParsingCallbacks &Callbacks, PathRef FileName, + bool StorePreambleInMemory, bool RunSync) + : StoppableTUWorker(Callbacks, FileName, RunSync), + StorePreambleInMemory(StorePreambleInMemory) {} + + size_t getUsedBytes() const override { + auto Preamble = getLatestBuiltPreamble(); + return Preamble ? Preamble->Preamble.getSize() : 0; } -public: - ASTWorkerHandle(const ASTWorkerHandle &) = delete; - ASTWorkerHandle &operator=(const ASTWorkerHandle &) = delete; - ASTWorkerHandle(ASTWorkerHandle &&) = default; - ASTWorkerHandle &operator=(ASTWorkerHandle &&) = default; + void requestBuild(CompilerInvocation *CI, ParseInputs PI) { + assert(!isDone() && "Build request to PreambleWorker after stop"); + // If compiler invocation was broken, just fail out early. + if (!CI) { + TUStatus::BuildDetails Details; + Details.BuildFailed = true; + std::string TaskName = llvm::formatv("Update ({0})", PI.Version); + emitTUStatus({TUAction::BuildingPreamble, std::move(TaskName)}, &Details); + // Make sure anyone waiting for the preamble gets notified it could not be + // built. + BuiltFirstPreamble.notify(); + return; + } + // Make possibly expensive copy while not holding the lock. + Request Req = {std::make_unique(*CI), std::move(PI)}; + if (RunSync) { + updateLatestBuiltPreamble(buildPreamble(std::move(Req))); + return; + } + { + std::lock_guard Lock(Mutex); + PreambleReq = std::move(Req); + } + // Let the worker thread know there's a request, notify_one is safe as there + // should be a single worker thread waiting on it. + PreambleRequested.notify_one(); + } - ~ASTWorkerHandle() { - if (Worker) - Worker->stop(); + /// Blocks until at least a single request has been processed. Note that it + /// will unblock even after an unsuccessful build. + void waitForFirstPreamble() const { BuiltFirstPreamble.wait(); } + + /// Returns the latest built preamble, might be null if no preamble has been + /// built or latest attempt resulted in a failure. + std::shared_ptr getLatestBuiltPreamble() const { + std::lock_guard Lock(Mutex); + return LastBuiltPreamble; } - ASTWorker &operator*() { - assert(Worker && "Handle was moved from"); - return *Worker; + void run() override { + dlog("Starting preamble worker for {0}", FileName); + while (true) { + { + std::unique_lock Lock(Mutex); + assert(!CurrentReq && "Already processing a request?"); + // Wait until stop is called or there is a request. + PreambleRequested.wait(Lock, + [this] { return PreambleReq || isDone(); }); + // No request means we are done. + if (!PreambleReq) + break; + CurrentReq = std::move(*PreambleReq); + // Reset it here, before we build the request to not overwrite any new + // request that might arrive while we are still building this one. + PreambleReq.reset(); + } + // Build the preamble and let the waiters know about it. + updateLatestBuiltPreamble(buildPreamble(std::move(*CurrentReq))); + } + // We are no longer going to build any preambles, let the waiters know that. + BuiltFirstPreamble.notify(); + dlog("Preamble worker for {0} finished", FileName); } - ASTWorker *operator->() { - assert(Worker && "Handle was moved from"); - return Worker.get(); + virtual void stop() override { + dlog("Stopping preamble worker for {0}", FileName); + StoppableTUWorker::stop(); + // Let the worker thread know we've been stopped. notify_one is safe as + // there should be only a single worker. + PreambleRequested.notify_one(); } - /// Returns an owning reference to the underlying ASTWorker that can outlive - /// the ASTWorkerHandle. However, no new requests to an active ASTWorker can - /// be schedule via the returned reference, i.e. only reads of the preamble - /// are possible. - std::shared_ptr lock() { return Worker; } + bool blockUntilIdle(Deadline Timeout) const override { + std::unique_lock Lock(Mutex); + return wait(Lock, PreambleBuilt, Timeout, + [&] { return !PreambleReq && !CurrentReq; }); + } private: - std::shared_ptr Worker; + /// Holds inputs required for building a preamble, CI is guaranteed to be + /// non-null. + struct Request { + std::unique_ptr CI; + ParseInputs Inputs; + }; + + /// Caches the latest built preamble and singals waiters about the built. + /// FIXME: We shouldn't cache failed preambles, if we've got a successful + /// build before. + void updateLatestBuiltPreamble(std::shared_ptr Preamble) { + { + std::lock_guard Lock(Mutex); + LastBuiltPreamble = std::move(Preamble); + CurrentReq.reset(); + } + BuiltFirstPreamble.notify(); + PreambleBuilt.notify_all(); + } + + /// Builds a preamble for Req, might re-use the latest built preamble if it is + /// valid for Req. + std::shared_ptr buildPreamble(Request Req) { + assert(Req.CI && "Got preamble request with null compiler invocation"); + const ParseInputs &Inputs = Req.Inputs; + std::shared_ptr OldPreamble = + Inputs.ForceRebuild ? std::shared_ptr() + : getLatestBuiltPreamble(); + // FIXME: We should rather use the compile command in the OldPreamble, but + // currently it is not populated. + const tooling::CompileCommand OldCmd = LastCmd; + LastCmd = Inputs.CompileCommand; + + std::string TaskName = llvm::formatv("Update ({0})", Inputs.Version); + emitTUStatus({TUAction::BuildingPreamble, std::move(TaskName)}); + + return clang::clangd::buildPreamble( + FileName, std::move(*Req.CI), OldPreamble, OldCmd, Inputs, + StorePreambleInMemory, + [this, Version(Inputs.Version)]( + ASTContext &Ctx, std::shared_ptr PP, + const CanonicalIncludes &CanonIncludes) { + Callbacks.onPreambleAST(FileName, Version, Ctx, std::move(PP), + CanonIncludes); + }); + } + + mutable std::mutex Mutex; + llvm::Optional PreambleReq; + // Written by only worker thread, might be read by multiple threads through + // BlockUntilIdle. + llvm::Optional CurrentReq; + // Signaled whenever a thread populates PreambleReq. + mutable std::condition_variable PreambleRequested; + // Signaled whenever a LastBuiltPreamble is replaced. + mutable std::condition_variable PreambleBuilt; + std::shared_ptr LastBuiltPreamble; + + Notification BuiltFirstPreamble; + + // Only used by current worker thread, so no data races. + tooling::CompileCommand LastCmd; + const bool StorePreambleInMemory; }; -ASTWorkerHandle -ASTWorker::create(PathRef FileName, const GlobalCompilationDatabase &CDB, - TUScheduler::ASTCache &IdleASTs, AsyncTaskRunner *Tasks, - Semaphore &Barrier, DebouncePolicy UpdateDebounce, - bool StorePreamblesInMemory, ParsingCallbacks &Callbacks) { - std::shared_ptr Worker( - new ASTWorker(FileName, CDB, IdleASTs, Barrier, /*RunSync=*/!Tasks, - UpdateDebounce, StorePreamblesInMemory, Callbacks)); - if (Tasks) - Tasks->runAsync("worker:" + llvm::sys::path::filename(FileName), - [Worker]() { Worker->run(); }); - - return ASTWorkerHandle(std::move(Worker)); -} +/// Responsible for building and providing access to the main file AST of a TU. +/// It serializes build requests and makes sure any access will happen after the +/// preceding build succeeds. It might discard build requests that doesn't have +/// any depending reads, or read requests that should be invalidated after a +/// write. Once stop is called it will serve any remaining requests and will +/// exit the run loop. +class MainFileWorker : public StoppableTUWorker { +public: + MainFileWorker(PathRef FileName, TUScheduler::ASTCache &LRUCache, + Semaphore &Barrier, bool RunSync, + DebouncePolicy UpdateDebounce, ParsingCallbacks &Callbacks) + : StoppableTUWorker(Callbacks, FileName, RunSync), Barrier(Barrier), + UpdateDebounce(UpdateDebounce), IdleASTs(LRUCache) {} + + size_t getUsedBytes() const override { return IdleASTs.getUsedBytes(this); } + bool isASTCached() const { return IdleASTs.getUsedBytes(this) != 0; } + + // Must be called exactly once on processing thread. Will return after stop() + // is called on a separate thread and all pending requests are processed. + void run() override { + dlog("Starting mainfile worker for {0}", FileName); + while (true) { + { + std::unique_lock Lock(Mutex); + assert(!CurrentRequest && "A task is running, multiple workers?"); + for (auto Wait = scheduleLocked(); !Wait.expired(); + Wait = scheduleLocked()) { + if (isDone()) { + if (Requests.empty()) + return; + // Even though Done is set, finish pending requests. However, skip + // delays to shutdown fast. + break; + } -ASTWorker::ASTWorker(PathRef FileName, const GlobalCompilationDatabase &CDB, - TUScheduler::ASTCache &LRUCache, Semaphore &Barrier, - bool RunSync, DebouncePolicy UpdateDebounce, - bool StorePreamblesInMemory, ParsingCallbacks &Callbacks) - : IdleASTs(LRUCache), RunSync(RunSync), UpdateDebounce(UpdateDebounce), - FileName(FileName), CDB(CDB), - StorePreambleInMemory(StorePreamblesInMemory), - Callbacks(Callbacks), Status{TUAction(TUAction::Idle, ""), - TUStatus::BuildDetails()}, - Barrier(Barrier), Done(false) { - auto Inputs = std::make_shared(); - // Set a fallback command because compile command can be accessed before - // `Inputs` is initialized. Other fields are only used after initialization - // from client inputs. - Inputs->CompileCommand = CDB.getFallbackCommand(FileName); - FileInputs = std::move(Inputs); -} + // Tracing: we have a next request, attribute this sleep to it. + llvm::Optional Ctx; + llvm::Optional Tracer; + if (!Requests.empty()) { + Ctx.emplace(Requests.front().Ctx.clone()); + Tracer.emplace("Debounce"); + SPAN_ATTACH(*Tracer, "next_request", Requests.front().Name); + if (!(Wait == Deadline::infinity())) { + emitTUStatus({TUAction::Queued, Requests.front().Name}); + SPAN_ATTACH(*Tracer, "sleep_ms", + std::chrono::duration_cast( + Wait.time() - steady_clock::now()) + .count()); + } + } -ASTWorker::~ASTWorker() { - // Make sure we remove the cached AST, if any. - IdleASTs.take(this); -#ifndef NDEBUG - std::lock_guard Lock(Mutex); - assert(Done && "handle was not destroyed"); - assert(Requests.empty() && !CurrentRequest && - "unprocessed requests when destroying ASTWorker"); -#endif -} + wait(Lock, RequestsCV, Wait); + } + CurrentRequest = std::move(Requests.front()); + Requests.pop_front(); + } // unlock Mutex -void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) { - std::string TaskName = llvm::formatv("Update ({0})", Inputs.Version); - auto Task = [=]() mutable { - auto RunPublish = [&](llvm::function_ref Publish) { + // It is safe to perform reads to CurrentRequest without holding the lock + // as only writer is also this thread. + { + std::unique_lock Lock(Barrier, std::try_to_lock); + if (!Lock.owns_lock()) { + emitTUStatus({TUAction::Queued, CurrentRequest->Name}); + Lock.lock(); + } + WithContext Guard(std::move(CurrentRequest->Ctx)); + trace::Span Tracer(CurrentRequest->Name); + emitTUStatus({TUAction::RunningAction, CurrentRequest->Name}); + CurrentRequest->Action(); + } + + bool IsEmpty = false; + { + std::lock_guard Lock(Mutex); + CurrentRequest.reset(); + IsEmpty = Requests.empty(); + } + if (IsEmpty) + emitTUStatus({TUAction::Idle, /*Name=*/""}); + RequestsCV.notify_all(); + } + } + + /// Adds a request to the queue, which will be executed after any requests + /// issued so far. + void dispatchUpdate(llvm::unique_function Update, + WantDiagnostics WantDiags, llvm::StringRef Version) { + std::string TaskName = llvm::formatv("Update ({0})", Version); + addToQueue(TaskName, std::move(Update), std::move(WantDiags), + TUScheduler::NoInvalidation); + } + + /// Builds an AST for provided inputs and invokes the relevant callbacks. + /// Might re-use a cached AST, and won't invoke callbacks if they've already + /// been invoked while building the cached AST. Should only be invoked by + /// worker thread. + void buildAST(std::unique_ptr Invocation, + std::vector CompilerInvocationDiags, ParseInputs Inputs, + WantDiagnostics WantDiags, + std::shared_ptr Preamble) { + std::string TaskName = llvm::formatv("Update ({0})", Inputs.Version); + + auto RunPublish = [this](llvm::function_ref Publish) { // Ensure we only publish results from the worker if the file was not // removed, making sure there are not race conditions. - std::lock_guard Lock(PublishMu); - if (CanPublishResults) + if (!isDone()) Publish(); }; - - // Get the actual command as `Inputs` does not have a command. - // FIXME: some build systems like Bazel will take time to preparing - // environment to build the file, it would be nice if we could emit a - // "PreparingBuild" status to inform users, it is non-trivial given the - // current implementation. - if (auto Cmd = CDB.getCompileCommand(FileName)) - Inputs.CompileCommand = *Cmd; - else - // FIXME: consider using old command if it's not a fallback one. - Inputs.CompileCommand = CDB.getFallbackCommand(FileName); - auto PrevInputs = getCurrentFileInputs(); + auto PrevInputs = getCurrentInputs(); // Will be used to check if we can avoid rebuilding the AST. - bool InputsAreTheSame = - std::tie(PrevInputs->CompileCommand, PrevInputs->Contents) == + const bool InputsAreTheSame = + std::tie(PrevInputs.CompileCommand, PrevInputs.Contents) == std::tie(Inputs.CompileCommand, Inputs.Contents); - tooling::CompileCommand OldCommand = PrevInputs->CompileCommand; bool RanCallbackForPrevInputs = RanASTCallback; - { - std::lock_guard Lock(Mutex); - FileInputs = std::make_shared(Inputs); - } + setCurrentInputs(Inputs); RanASTCallback = false; - emitTUStatus({TUAction::BuildingPreamble, TaskName}); - log("ASTWorker building file {0} version {1} with command {2}\n[{3}]\n{4}", + + log("ASTWorker building file {0} version {1} with command " + "{2}\n[{3}]\n{4}", FileName, Inputs.Version, Inputs.CompileCommand.Heuristic, Inputs.CompileCommand.Directory, llvm::join(Inputs.CompileCommand.CommandLine, " ")); - // Rebuild the preamble and the AST. - StoreDiags CompilerInvocationDiagConsumer; - std::vector CC1Args; - std::unique_ptr Invocation = buildCompilerInvocation( - Inputs, CompilerInvocationDiagConsumer, &CC1Args); - // Log cc1 args even (especially!) if creating invocation failed. - if (!CC1Args.empty()) - vlog("Driver produced command: cc1 {0}", llvm::join(CC1Args, " ")); - std::vector CompilerInvocationDiags = - CompilerInvocationDiagConsumer.take(); + if (!Invocation) { - elog("Could not build CompilerInvocation for file {0}", FileName); - // Remove the old AST if it's still in cache. IdleASTs.take(this); - TUStatus::BuildDetails Details; - Details.BuildFailed = true; - emitTUStatus({TUAction::BuildingPreamble, TaskName}, &Details); // Report the diagnostics we collected when parsing the command line. Callbacks.onFailedAST(FileName, Inputs.Version, std::move(CompilerInvocationDiags), RunPublish); - // Make sure anyone waiting for the preamble gets notified it could not - // be built. - PreambleWasBuilt.notify(); return; } - std::shared_ptr OldPreamble = - Inputs.ForceRebuild ? std::shared_ptr() - : getPossiblyStalePreamble(); - std::shared_ptr NewPreamble = buildPreamble( - FileName, *Invocation, OldPreamble, OldCommand, Inputs, - StorePreambleInMemory, - [this, Version(Inputs.Version)]( - ASTContext &Ctx, std::shared_ptr PP, - const CanonicalIncludes &CanonIncludes) { - Callbacks.onPreambleAST(FileName, Version, Ctx, std::move(PP), - CanonIncludes); - }); - - bool CanReuseAST = InputsAreTheSame && (OldPreamble == NewPreamble); - { - std::lock_guard Lock(Mutex); - LastBuiltPreamble = NewPreamble; - } - // Before doing the expensive AST reparse, we want to release our reference - // to the old preamble, so it can be freed if there are no other references - // to it. - OldPreamble.reset(); - PreambleWasBuilt.notify(); + bool CanReuseAST = InputsAreTheSame && (CurrentPreamble == Preamble); + // Before doing the expensive AST reparse, we want to release our + // reference to the old preamble, so it can be freed if there are no other + // references to it. + CurrentPreamble = std::move(Preamble); emitTUStatus({TUAction::BuildingFile, TaskName}); - if (!CanReuseAST) { - IdleASTs.take(this); // Remove the old AST if it's still in cache. - } else { - // We don't need to rebuild the AST, check if we need to run the callback. - if (RanCallbackForPrevInputs) { - RanASTCallback = true; - // Take a shortcut and don't report the diagnostics, since they should - // not changed. All the clients should handle the lack of OnUpdated() - // call anyway to handle empty result from buildAST. - // FIXME(ibiryukov): the AST could actually change if non-preamble - // includes changed, but we choose to ignore it. - // FIXME(ibiryukov): should we refresh the cache in IdleASTs for the - // current file at this point? - log("Skipping rebuild of the AST for {0}, inputs are the same.", - FileName); - TUStatus::BuildDetails Details; - Details.ReuseAST = true; - emitTUStatus({TUAction::BuildingFile, TaskName}, &Details); - return; - } + // We don't need to rebuild the AST, check if we need to run the + // callback. + if (CanReuseAST && RanCallbackForPrevInputs) { + RanASTCallback = true; + // Take a shortcut and don't report the diagnostics, since they should + // not changed. All the clients should handle the lack of OnUpdated() + // call anyway to handle empty result from buildAST. + // FIXME(ibiryukov): the AST could actually change if non-preamble + // includes changed, but we choose to ignore it. + // FIXME(ibiryukov): should we refresh the cache in IdleASTs for the + // current file at this point? + log("Skipping rebuild of the AST for {0}, inputs are the same.", + FileName); + TUStatus::BuildDetails Details; + Details.ReuseAST = true; + emitTUStatus({TUAction::BuildingFile, TaskName}, &Details); + return; } + // We either need a new AST or haven't published diags yet. + // Remove the old AST if we need to build a new one. + if (!CanReuseAST) + IdleASTs.take(this); // We only need to build the AST if diagnostics were requested. if (WantDiags == WantDiagnostics::No) return; - { - std::lock_guard Lock(PublishMu); - // No need to rebuild the AST if we won't send the diagnostics. However, - // note that we don't prevent preamble rebuilds. - if (!CanPublishResults) - return; - } + // Don't send the diagnostics if worker has stopped, as there might be a new + // worker and we shouldn't race with it. + if (isDone()) + return; // Get the AST for diagnostics. llvm::Optional> AST = IdleASTs.take(this); auto RebuildStartTime = DebouncePolicy::clock::now(); if (!AST) { - llvm::Optional NewAST = - buildAST(FileName, std::move(Invocation), CompilerInvocationDiags, - Inputs, NewPreamble); + llvm::Optional NewAST = clang::clangd::buildAST( + FileName, std::move(Invocation), CompilerInvocationDiags, Inputs, + CurrentPreamble); AST = NewAST ? std::make_unique(std::move(*NewAST)) : nullptr; if (!(*AST)) { // buildAST fails. TUStatus::BuildDetails Details; @@ -525,12 +564,14 @@ // Note *AST can still be null if buildAST fails. if (*AST) { { - // Try to record the AST-build time, to inform future update debouncing. - // This is best-effort only: if the lock is held, don't bother. + // Try to record the AST-build time, to inform future update + // debouncing. This is best-effort only: if the lock is held, don't + // bother. auto RebuildDuration = DebouncePolicy::clock::now() - RebuildStartTime; std::unique_lock Lock(Mutex, std::try_to_lock); if (Lock.owns_lock()) { - // Do not let RebuildTimes grow beyond its small-size (i.e. capacity). + // Do not let RebuildTimes grow beyond its small-size (i.e. + // capacity). if (RebuildTimes.size() == RebuildTimes.capacity()) RebuildTimes.erase(RebuildTimes.begin()); RebuildTimes.push_back(RebuildDuration); @@ -550,307 +591,474 @@ } // Stash the AST in the cache for further use. IdleASTs.put(this, std::move(*AST)); - }; - startTask(TaskName, std::move(Task), WantDiags, TUScheduler::NoInvalidation); -} + } -void ASTWorker::runWithAST( - llvm::StringRef Name, - llvm::unique_function)> Action, - TUScheduler::ASTActionInvalidation Invalidation) { - auto Task = [=, Action = std::move(Action)]() mutable { - if (isCancelled()) - return Action(llvm::make_error()); - llvm::Optional> AST = IdleASTs.take(this); - auto CurrentInputs = getCurrentFileInputs(); - if (!AST) { - StoreDiags CompilerInvocationDiagConsumer; - std::unique_ptr Invocation = buildCompilerInvocation( - *CurrentInputs, CompilerInvocationDiagConsumer); - // Try rebuilding the AST. - vlog("ASTWorker rebuilding evicted AST to run {0}: {1} version {2}", Name, - FileName, CurrentInputs->Version); - llvm::Optional NewAST = - Invocation - ? buildAST(FileName, - std::make_unique(*Invocation), - CompilerInvocationDiagConsumer.take(), *CurrentInputs, - getPossiblyStalePreamble()) - : None; - AST = NewAST ? std::make_unique(std::move(*NewAST)) : nullptr; + void stop() override { + dlog("Stopping mainfile worker for {0}", FileName); + StoppableTUWorker::stop(); + // Let the worker thread know we've stopped. We can't use notify_one, as + // there might be other threads calling blockUntilIdle. + RequestsCV.notify_all(); + } + + bool blockUntilIdle(Deadline Timeout) const override { + std::unique_lock Lock(Mutex); + return wait(Lock, RequestsCV, Timeout, + [&] { return Requests.empty() && !CurrentRequest; }); + } + + ~MainFileWorker() { + // Make sure we remove the cached AST, if any. + IdleASTs.take(this); +#ifndef NDEBUG + std::lock_guard Lock(Mutex); + assert(isDone() && "handle was not destroyed"); + assert(Requests.empty() && !CurrentRequest && + "unprocessed requests when destroying ASTWorker"); +#endif + } + + /// Adds a read request to the queue. Might end up building the AST if it is + /// evicted from the cache. + /// GetPreamble is used to fetch the latest build preamble while executing + /// this action, rather than using the latest build preamble while issuing the + /// request. + void + runAction(llvm::StringRef Name, + llvm::unique_function)> Action, + TUScheduler::ASTActionInvalidation Invalidation, + llvm::unique_function()> + GetPreamble) { + auto Task = [Action = std::move(Action), + GetPreamble = std::move(GetPreamble), Name, this]() mutable { + if (isCancelled()) + return Action(llvm::make_error()); + auto CurrentInputs = getCurrentInputs(); + auto AST = IdleASTs.take(this); + if (!AST) { + StoreDiags CompilerInvocationDiagConsumer; + std::unique_ptr Invocation = + buildCompilerInvocation(CurrentInputs, + CompilerInvocationDiagConsumer); + // Try rebuilding the AST. + vlog("ASTWorker rebuilding evicted AST to run {0}: {1} version {2}", + Name, FileName, CurrentInputs.Version); + /// FIXME: We should be updating CurrentInputs and Preamble here. + llvm::Optional NewAST = + Invocation ? clang::clangd::buildAST( + FileName, + std::make_unique(*Invocation), + CompilerInvocationDiagConsumer.take(), + CurrentInputs, GetPreamble()) + : None; + AST = + NewAST ? std::make_unique(std::move(*NewAST)) : nullptr; + } + // 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( + "invalid AST", llvm::errc::invalid_argument)); + vlog("ASTWorker running {0} on version {2} of {1}", Name, FileName, + CurrentInputs.Version); + Action(InputsAndAST{CurrentInputs, **AST}); + }; + addToQueue(Name, std::move(Task), /*UpdateType=*/llvm::None, Invalidation); + } + +private: + /// These two methods should only be accessed inside a request, otherwise + /// there will be a race. + const ParseInputs &getCurrentInputs() const { return CurrentInputs; } + void setCurrentInputs(const ParseInputs &PI) { CurrentInputs = PI; } + + /// Puts a request to the end of the queue. Can invalidate existing requests + /// depending on their policy. + void addToQueue(llvm::StringRef Name, llvm::unique_function Task, + llvm::Optional UpdateType, + TUScheduler::ASTActionInvalidation Invalidation) { + assert(!isDone() && "running a task after stop()"); + if (RunSync) { + trace::Span Tracer(Name + ":" + llvm::sys::path::filename(FileName)); + Task(); + return; } - // 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( - "invalid AST", llvm::errc::invalid_argument)); - vlog("ASTWorker running {0} on version {2} of {1}", Name, FileName, - CurrentInputs->Version); - Action(InputsAndAST{*CurrentInputs, **AST}); - }; - startTask(Name, std::move(Task), /*UpdateType=*/None, Invalidation); -} -std::shared_ptr -ASTWorker::getPossiblyStalePreamble() const { - std::lock_guard Lock(Mutex); - return LastBuiltPreamble; -} + { + std::lock_guard Lock(Mutex); + // Cancel any requests invalidated by this request. + if (UpdateType) { + for (auto &R : llvm::reverse(Requests)) { + if (R.InvalidationPolicy == TUScheduler::InvalidateOnUpdate) + R.Invalidate(); + // Older requests were already invalidated by the older update. + if (R.UpdateType) + break; + } + } -void ASTWorker::getCurrentPreamble( - llvm::unique_function)> Callback) { - // We could just call startTask() to throw the read on the queue, knowing - // it will run after any updates. But we know this task is cheap, so to - // improve latency we cheat: insert it on the queue after the last update. - std::unique_lock Lock(Mutex); - auto LastUpdate = - std::find_if(Requests.rbegin(), Requests.rend(), - [](const Request &R) { return R.UpdateType.hasValue(); }); - // If there were no writes in the queue, and CurrentRequest is not a write, - // the preamble is ready now. - if (LastUpdate == Requests.rend() && - (!CurrentRequest || CurrentRequest->UpdateType.hasValue())) { - Lock.unlock(); - return Callback(getPossiblyStalePreamble()); + Context Ctx = + Context::current().derive(kFileBeingProcessed, FileName.str()); + // Allow this request to be cancelled if invalidated. + Canceler Invalidate = nullptr; + if (Invalidation) { + WithContext WC(std::move(Ctx)); + std::tie(Ctx, Invalidate) = cancelableTask(); + } + Requests.push_back({std::move(Task), std::string(Name), + steady_clock::now(), std::move(Ctx), UpdateType, + Invalidation, std::move(Invalidate)}); + } + RequestsCV.notify_all(); } - assert(!RunSync && "Running synchronously, but queue is non-empty!"); - Requests.insert(LastUpdate.base(), - Request{[Callback = std::move(Callback), this]() mutable { - Callback(getPossiblyStalePreamble()); - }, - "GetPreamble", steady_clock::now(), - Context::current().clone(), - /*UpdateType=*/None, - /*InvalidationPolicy=*/TUScheduler::NoInvalidation, - /*Invalidate=*/nullptr}); - Lock.unlock(); - RequestsCV.notify_all(); -} -void ASTWorker::waitForFirstPreamble() const { PreambleWasBuilt.wait(); } + // Returns true if Requests.front() is a dead update that can be skipped. + bool shouldSkipHeadLocked() const { + assert(!Requests.empty()); + auto Next = Requests.begin(); + auto UpdateType = Next->UpdateType; + if (!UpdateType) // Only skip updates. + return false; + ++Next; + // An update is live if its AST might still be read. + // That is, if it's not immediately followed by another update. + if (Next == Requests.end() || !Next->UpdateType) + return false; + // The other way an update can be live is if its diagnostics might be used. + switch (*UpdateType) { + case WantDiagnostics::Yes: + return false; // Always used. + case WantDiagnostics::No: + return true; // Always dead. + case WantDiagnostics::Auto: + // Used unless followed by an update that generates diagnostics. + for (; Next != Requests.end(); ++Next) + if (Next->UpdateType == WantDiagnostics::Yes || + Next->UpdateType == WantDiagnostics::Auto) + return true; // Prefer later diagnostics. + return false; + } + llvm_unreachable("Unknown WantDiagnostics"); + } -std::shared_ptr ASTWorker::getCurrentFileInputs() const { - std::unique_lock Lock(Mutex); - return FileInputs; -} + /// Determines the next action to perform. + /// All actions that should never run are discarded. + /// Returns a deadline for the next action. If it's expired, run now. + /// scheduleLocked() is called again at the deadline, or if requests arrive. + Deadline scheduleLocked() { + if (Requests.empty()) + return Deadline::infinity(); // Wait for new requests. + // Handle cancelled requests first so the rest of the scheduler doesn't. + for (auto I = Requests.begin(), E = Requests.end(); I != E; ++I) { + if (!isCancelled(I->Ctx)) { + // Cancellations after the first read don't affect current scheduling. + if (I->UpdateType == None) + break; + continue; + } + // Cancelled reads are moved to the front of the queue and run + // immediately. + if (I->UpdateType == None) { + Request R = std::move(*I); + Requests.erase(I); + Requests.push_front(std::move(R)); + return Deadline::zero(); + } + // Cancelled updates are downgraded to auto-diagnostics, and may be + // elided. + if (I->UpdateType == WantDiagnostics::Yes) + I->UpdateType = WantDiagnostics::Auto; + } -tooling::CompileCommand ASTWorker::getCurrentCompileCommand() const { - std::unique_lock Lock(Mutex); - return FileInputs->CompileCommand; -} + while (shouldSkipHeadLocked()) { + vlog("ASTWorker skipping {0} for {1}", Requests.front().Name, FileName); + Requests.pop_front(); + } + assert(!Requests.empty() && "skipped the whole queue"); + // Some updates aren't dead yet, but never end up being used. + // e.g. the first keystroke is live until obsoleted by the second. + // We debounce "maybe-unused" writes, sleeping in case they become dead. + // But don't delay reads (including updates where diagnostics are needed). + for (const auto &R : Requests) + if (R.UpdateType == None || R.UpdateType == WantDiagnostics::Yes) + return Deadline::zero(); + // Front request needs to be debounced, so determine when we're ready. + Deadline D(Requests.front().AddTime + UpdateDebounce.compute(RebuildTimes)); + return D; + } -std::size_t ASTWorker::getUsedBytes() const { - // 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; -} + struct Request { + llvm::unique_function Action; + std::string Name; + steady_clock::time_point AddTime; + Context Ctx; + llvm::Optional UpdateType; + TUScheduler::ASTActionInvalidation InvalidationPolicy; + Canceler Invalidate; + }; + + // Used to limit total number of threads doing any work. + Semaphore &Barrier; + + mutable std::mutex Mutex; + mutable std::condition_variable RequestsCV; + std::deque Requests; + llvm::Optional CurrentRequest; + // FIXME: Following might make sense for preamble worker as well, consider + // moving into StoppableTUWorker once we implement debouncing for preamble + // requests. + const DebouncePolicy UpdateDebounce; + llvm::SmallVector RebuildTimes; + + /// Following fields should only be accessed by worker thread. + /// Latest inputs used for building an AST. + ParseInputs CurrentInputs; + bool RanASTCallback = false; + /// The preamble that was used during latest AST build. + std::shared_ptr CurrentPreamble; + + TUScheduler::ASTCache &IdleASTs; +}; + +class TUWorkerHandle; + +/// Owns one instance of the AST, schedules updates and reads of it. +/// Also responsible for building and providing access to the preamble. +/// Each TUWorker processes the async requests sent to it on a separate +/// dedicated thread. +/// The TUWorker that manages the AST is shared by both the processing thread +/// and the TUScheduler. The TUScheduler should discard a TUWorker when +/// remove() is called, but its thread may be busy and we don't want to block. +/// So the workers are accessed via a TUWorkerHandle. Destroying the handle +/// signals the worker to exit its run loop and gives up shared ownership of the +/// worker. +class TUWorker { + friend class TUWorkerHandle; + TUWorker(PathRef FileName, const GlobalCompilationDatabase &CDB, + TUScheduler::ASTCache &LRUCache, Semaphore &Barrier, bool RunSync, + DebouncePolicy UpdateDebounce, bool StorePreamblesInMemory, + ParsingCallbacks &Callbacks); + +public: + /// Create a new ASTWorker and return a handle to it. + /// The processing thread is spawned using \p Tasks. However, when \p Tasks + /// is null, all requests will be processed on the calling thread + /// synchronously instead. \p Barrier is acquired when processing each + /// request, it is used to limit the number of actively running threads. + static TUWorkerHandle + create(PathRef FileName, const GlobalCompilationDatabase &CDB, + TUScheduler::ASTCache &IdleASTs, AsyncTaskRunner *Tasks, + Semaphore &Barrier, DebouncePolicy UpdateDebounce, + bool StorePreamblesInMemory, ParsingCallbacks &Callbacks); + + /// Issues build of a new preamble and ast with \p Inputs. + void update(ParseInputs Inputs, WantDiagnostics); + /// Issues a read that will happen after any preceding writes. + void + runWithAST(llvm::StringRef Name, + llvm::unique_function)> Action, + TUScheduler::ASTActionInvalidation); + /// Blocks until worker thread goes idle or Timeout is reached, should only be + /// used by tests. Returns true for the former. + bool blockUntilIdle(Deadline Timeout) const; + + /// Blocks until at least one preamble is built. + std::shared_ptr getLatestBuiltPreamble() const; + std::shared_ptr getPossiblyMissingPreamble() const; + + tooling::CompileCommand getCurrentCompileCommand() const; + void setCurrentCompileCommand(tooling::CompileCommand Cmd); + /// Obtain a preamble reflecting all updates so far. Threadsafe. + void getCurrentPreamble( + llvm::unique_function)>); + + std::size_t getUsedBytes() const; + bool isASTCached() const; + +private: + /// Signal that workers should finish processing pending requests and exit. + void stop(); + + /// File that ASTWorker is responsible for. + const Path FileName; + const GlobalCompilationDatabase &CDB; + + mutable std::mutex Mutex; + /// Latest compile command used by any request so far. + // FIXME: Only used by runWithPreamble, should rather come from the preamble + // itself. + tooling::CompileCommand CurrentCompileCmd; -bool ASTWorker::isASTCached() const { return IdleASTs.getUsedBytes(this) != 0; } + PreambleWorker PW; + MainFileWorker MW; +}; -void ASTWorker::stop() { - { - std::lock_guard Lock(PublishMu); - CanPublishResults = false; +/// A smart-pointer-like class that points to an active ASTWorker. +/// In destructor, signals to the underlying ASTWorker that no new requests will +/// be sent and the processing loop may exit (after running all pending +/// requests). +class TUWorkerHandle { + friend class TUWorker; + TUWorkerHandle(std::shared_ptr Worker) : Worker(std::move(Worker)) { + assert(this->Worker); } - { - std::lock_guard Lock(Mutex); - assert(!Done && "stop() called twice"); - Done = true; + +public: + TUWorkerHandle(const TUWorkerHandle &) = delete; + TUWorkerHandle &operator=(const TUWorkerHandle &) = delete; + TUWorkerHandle(TUWorkerHandle &&) = default; + TUWorkerHandle &operator=(TUWorkerHandle &&) = default; + + ~TUWorkerHandle() { + if (Worker) + Worker->stop(); } - RequestsCV.notify_all(); -} -void ASTWorker::startTask(llvm::StringRef Name, - llvm::unique_function Task, - llvm::Optional UpdateType, - TUScheduler::ASTActionInvalidation Invalidation) { - if (RunSync) { - assert(!Done && "running a task after stop()"); - trace::Span Tracer(Name + ":" + llvm::sys::path::filename(FileName)); - Task(); - return; + TUWorker &operator*() { + assert(Worker && "Handle was moved from"); + return *Worker; } - { - std::lock_guard Lock(Mutex); - assert(!Done && "running a task after stop()"); - // Cancel any requests invalidated by this request. - if (UpdateType) { - for (auto &R : llvm::reverse(Requests)) { - if (R.InvalidationPolicy == TUScheduler::InvalidateOnUpdate) - R.Invalidate(); - if (R.UpdateType) - break; // Older requests were already invalidated by the older update. - } - } + TUWorker *operator->() { + assert(Worker && "Handle was moved from"); + return Worker.get(); + } - // Allow this request to be cancelled if invalidated. - Context Ctx = Context::current().derive(kFileBeingProcessed, FileName); - Canceler Invalidate = nullptr; - if (Invalidation) { - WithContext WC(std::move(Ctx)); - std::tie(Ctx, Invalidate) = cancelableTask(); - } - Requests.push_back({std::move(Task), std::string(Name), steady_clock::now(), - std::move(Ctx), UpdateType, Invalidation, - std::move(Invalidate)}); + /// Returns an owning reference to the underlying ASTWorker that can outlive + /// the ASTWorkerHandle. However, no new requests to an active ASTWorker can + /// be schedule via the returned reference, i.e. only reads of the preamble + /// are possible. + std::shared_ptr lock() { return Worker; } + +private: + std::shared_ptr Worker; +}; + +TUWorkerHandle +TUWorker::create(PathRef FileName, const GlobalCompilationDatabase &CDB, + TUScheduler::ASTCache &IdleASTs, AsyncTaskRunner *Tasks, + Semaphore &Barrier, DebouncePolicy UpdateDebounce, + bool StorePreamblesInMemory, ParsingCallbacks &Callbacks) { + std::shared_ptr Worker( + new TUWorker(FileName, CDB, IdleASTs, Barrier, /*RunSync=*/!Tasks, + UpdateDebounce, StorePreamblesInMemory, Callbacks)); + if (Tasks) { + Tasks->runAsync("workerMainFile:" + llvm::sys::path::filename(FileName), + [Worker]() { Worker->MW.run(); }); + Tasks->runAsync("workerPreamble:" + llvm::sys::path::filename(FileName), + [Worker]() { Worker->PW.run(); }); } - RequestsCV.notify_all(); + + return TUWorkerHandle(std::move(Worker)); } -void ASTWorker::emitTUStatus(TUAction Action, - const TUStatus::BuildDetails *Details) { - Status.Action = std::move(Action); - if (Details) - Status.Details = *Details; - std::lock_guard Lock(PublishMu); - // Do not emit TU statuses when the ASTWorker is shutting down. - if (CanPublishResults) { - Callbacks.onFileUpdated(FileName, Status); - } +void TUWorker::getCurrentPreamble( + llvm::unique_function)> Callback) { + // FIXME: This will result in waiting for an extra AST built. + runWithAST( + "getCurrentPreamble", + [Callback = std::move(Callback), + this](llvm::Expected AST) mutable { + if (!AST) + llvm::consumeError(AST.takeError()); + Callback(getPossiblyMissingPreamble()); + }, + TUScheduler::NoInvalidation); } -void ASTWorker::run() { - while (true) { - { - std::unique_lock Lock(Mutex); - assert(!CurrentRequest && "A task is already running, multiple workers?"); - for (auto Wait = scheduleLocked(); !Wait.expired(); - Wait = scheduleLocked()) { - if (Done) { - if (Requests.empty()) - return; - else // Even though Done is set, finish pending requests. - break; // However, skip delays to shutdown fast. - } +std::size_t TUWorker::getUsedBytes() const { + return PW.getUsedBytes() + MW.getUsedBytes(); +} - // Tracing: we have a next request, attribute this sleep to it. - llvm::Optional Ctx; - llvm::Optional Tracer; - if (!Requests.empty()) { - Ctx.emplace(Requests.front().Ctx.clone()); - Tracer.emplace("Debounce"); - SPAN_ATTACH(*Tracer, "next_request", Requests.front().Name); - if (!(Wait == Deadline::infinity())) { - emitTUStatus({TUAction::Queued, Requests.front().Name}); - SPAN_ATTACH(*Tracer, "sleep_ms", - std::chrono::duration_cast( - Wait.time() - steady_clock::now()) - .count()); - } - } +bool TUWorker::isASTCached() const { return MW.isASTCached(); } - wait(Lock, RequestsCV, Wait); - } - CurrentRequest = std::move(Requests.front()); - Requests.pop_front(); - } // unlock Mutex +void TUWorker::setCurrentCompileCommand(tooling::CompileCommand Cmd) { + std::lock_guard Lock(Mutex); + CurrentCompileCmd = std::move(Cmd); +} - // It is safe to perform reads to CurrentRequest without holding the lock as - // only writer is also this thread. - { - std::unique_lock Lock(Barrier, std::try_to_lock); - if (!Lock.owns_lock()) { - emitTUStatus({TUAction::Queued, CurrentRequest->Name}); - Lock.lock(); - } - WithContext Guard(std::move(CurrentRequest->Ctx)); - trace::Span Tracer(CurrentRequest->Name); - emitTUStatus({TUAction::RunningAction, CurrentRequest->Name}); - CurrentRequest->Action(); - } +TUWorker::TUWorker(PathRef FileName, const GlobalCompilationDatabase &CDB, + TUScheduler::ASTCache &LRUCache, Semaphore &Barrier, + bool RunSync, DebouncePolicy UpdateDebounce, + bool StorePreamblesInMemory, ParsingCallbacks &Callbacks) + : FileName(FileName), CDB(CDB), + PW(Callbacks, this->FileName, StorePreamblesInMemory, RunSync), + MW(this->FileName, LRUCache, Barrier, RunSync, UpdateDebounce, + Callbacks) { + // Set a fallback command because compile command can be accessed before + // `Inputs` is initialized. Other fields are only used after initialization + // from client inputs. + setCurrentCompileCommand(CDB.getFallbackCommand(FileName)); +} - bool IsEmpty = false; - { - std::lock_guard Lock(Mutex); - CurrentRequest.reset(); - IsEmpty = Requests.empty(); - } - if (IsEmpty) - emitTUStatus({TUAction::Idle, /*Name*/ ""}); - RequestsCV.notify_all(); - } +void TUWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) { + std::string Version = Inputs.Version; + auto Task = [Inputs = std::move(Inputs), WantDiags, this]() mutable { + // Get the actual command as `Inputs` does not have a command. + // FIXME: some build systems like Bazel will take time to preparing + // environment to build the file, it would be nice if we could emit a + // "PreparingBuild" status to inform users, it is non-trivial given the + // current implementation. + if (auto Cmd = CDB.getCompileCommand(FileName)) + Inputs.CompileCommand = *Cmd; + else + // FIXME: consider using old command if it's not a fallback one. + Inputs.CompileCommand = CDB.getFallbackCommand(FileName); + setCurrentCompileCommand(Inputs.CompileCommand); + StoreDiags CompilerInvocationDiagConsumer; + std::vector CC1Args; + std::unique_ptr Invocation = buildCompilerInvocation( + Inputs, CompilerInvocationDiagConsumer, &CC1Args); + // Log cc1 args even (especially!) if creating invocation failed. + if (!CC1Args.empty()) + vlog("Driver produced command: cc1 {0}", llvm::join(CC1Args, " ")); + std::vector CompilerInvocationDiags = + CompilerInvocationDiagConsumer.take(); + if (!Invocation) + elog("Could not build CompilerInvocation for file {0}", FileName); + PW.requestBuild(Invocation.get(), Inputs); + // FIXME: Enable out-of-order preamble/ast builds. + assert(PW.blockUntilIdle(timeoutSeconds(10))); + MW.buildAST(std::move(Invocation), std::move(CompilerInvocationDiags), + std::move(Inputs), std::move(WantDiags), + PW.getLatestBuiltPreamble()); + }; + MW.dispatchUpdate(std::move(Task), std::move(WantDiags), Version); } -Deadline ASTWorker::scheduleLocked() { - if (Requests.empty()) - return Deadline::infinity(); // Wait for new requests. - // Handle cancelled requests first so the rest of the scheduler doesn't. - for (auto I = Requests.begin(), E = Requests.end(); I != E; ++I) { - if (!isCancelled(I->Ctx)) { - // Cancellations after the first read don't affect current scheduling. - if (I->UpdateType == None) - break; - continue; - } - // Cancelled reads are moved to the front of the queue and run immediately. - if (I->UpdateType == None) { - Request R = std::move(*I); - Requests.erase(I); - Requests.push_front(std::move(R)); - return Deadline::zero(); - } - // Cancelled updates are downgraded to auto-diagnostics, and may be elided. - if (I->UpdateType == WantDiagnostics::Yes) - I->UpdateType = WantDiagnostics::Auto; - } +void TUWorker::runWithAST( + llvm::StringRef Name, + llvm::unique_function)> Action, + TUScheduler::ASTActionInvalidation Invalidation) { + MW.runAction(Name, std::move(Action), Invalidation, + [this] { return PW.getLatestBuiltPreamble(); }); +} - while (shouldSkipHeadLocked()) { - vlog("ASTWorker skipping {0} for {1}", Requests.front().Name, FileName); - Requests.pop_front(); - } - assert(!Requests.empty() && "skipped the whole queue"); - // Some updates aren't dead yet, but never end up being used. - // e.g. the first keystroke is live until obsoleted by the second. - // We debounce "maybe-unused" writes, sleeping in case they become dead. - // But don't delay reads (including updates where diagnostics are needed). - for (const auto &R : Requests) - if (R.UpdateType == None || R.UpdateType == WantDiagnostics::Yes) - return Deadline::zero(); - // Front request needs to be debounced, so determine when we're ready. - Deadline D(Requests.front().AddTime + UpdateDebounce.compute(RebuildTimes)); - return D; +bool TUWorker::blockUntilIdle(Deadline Timeout) const { + return MW.blockUntilIdle(Timeout) && PW.blockUntilIdle(Timeout); } -// Returns true if Requests.front() is a dead update that can be skipped. -bool ASTWorker::shouldSkipHeadLocked() const { - assert(!Requests.empty()); - auto Next = Requests.begin(); - auto UpdateType = Next->UpdateType; - if (!UpdateType) // Only skip updates. - return false; - ++Next; - // An update is live if its AST might still be read. - // That is, if it's not immediately followed by another update. - if (Next == Requests.end() || !Next->UpdateType) - return false; - // The other way an update can be live is if its diagnostics might be used. - switch (*UpdateType) { - case WantDiagnostics::Yes: - return false; // Always used. - case WantDiagnostics::No: - return true; // Always dead. - case WantDiagnostics::Auto: - // Used unless followed by an update that generates diagnostics. - for (; Next != Requests.end(); ++Next) - if (Next->UpdateType == WantDiagnostics::Yes || - Next->UpdateType == WantDiagnostics::Auto) - return true; // Prefer later diagnostics. - return false; - } - llvm_unreachable("Unknown WantDiagnostics"); +std::shared_ptr TUWorker::getLatestBuiltPreamble() const { + PW.waitForFirstPreamble(); + return PW.getLatestBuiltPreamble(); +} + +std::shared_ptr +TUWorker::getPossiblyMissingPreamble() const { + return PW.getLatestBuiltPreamble(); +} + +tooling::CompileCommand TUWorker::getCurrentCompileCommand() const { + std::lock_guard Lock(Mutex); + return CurrentCompileCmd; } -bool ASTWorker::blockUntilIdle(Deadline Timeout) const { - std::unique_lock Lock(Mutex); - return wait(Lock, RequestsCV, Timeout, - [&] { return Requests.empty() && !CurrentRequest; }); +void TUWorker::stop() { + // We want PW to be stopped after MW finishes. + MW.dispatchUpdate([this] { PW.stop(); }, WantDiagnostics::No, ""); + MW.stop(); } // Render a TUAction to a user-facing string representation. @@ -896,7 +1104,7 @@ struct TUScheduler::FileData { /// Latest inputs, passed to TUScheduler::update(). std::string Contents; - ASTWorkerHandle Worker; + TUWorkerHandle Worker; }; TUScheduler::TUScheduler(const GlobalCompilationDatabase &CDB, @@ -920,19 +1128,27 @@ Files.clear(); // Wait for all in-flight tasks to finish. - if (PreambleTasks) + if (PreambleTasks) { + vlog("waiting for preamble tasks"); PreambleTasks->wait(); - if (WorkerThreads) + } + if (WorkerThreads) { + vlog("waiting for worker tasks"); WorkerThreads->wait(); + } } bool TUScheduler::blockUntilIdle(Deadline D) const { for (auto &File : Files) - if (!File.getValue()->Worker->blockUntilIdle(D)) + if (!File.getValue()->Worker->blockUntilIdle(D)) { + vlog("ASTWorker for {0} got stuck", File.first()); return false; + } if (PreambleTasks) - if (!PreambleTasks->wait(D)) + if (!PreambleTasks->wait(D)) { + vlog("Preamble tasks got stuck"); return false; + } return true; } @@ -942,7 +1158,7 @@ bool NewFile = FD == nullptr; if (!FD) { // Create a new worker to process the AST-related tasks. - ASTWorkerHandle Worker = ASTWorker::create( + TUWorkerHandle Worker = TUWorker::create( File, CDB, *IdleASTs, WorkerThreads ? WorkerThreads.getPointer() : nullptr, Barrier, UpdateDebounce, StorePreamblesInMemory, *Callbacks); @@ -1006,14 +1222,14 @@ return; } + std::shared_ptr Worker = It->second->Worker.lock(); + tooling::CompileCommand Command = Worker->getCurrentCompileCommand(); if (!PreambleTasks) { trace::Span Tracer(Name); SPAN_ATTACH(Tracer, "file", File); std::shared_ptr Preamble = - It->second->Worker->getPossiblyStalePreamble(); - Action(InputsAndPreamble{It->second->Contents, - It->second->Worker->getCurrentCompileCommand(), - Preamble.get()}); + It->second->Worker->getPossiblyMissingPreamble(); + Action(InputsAndPreamble{It->second->Contents, Command, Preamble.get()}); return; } @@ -1029,25 +1245,24 @@ }); } - std::shared_ptr Worker = It->second->Worker.lock(); auto Task = [Worker, Consistency, Name = Name.str(), File = File.str(), - Contents = It->second->Contents, - Command = Worker->getCurrentCompileCommand(), + Contents = It->second->Contents, Command = std::move(Command), Ctx = Context::current().derive(kFileBeingProcessed, std::string(File)), ConsistentPreamble = std::move(ConsistentPreamble), Action = std::move(Action), this]() mutable { std::shared_ptr Preamble; if (ConsistentPreamble.valid()) { + assert(Consistency == Consistent); Preamble = ConsistentPreamble.get(); + } else if (Consistency == Stale) { + // Wait until the preamble is built for the first time, if preamble is + // required. This avoids extra work of processing the preamble headers + // in parallel multiple times. + Preamble = Worker->getLatestBuiltPreamble(); } else { - if (Consistency != PreambleConsistency::StaleOrAbsent) { - // Wait until the preamble is built for the first time, if preamble - // is required. This avoids extra work of processing the preamble - // headers in parallel multiple times. - Worker->waitForFirstPreamble(); - } - Preamble = Worker->getPossiblyStalePreamble(); + assert(Consistency == StaleOrAbsent); + Preamble = Worker->getPossiblyMissingPreamble(); } std::lock_guard BarrierLock(Barrier); diff --git a/clang-tools-extra/clangd/Threading.h b/clang-tools-extra/clangd/Threading.h --- a/clang-tools-extra/clangd/Threading.h +++ b/clang-tools-extra/clangd/Threading.h @@ -107,7 +107,7 @@ /// Destructor waits for all pending tasks to finish. ~AsyncTaskRunner(); - void wait() const { (void)wait(Deadline::infinity()); } + void wait() const { (void)wait(timeoutSeconds(20)); } LLVM_NODISCARD bool wait(Deadline D) const; // The name is used for tracing and debugging (e.g. to name a spawned thread). void runAsync(const llvm::Twine &Name, llvm::unique_function Action);