//===--- TUScheduler.cpp -----------------------------------------*-C++-*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // TUScheduler manages a worker per active file. This ASTWorker processes // updates (modifications to file contents) and reads (actions performed on // preamble/AST) to the file. // // Each ASTWorker owns a dedicated thread to process updates and reads to the // relevant file. Any request gets queued in FIFO order to be processed by that // thread. // // An update request replaces current praser inputs to ensure any subsequent // read sees the version of the file they were requested. It will also issue a // build for new inputs. // // ASTWorker processes the file in two parts, a preamble and a main-file // section. A preamble can be reused between multiple versions of the file until // invalidated by a modification to a header, compile commands or modification // to relevant part of the current file. Such a preamble is called compatible. // An update is considered dead if no read was issued for that version and // diagnostics weren't requested by client or could be generated for a later // version of the file. ASTWorker eliminates such requests as they are // redundant. // // In the presence of stale (non-compatible) preambles, ASTWorker won't publish // diagnostics for update requests. Read requests will be served with ASTs build // with stale preambles, unless the read is picky and requires a compatible // preamble. In such cases it will block until new preamble is built. // // ASTWorker owns a PreambleThread for building preambles. If the preamble gets // invalidated by an update request, a new build will be requested on // PreambleThread. Since PreambleThread only receives requests for newer // versions of the file, in case of multiple requests it will only build the // last one and skip requests in between. Unless client force requested // diagnostics(WantDiagnostics::Yes). // // When a new preamble is built, a "golden" AST is immediately built from that // version of the file. This ensures diagnostics get updated even if the queue // is full. // // Some read requests might just need preamble. Since preambles can be read // concurrently, ASTWorker runs these requests on their own thread. These // requests will receive latest build preamble, which might possibly be stale. #include "TUScheduler.h" #include "CompileCommands.h" #include "Compiler.h" #include "Config.h" #include "Diagnostics.h" #include "GlobalCompilationDatabase.h" #include "ParsedAST.h" #include "Preamble.h" #include "index/CanonicalIncludes.h" #include "support/Cancellation.h" #include "support/Context.h" #include "support/Logger.h" #include "support/MemoryTree.h" #include "support/Path.h" #include "support/ThreadCrashReporter.h" #include "support/Threading.h" #include "support/Trace.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/FunctionExtras.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/Errc.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/Threading.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace clang { namespace clangd { using std::chrono::steady_clock; namespace { // Tracks latency (in seconds) of FS operations done during a preamble build. // build_type allows to split by expected VFS cache state (cold on first // preamble, somewhat warm after that when building first preamble for new file, // likely ~everything cached on preamble rebuild. constexpr trace::Metric PreambleBuildFilesystemLatency("preamble_fs_latency", trace::Metric::Distribution, "build_type"); // Tracks latency of FS operations done during a preamble build as a ratio of // preamble build time. build_type is same as above. constexpr trace::Metric PreambleBuildFilesystemLatencyRatio( "preamble_fs_latency_ratio", trace::Metric::Distribution, "build_type"); constexpr trace::Metric PreambleBuildSize("preamble_build_size", trace::Metric::Distribution); constexpr trace::Metric PreambleSerializedSize("preamble_serialized_size", trace::Metric::Distribution); void reportPreambleBuild(const PreambleBuildStats &Stats, bool IsFirstPreamble) { auto RecordWithLabel = [&Stats](llvm::StringRef Label) { PreambleBuildFilesystemLatency.record(Stats.FileSystemTime, Label); if (Stats.TotalBuildTime > 0) // Avoid division by zero. PreambleBuildFilesystemLatencyRatio.record( Stats.FileSystemTime / Stats.TotalBuildTime, Label); }; static llvm::once_flag OnceFlag; llvm::call_once(OnceFlag, [&] { RecordWithLabel("first_build"); }); RecordWithLabel(IsFirstPreamble ? "first_build_for_file" : "rebuild"); PreambleBuildSize.record(Stats.BuildSize); PreambleSerializedSize.record(Stats.SerializedSize); } class ASTWorker; } // namespace static clang::clangd::Key FileBeingProcessed; std::optional TUScheduler::getFileBeingProcessedInContext() { if (auto *File = Context::current().get(FileBeingProcessed)) return llvm::StringRef(*File); return std::nullopt; } /// An LRU cache of idle ASTs. /// Because we want to limit the overall number of these we retain, the cache /// owns ASTs (and may evict them) while their workers are idle. /// Workers borrow ASTs when active, and return them when done. class TUScheduler::ASTCache { public: using Key = const ASTWorker *; ASTCache(unsigned MaxRetainedASTs) : MaxRetainedASTs(MaxRetainedASTs) {} /// Returns result of getUsedBytes() for the AST cached by \p K. /// If no AST is cached, 0 is returned. std::size_t getUsedBytes(Key K) { std::lock_guard Lock(Mut); auto It = findByKey(K); if (It == LRU.end() || !It->second) return 0; return It->second->getUsedBytes(); } /// Store the value in the pool, possibly removing the last used AST. /// The value should not be in the pool when this function is called. void put(Key K, std::unique_ptr V) { std::unique_lock Lock(Mut); assert(findByKey(K) == LRU.end()); LRU.insert(LRU.begin(), {K, std::move(V)}); if (LRU.size() <= MaxRetainedASTs) return; // We're past the limit, remove the last element. std::unique_ptr ForCleanup = std::move(LRU.back().second); LRU.pop_back(); // Run the expensive destructor outside the lock. Lock.unlock(); ForCleanup.reset(); } /// Returns the cached value for \p K, or std::nullopt if the value is not in /// the cache anymore. If nullptr was cached for \p K, this function will /// return a null unique_ptr wrapped into an optional. /// If \p AccessMetric is set records whether there was a hit or miss. std::optional> take(Key K, const trace::Metric *AccessMetric = nullptr) { // Record metric after unlocking the mutex. std::unique_lock Lock(Mut); auto Existing = findByKey(K); if (Existing == LRU.end()) { if (AccessMetric) AccessMetric->record(1, "miss"); return std::nullopt; } if (AccessMetric) AccessMetric->record(1, "hit"); std::unique_ptr V = std::move(Existing->second); LRU.erase(Existing); // GCC 4.8 fails to compile `return V;`, as it tries to call the copy // constructor of unique_ptr, so we call the move ctor explicitly to avoid // this miscompile. return std::optional>(std::move(V)); } private: using KVPair = std::pair>; std::vector::iterator findByKey(Key K) { return llvm::find_if(LRU, [K](const KVPair &P) { return P.first == K; }); } std::mutex Mut; unsigned MaxRetainedASTs; /// Items sorted in LRU order, i.e. first item is the most recently accessed /// one. std::vector LRU; /* GUARDED_BY(Mut) */ }; /// A map from header files to an opened "proxy" file that includes them. /// If you open the header, the compile command from the proxy file is used. /// /// This inclusion information could also naturally live in the index, but there /// are advantages to using open files instead: /// - it's easier to achieve a *stable* choice of proxy, which is important /// to avoid invalidating the preamble /// - context-sensitive flags for libraries with multiple configurations /// (e.g. C++ stdlib sensitivity to -std version) /// - predictable behavior, e.g. guarantees that go-to-def landing on a header /// will have a suitable command available /// - fewer scaling problems to solve (project include graphs are big!) /// /// Implementation details: /// - We only record this for mainfiles where the command was trustworthy /// (i.e. not inferred). This avoids a bad inference "infecting" other files. /// - Once we've picked a proxy file for a header, we stick with it until the /// proxy file is invalidated *and* a new candidate proxy file is built. /// Switching proxies is expensive, as the compile flags will (probably) /// change and therefore we'll end up rebuilding the header's preamble. /// - We don't capture the actual compile command, but just the filename we /// should query to get it. This avoids getting out of sync with the CDB. /// /// All methods are threadsafe. In practice, update() comes from preamble /// threads, remove()s mostly from the main thread, and get() from ASTWorker. /// Writes are rare and reads are cheap, so we don't expect much contention. class TUScheduler::HeaderIncluderCache { // We should be a little careful how we store the include graph of open // files, as each can have a large number of transitive headers. // This representation is O(unique transitive source files). llvm::BumpPtrAllocator Arena; struct Association { llvm::StringRef MainFile; // Circular-linked-list of associations with the same mainFile. // Null indicates that the mainfile was removed. Association *Next; }; llvm::StringMap HeaderToMain; llvm::StringMap MainToFirst; std::atomic UsedBytes; // Updated after writes. mutable std::mutex Mu; void invalidate(Association *First) { Association *Current = First; do { Association *Next = Current->Next; Current->Next = nullptr; Current = Next; } while (Current != First); } // Create the circular list and return the head of it. Association *associate(llvm::StringRef MainFile, llvm::ArrayRef Headers) { Association *First = nullptr, *Prev = nullptr; for (const std::string &Header : Headers) { auto &Assoc = HeaderToMain[Header]; if (Assoc.Next) continue; // Already has a valid association. Assoc.MainFile = MainFile; Assoc.Next = Prev; Prev = &Assoc; if (!First) First = &Assoc; } if (First) First->Next = Prev; return First; } void updateMemoryUsage() { auto StringMapHeap = [](const auto &Map) { // StringMap stores the hashtable on the heap. // It contains pointers to the entries, and a hashcode for each. return Map.getNumBuckets() * (sizeof(void *) + sizeof(unsigned)); }; size_t Usage = Arena.getTotalMemory() + StringMapHeap(MainToFirst) + StringMapHeap(HeaderToMain) + sizeof(*this); UsedBytes.store(Usage, std::memory_order_release); } public: HeaderIncluderCache() : HeaderToMain(Arena), MainToFirst(Arena) { updateMemoryUsage(); } // Associate each header with MainFile (unless already associated). // Headers not in the list will have their associations removed. void update(PathRef MainFile, llvm::ArrayRef Headers) { std::lock_guard Lock(Mu); auto It = MainToFirst.try_emplace(MainFile, nullptr); Association *&First = It.first->second; if (First) invalidate(First); First = associate(It.first->first(), Headers); updateMemoryUsage(); } // Mark MainFile as gone. // This will *not* disassociate headers with MainFile immediately, but they // will be eligible for association with other files that get update()d. void remove(PathRef MainFile) { std::lock_guard Lock(Mu); Association *&First = MainToFirst[MainFile]; if (First) { invalidate(First); First = nullptr; } // MainToFirst entry should stay alive, as Associations might be pointing at // its key. } /// Get the mainfile associated with Header, or the empty string if none. std::string get(PathRef Header) const { std::lock_guard Lock(Mu); return HeaderToMain.lookup(Header).MainFile.str(); } size_t getUsedBytes() const { return UsedBytes.load(std::memory_order_acquire); } }; namespace { bool isReliable(const tooling::CompileCommand &Cmd) { return Cmd.Heuristic.empty(); } /// Threadsafe manager for updating a TUStatus and emitting it after each /// update. class SynchronizedTUStatus { public: SynchronizedTUStatus(PathRef FileName, ParsingCallbacks &Callbacks) : FileName(FileName), Callbacks(Callbacks) {} void update(llvm::function_ref Mutator) { std::lock_guard Lock(StatusMu); Mutator(Status); emitStatusLocked(); } /// Prevents emitting of further updates. void stop() { std::lock_guard Lock(StatusMu); CanPublish = false; } private: void emitStatusLocked() { if (CanPublish) Callbacks.onFileUpdated(FileName, Status); } const Path FileName; std::mutex StatusMu; TUStatus Status; bool CanPublish = true; ParsingCallbacks &Callbacks; }; // An attempt to acquire resources for a task using PreambleThrottler. // Initially it is unsatisfied, it (hopefully) becomes satisfied later but may // be destroyed before then. Destruction releases all resources. class PreambleThrottlerRequest { public: // The condition variable is signalled when the request is satisfied. PreambleThrottlerRequest(llvm::StringRef Filename, PreambleThrottler *Throttler, std::condition_variable &CV) : Throttler(Throttler), Satisfied(Throttler == nullptr) { // If there is no throttler, this dummy request is always satisfied. if (!Throttler) return; ID = Throttler->acquire(Filename, [&] { Satisfied.store(true, std::memory_order_release); CV.notify_all(); }); } bool satisfied() const { return Satisfied.load(std::memory_order_acquire); } // When the request is destroyed: // - if resources are not yet obtained, stop trying to get them. // - if resources were obtained, release them. ~PreambleThrottlerRequest() { if (Throttler) Throttler->release(ID); } private: PreambleThrottler::RequestID ID; PreambleThrottler *Throttler; std::atomic Satisfied = {false}; }; /// Responsible for building preambles. Whenever the thread is idle and the /// preamble is outdated, it starts to build a fresh preamble from the latest /// inputs. If RunSync is true, preambles are built synchronously in update() /// instead. class PreambleThread { public: PreambleThread(llvm::StringRef FileName, ParsingCallbacks &Callbacks, bool StorePreambleInMemory, bool RunSync, PreambleThrottler *Throttler, SynchronizedTUStatus &Status, TUScheduler::HeaderIncluderCache &HeaderIncluders, ASTWorker &AW) : FileName(FileName), Callbacks(Callbacks), StoreInMemory(StorePreambleInMemory), RunSync(RunSync), Throttler(Throttler), Status(Status), ASTPeer(AW), HeaderIncluders(HeaderIncluders) {} /// It isn't guaranteed that each requested version will be built. If there /// are multiple update requests while building a preamble, only the last one /// will be built. void update(std::unique_ptr CI, ParseInputs PI, std::vector CIDiags, WantDiagnostics WantDiags) { Request Req = {std::move(CI), std::move(PI), std::move(CIDiags), WantDiags, Context::current().clone()}; if (RunSync) { build(std::move(Req)); Status.update([](TUStatus &Status) { Status.PreambleActivity = PreambleAction::Idle; }); return; } { std::unique_lock Lock(Mutex); // If NextReq was requested with WantDiagnostics::Yes we cannot just drop // that on the floor. Block until we start building it. This won't // dead-lock as we are blocking the caller thread, while builds continue // on preamble thread. ReqCV.wait(Lock, [this] { return !NextReq || NextReq->WantDiags != WantDiagnostics::Yes; }); NextReq = 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. ReqCV.notify_all(); } void run() { while (true) { std::optional Throttle; { std::unique_lock Lock(Mutex); assert(!CurrentReq && "Already processing a request?"); // Wait until stop is called or there is a request. ReqCV.wait(Lock, [&] { return NextReq || Done; }); if (Done) break; { Throttle.emplace(FileName, Throttler, ReqCV); std::optional Tracer; // If acquire succeeded synchronously, avoid status jitter. if (!Throttle->satisfied()) { Tracer.emplace("PreambleThrottle"); Status.update([&](TUStatus &Status) { Status.PreambleActivity = PreambleAction::Queued; }); } ReqCV.wait(Lock, [&] { return Throttle->satisfied() || Done; }); } if (Done) break; // While waiting for the throttler, the request may have been updated! // That's fine though, there's still guaranteed to be some request. CurrentReq = std::move(*NextReq); NextReq.reset(); } { WithContext Guard(std::move(CurrentReq->Ctx)); // Note that we don't make use of the ContextProvider here. // Preamble tasks are always scheduled by ASTWorker tasks, and we // reuse the context/config that was created at that level. // Build the preamble and let the waiters know about it. build(std::move(*CurrentReq)); } // Releasing the throttle before destroying the request assists testing. Throttle.reset(); bool IsEmpty = false; { std::lock_guard Lock(Mutex); CurrentReq.reset(); IsEmpty = !NextReq; } if (IsEmpty) { // We don't perform this above, before waiting for a request to make // tests more deterministic. As there can be a race between this thread // and client thread(clangdserver). Status.update([](TUStatus &Status) { Status.PreambleActivity = PreambleAction::Idle; }); } ReqCV.notify_all(); } dlog("Preamble worker for {0} stopped", FileName); } /// Signals the run loop to exit. void stop() { dlog("Preamble worker for {0} received stop", FileName); { std::lock_guard Lock(Mutex); Done = true; NextReq.reset(); } // Let the worker thread know that it should stop. ReqCV.notify_all(); } bool blockUntilIdle(Deadline Timeout) const { std::unique_lock Lock(Mutex); return wait(Lock, ReqCV, Timeout, [&] { return !NextReq && !CurrentReq; }); } private: /// Holds inputs required for building a preamble. CI is guaranteed to be /// non-null. struct Request { std::unique_ptr CI; ParseInputs Inputs; std::vector CIDiags; WantDiagnostics WantDiags; Context Ctx; }; bool isDone() { std::lock_guard Lock(Mutex); return Done; } /// Builds a preamble for \p Req, might reuse LatestBuild if possible. /// Notifies ASTWorker after build finishes. void build(Request Req); mutable std::mutex Mutex; bool Done = false; /* GUARDED_BY(Mutex) */ std::optional NextReq; /* GUARDED_BY(Mutex) */ std::optional CurrentReq; /* GUARDED_BY(Mutex) */ // Signaled whenever a thread populates NextReq or worker thread builds a // Preamble. mutable std::condition_variable ReqCV; /* GUARDED_BY(Mutex) */ // Accessed only by preamble thread. std::shared_ptr LatestBuild; const Path FileName; ParsingCallbacks &Callbacks; const bool StoreInMemory; const bool RunSync; PreambleThrottler *Throttler; SynchronizedTUStatus &Status; ASTWorker &ASTPeer; TUScheduler::HeaderIncluderCache &HeaderIncluders; }; 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, TUScheduler::HeaderIncluderCache &HeaderIncluders, Semaphore &Barrier, bool RunSync, const TUScheduler::Options &Opts, 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 ASTWorkerHandle create(PathRef FileName, const GlobalCompilationDatabase &CDB, TUScheduler::ASTCache &IdleASTs, TUScheduler::HeaderIncluderCache &HeaderIncluders, AsyncTaskRunner *Tasks, Semaphore &Barrier, const TUScheduler::Options &Opts, ParsingCallbacks &Callbacks); ~ASTWorker(); void update(ParseInputs Inputs, WantDiagnostics, bool ContentChanged); void runWithAST(llvm::StringRef Name, llvm::unique_function)> Action, TUScheduler::ASTActionInvalidation); bool blockUntilIdle(Deadline Timeout) const; std::shared_ptr getPossiblyStalePreamble( std::shared_ptr *ASTSignals = nullptr) const; /// Used to inform ASTWorker about a new preamble build by PreambleThread. /// Diagnostics are only published through this callback. This ensures they /// are always for newer versions of the file, as the callback gets called in /// the same order as update requests. void updatePreamble(std::unique_ptr CI, ParseInputs PI, std::shared_ptr Preamble, std::vector CIDiags, WantDiagnostics WantDiags); /// 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; /// 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; TUScheduler::FileStats stats() const; bool isASTCached() const; private: // Details of an update request that are relevant to scheduling. struct UpdateType { // Do we want diagnostics from this version? // If Yes, we must always build this version. // If No, we only need to build this version if it's read. // If Auto, we build if it's read or if the debounce expires. WantDiagnostics Diagnostics; // Did the main-file content of the document change? // If so, we're allowed to cancel certain invalidated preceding reads. bool ContentChanged; }; /// Publishes diagnostics for \p Inputs. It will build an AST or reuse the /// cached one if applicable. Assumes LatestPreamble is compatible for \p /// Inputs. void generateDiagnostics(std::unique_ptr Invocation, ParseInputs Inputs, std::vector CIDiags); void updateASTSignals(ParsedAST &AST); // 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, std::optional Update, TUScheduler::ASTActionInvalidation); /// Runs a task synchronously. void runTask(llvm::StringRef Name, llvm::function_ref Task); /// 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; struct Request { llvm::unique_function Action; std::string Name; steady_clock::time_point AddTime; Context Ctx; std::optional QueueCtx; std::optional Update; TUScheduler::ASTActionInvalidation InvalidationPolicy; Canceler Invalidate; }; /// Handles retention of ASTs. TUScheduler::ASTCache &IdleASTs; TUScheduler::HeaderIncluderCache &HeaderIncluders; 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; /// Callback to create processing contexts for tasks. const std::function ContextProvider; const GlobalCompilationDatabase &CDB; /// Callback invoked when preamble or main file AST is built. ParsingCallbacks &Callbacks; 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. /// Writes and reads from unknown threads are locked. Reads from the worker /// thread are not locked, as it's the only writer. ParseInputs FileInputs; /* GUARDED_BY(Mutex) */ /// Times of recent AST rebuilds, used for UpdateDebounce computation. llvm::SmallVector RebuildTimes; /* GUARDED_BY(Mutex) */ /// Set to true to signal run() to finish processing. bool Done; /* GUARDED_BY(Mutex) */ std::deque Requests; /* GUARDED_BY(Mutex) */ std::optional CurrentRequest; /* GUARDED_BY(Mutex) */ /// Signalled whenever a new request has been scheduled or processing of a /// request has completed. mutable std::condition_variable RequestsCV; std::shared_ptr LatestASTSignals; /* GUARDED_BY(Mutex) */ /// Latest build preamble for current TU. /// None means no builds yet, null means there was an error while building. /// Only written by ASTWorker's thread. std::optional> LatestPreamble; std::deque PreambleRequests; /* GUARDED_BY(Mutex) */ /// Signaled whenever LatestPreamble changes state or there's a new /// PreambleRequest. mutable std::condition_variable PreambleCV; /// Guards the callback that publishes results of AST-related computations /// (diagnostics) 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) */ std::atomic ASTBuildCount = {0}; std::atomic PreambleBuildCount = {0}; SynchronizedTUStatus Status; PreambleThread PreamblePeer; }; /// 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); } public: ASTWorkerHandle(const ASTWorkerHandle &) = delete; ASTWorkerHandle &operator=(const ASTWorkerHandle &) = delete; ASTWorkerHandle(ASTWorkerHandle &&) = default; ASTWorkerHandle &operator=(ASTWorkerHandle &&) = default; ~ASTWorkerHandle() { if (Worker) Worker->stop(); } ASTWorker &operator*() { assert(Worker && "Handle was moved from"); return *Worker; } ASTWorker *operator->() { assert(Worker && "Handle was moved from"); return Worker.get(); } /// 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; }; ASTWorkerHandle ASTWorker::create(PathRef FileName, const GlobalCompilationDatabase &CDB, TUScheduler::ASTCache &IdleASTs, TUScheduler::HeaderIncluderCache &HeaderIncluders, AsyncTaskRunner *Tasks, Semaphore &Barrier, const TUScheduler::Options &Opts, ParsingCallbacks &Callbacks) { std::shared_ptr Worker( new ASTWorker(FileName, CDB, IdleASTs, HeaderIncluders, Barrier, /*RunSync=*/!Tasks, Opts, Callbacks)); if (Tasks) { Tasks->runAsync("ASTWorker:" + llvm::sys::path::filename(FileName), [Worker]() { Worker->run(); }); Tasks->runAsync("PreambleWorker:" + llvm::sys::path::filename(FileName), [Worker]() { Worker->PreamblePeer.run(); }); } return ASTWorkerHandle(std::move(Worker)); } ASTWorker::ASTWorker(PathRef FileName, const GlobalCompilationDatabase &CDB, TUScheduler::ASTCache &LRUCache, TUScheduler::HeaderIncluderCache &HeaderIncluders, Semaphore &Barrier, bool RunSync, const TUScheduler::Options &Opts, ParsingCallbacks &Callbacks) : IdleASTs(LRUCache), HeaderIncluders(HeaderIncluders), RunSync(RunSync), UpdateDebounce(Opts.UpdateDebounce), FileName(FileName), ContextProvider(Opts.ContextProvider), CDB(CDB), Callbacks(Callbacks), Barrier(Barrier), Done(false), Status(FileName, Callbacks), PreamblePeer(FileName, Callbacks, Opts.StorePreamblesInMemory, RunSync, Opts.PreambleThrottler, Status, HeaderIncluders, *this) { // Set a fallback command because compile command can be accessed before // `Inputs` is initialized. Other fields are only used after initialization // from client inputs. FileInputs.CompileCommand = CDB.getFallbackCommand(FileName); } 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 } void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags, bool ContentChanged) { llvm::StringLiteral TaskName = "Update"; auto Task = [=]() 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. auto Cmd = CDB.getCompileCommand(FileName); // If we don't have a reliable command for this file, it may be a header. // Try to find a file that includes it, to borrow its command. if (!Cmd || !isReliable(*Cmd)) { std::string ProxyFile = HeaderIncluders.get(FileName); if (!ProxyFile.empty()) { auto ProxyCmd = CDB.getCompileCommand(ProxyFile); if (!ProxyCmd || !isReliable(*ProxyCmd)) { // This command is supposed to be reliable! It's probably gone. HeaderIncluders.remove(ProxyFile); } else { // We have a reliable command for an including file, use it. Cmd = tooling::transferCompileCommand(std::move(*ProxyCmd), FileName); } } } if (Cmd) Inputs.CompileCommand = std::move(*Cmd); else Inputs.CompileCommand = CDB.getFallbackCommand(FileName); bool InputsAreTheSame = std::tie(FileInputs.CompileCommand, FileInputs.Contents) == std::tie(Inputs.CompileCommand, Inputs.Contents); // Cached AST is invalidated. if (!InputsAreTheSame) { IdleASTs.take(this); RanASTCallback = false; } // Update current inputs so that subsequent reads can see them. { std::lock_guard Lock(Mutex); FileInputs = Inputs; } log("ASTWorker building file {0} version {1} with command {2}\n[{3}]\n{4}", FileName, Inputs.Version, Inputs.CompileCommand.Heuristic, Inputs.CompileCommand.Directory, printArgv(Inputs.CompileCommand.CommandLine)); 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}", printArgv(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); RanASTCallback = false; // Report the diagnostics we collected when parsing the command line. Callbacks.onFailedAST(FileName, Inputs.Version, std::move(CompilerInvocationDiags), [&](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) Publish(); }); // Note that this might throw away a stale preamble that might still be // useful, but this is how we communicate a build error. LatestPreamble.emplace(); // Make sure anyone waiting for the preamble gets notified it could not be // built. PreambleCV.notify_all(); return; } // Inform preamble peer, before attempting to build diagnostics so that they // can be built concurrently. PreamblePeer.update(std::make_unique(*Invocation), Inputs, CompilerInvocationDiags, WantDiags); // Emit diagnostics from (possibly) stale preamble while waiting for a // rebuild. Newly built preamble cannot emit diagnostics before this call // finishes (ast callbacks are called from astpeer thread), hence we // gurantee eventual consistency. if (LatestPreamble && WantDiags != WantDiagnostics::No && Config::current().Diagnostics.AllowStalePreamble) generateDiagnostics(std::move(Invocation), std::move(Inputs), std::move(CompilerInvocationDiags)); std::unique_lock Lock(Mutex); PreambleCV.wait(Lock, [this] { // Block until we reiceve a preamble request, unless a preamble already // exists, as patching an empty preamble would imply rebuilding it from // scratch. // We block here instead of the consumer to prevent any deadlocks. Since // LatestPreamble is only populated by ASTWorker thread. return LatestPreamble || !PreambleRequests.empty() || Done; }); }; startTask(TaskName, std::move(Task), UpdateType{WantDiags, ContentChanged}, TUScheduler::NoInvalidation); } void ASTWorker::runWithAST( llvm::StringRef Name, llvm::unique_function)> Action, TUScheduler::ASTActionInvalidation Invalidation) { // Tracks ast cache accesses for read operations. static constexpr trace::Metric ASTAccessForRead( "ast_access_read", trace::Metric::Counter, "result"); auto Task = [=, Action = std::move(Action)]() mutable { if (auto Reason = isCancelled()) return Action(llvm::make_error(Reason)); std::optional> AST = IdleASTs.take(this, &ASTAccessForRead); if (!AST) { StoreDiags CompilerInvocationDiagConsumer; std::unique_ptr Invocation = buildCompilerInvocation(FileInputs, CompilerInvocationDiagConsumer); // Try rebuilding the AST. vlog("ASTWorker rebuilding evicted AST to run {0}: {1} version {2}", Name, FileName, FileInputs.Version); // FIXME: We might need to build a patched ast once preamble thread starts // running async. Currently getPossiblyStalePreamble below will always // return a compatible preamble as ASTWorker::update blocks. std::optional NewAST; if (Invocation) { NewAST = ParsedAST::build(FileName, FileInputs, std::move(Invocation), CompilerInvocationDiagConsumer.take(), getPossiblyStalePreamble()); ++ASTBuildCount; } 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(error(llvm::errc::invalid_argument, "invalid AST")); vlog("ASTWorker running {0} on version {2} of {1}", Name, FileName, FileInputs.Version); Action(InputsAndAST{FileInputs, **AST}); }; startTask(Name, std::move(Task), /*Update=*/std::nullopt, Invalidation); } /// To be called from ThreadCrashReporter's signal handler. static void crashDumpCompileCommand(llvm::raw_ostream &OS, const tooling::CompileCommand &Command) { OS << " Filename: " << Command.Filename << "\n"; OS << " Directory: " << Command.Directory << "\n"; OS << " Command Line:"; for (auto &Arg : Command.CommandLine) { OS << " " << Arg; } OS << "\n"; } /// To be called from ThreadCrashReporter's signal handler. static void crashDumpFileContents(llvm::raw_ostream &OS, const std::string &Contents) { // Avoid flooding the terminal with source code by default, but allow clients // to opt in. Use an env var to preserve backwards compatibility of the // command line interface, while allowing it to be set outside the clangd // launch site for more flexibility. if (getenv("CLANGD_CRASH_DUMP_SOURCE")) { OS << " Contents:\n"; OS << Contents << "\n"; } } /// To be called from ThreadCrashReporter's signal handler. static void crashDumpParseInputs(llvm::raw_ostream &OS, const ParseInputs &FileInputs) { auto &Command = FileInputs.CompileCommand; crashDumpCompileCommand(OS, Command); OS << " Version: " << FileInputs.Version << "\n"; crashDumpFileContents(OS, FileInputs.Contents); } void PreambleThread::build(Request Req) { assert(Req.CI && "Got preamble request with null compiler invocation"); const ParseInputs &Inputs = Req.Inputs; bool ReusedPreamble = false; Status.update([&](TUStatus &Status) { Status.PreambleActivity = PreambleAction::Building; }); auto _ = llvm::make_scope_exit([this, &Req, &ReusedPreamble] { ASTPeer.updatePreamble(std::move(Req.CI), std::move(Req.Inputs), LatestBuild, std::move(Req.CIDiags), std::move(Req.WantDiags)); if (!ReusedPreamble) Callbacks.onPreamblePublished(FileName); }); if (!LatestBuild || Inputs.ForceRebuild) { vlog("Building first preamble for {0} version {1}", FileName, Inputs.Version); } else if (isPreambleCompatible(*LatestBuild, Inputs, FileName, *Req.CI)) { vlog("Reusing preamble version {0} for version {1} of {2}", LatestBuild->Version, Inputs.Version, FileName); ReusedPreamble = true; return; } else { vlog("Rebuilding invalidated preamble for {0} version {1} (previous was " "version {2})", FileName, Inputs.Version, LatestBuild->Version); } ThreadCrashReporter ScopedReporter([&Inputs]() { llvm::errs() << "Signalled while building preamble\n"; crashDumpParseInputs(llvm::errs(), Inputs); }); PreambleBuildStats Stats; bool IsFirstPreamble = !LatestBuild; LatestBuild = clang::clangd::buildPreamble( FileName, *Req.CI, Inputs, StoreInMemory, [&](ASTContext &Ctx, Preprocessor &PP, const CanonicalIncludes &CanonIncludes) { Callbacks.onPreambleAST(FileName, Inputs.Version, *Req.CI, Ctx, PP, CanonIncludes); }, &Stats); if (!LatestBuild) return; reportPreambleBuild(Stats, IsFirstPreamble); if (isReliable(LatestBuild->CompileCommand)) HeaderIncluders.update(FileName, LatestBuild->Includes.allHeaders()); } void ASTWorker::updatePreamble(std::unique_ptr CI, ParseInputs PI, std::shared_ptr Preamble, std::vector CIDiags, WantDiagnostics WantDiags) { llvm::StringLiteral TaskName = "Build AST"; // Store preamble and build diagnostics with new preamble if requested. auto Task = [this, Preamble = std::move(Preamble), CI = std::move(CI), PI = std::move(PI), CIDiags = std::move(CIDiags), WantDiags = std::move(WantDiags)]() mutable { // Update the preamble inside ASTWorker queue to ensure atomicity. As a task // running inside ASTWorker assumes internals won't change until it // finishes. if (!LatestPreamble || Preamble != *LatestPreamble) { ++PreambleBuildCount; // Cached AST is no longer valid. IdleASTs.take(this); RanASTCallback = false; std::lock_guard Lock(Mutex); // LatestPreamble might be the last reference to old preamble, do not // trigger destructor while holding the lock. if (LatestPreamble) std::swap(*LatestPreamble, Preamble); else LatestPreamble = std::move(Preamble); } // Notify anyone waiting for a preamble. PreambleCV.notify_all(); // Give up our ownership to old preamble before starting expensive AST // build. Preamble.reset(); // We only need to build the AST if diagnostics were requested. if (WantDiags == WantDiagnostics::No) return; // The file may have been edited since we started building this preamble. // If diagnostics need a fresh preamble, we must use the old version that // matches the preamble. We make forward progress as updatePreamble() // receives increasing versions, and this is the only place we emit // diagnostics. // If diagnostics can use a stale preamble, we use the current contents of // the file instead. This provides more up-to-date diagnostics, and avoids // diagnostics going backwards (we may have already emitted staler-preamble // diagnostics for the new version). We still have eventual consistency: at // some point updatePreamble() will catch up to the current file. if (Config::current().Diagnostics.AllowStalePreamble) PI = FileInputs; // Report diagnostics with the new preamble to ensure progress. Otherwise // diagnostics might get stale indefinitely if user keeps invalidating the // preamble. generateDiagnostics(std::move(CI), std::move(PI), std::move(CIDiags)); }; if (RunSync) { runTask(TaskName, Task); return; } { std::lock_guard Lock(Mutex); PreambleRequests.push_back({std::move(Task), std::string(TaskName), steady_clock::now(), Context::current().clone(), std::nullopt, std::nullopt, TUScheduler::NoInvalidation, nullptr}); } PreambleCV.notify_all(); RequestsCV.notify_all(); } void ASTWorker::updateASTSignals(ParsedAST &AST) { auto Signals = std::make_shared(ASTSignals::derive(AST)); // Existing readers of ASTSignals will have their copy preserved until the // read is completed. The last reader deletes the old ASTSignals. { std::lock_guard Lock(Mutex); std::swap(LatestASTSignals, Signals); } } void ASTWorker::generateDiagnostics( std::unique_ptr Invocation, ParseInputs Inputs, std::vector CIDiags) { // Tracks ast cache accesses for publishing diags. static constexpr trace::Metric ASTAccessForDiag( "ast_access_diag", trace::Metric::Counter, "result"); assert(Invocation); assert(LatestPreamble); // No need to rebuild the AST if we won't send the diagnostics. { std::lock_guard Lock(PublishMu); if (!CanPublishResults) return; } // Used to check whether we can update AST cache. bool InputsAreLatest = std::tie(FileInputs.CompileCommand, FileInputs.Contents) == std::tie(Inputs.CompileCommand, Inputs.Contents); // Take a shortcut and don't report the diagnostics, since they should be the // same. All the clients should handle the lack of OnUpdated() call anyway to // handle empty result from buildAST. // FIXME: the AST could actually change if non-preamble includes changed, // but we choose to ignore it. if (InputsAreLatest && RanASTCallback) return; // Get the AST for diagnostics, either build it or use the cached one. std::string TaskName = llvm::formatv("Build AST ({0})", Inputs.Version); Status.update([&](TUStatus &Status) { Status.ASTActivity.K = ASTAction::Building; Status.ASTActivity.Name = std::move(TaskName); }); // We might be able to reuse the last we've built for a read request. // FIXME: It might be better to not reuse this AST. That way queued AST builds // won't be required for diags. std::optional> AST = IdleASTs.take(this, &ASTAccessForDiag); if (!AST || !InputsAreLatest) { auto RebuildStartTime = DebouncePolicy::clock::now(); std::optional NewAST = ParsedAST::build( FileName, Inputs, std::move(Invocation), CIDiags, *LatestPreamble); auto RebuildDuration = DebouncePolicy::clock::now() - RebuildStartTime; ++ASTBuildCount; // 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. 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). if (RebuildTimes.size() == RebuildTimes.capacity()) RebuildTimes.erase(RebuildTimes.begin()); RebuildTimes.push_back(RebuildDuration); Lock.unlock(); } Status.update([&](TUStatus &Status) { Status.Details.ReuseAST = false; Status.Details.BuildFailed = !NewAST; }); AST = NewAST ? std::make_unique(std::move(*NewAST)) : nullptr; } else { log("Skipping rebuild of the AST for {0}, inputs are the same.", FileName); Status.update([](TUStatus &Status) { Status.Details.ReuseAST = true; Status.Details.BuildFailed = false; }); } // Publish diagnostics. auto RunPublish = [&](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) Publish(); }; if (*AST) { trace::Span Span("Running main AST callback"); Callbacks.onMainAST(FileName, **AST, RunPublish); updateASTSignals(**AST); } else { // Failed to build the AST, at least report diagnostics from the // command line if there were any. // FIXME: we might have got more errors while trying to build the // AST, surface them too. Callbacks.onFailedAST(FileName, Inputs.Version, CIDiags, RunPublish); } // AST might've been built for an older version of the source, as ASTWorker // queue raced ahead while we were waiting on the preamble. In that case the // queue can't reuse the AST. if (InputsAreLatest) { RanASTCallback = *AST != nullptr; IdleASTs.put(this, std::move(*AST)); } } std::shared_ptr ASTWorker::getPossiblyStalePreamble( std::shared_ptr *ASTSignals) const { std::lock_guard Lock(Mutex); if (ASTSignals) *ASTSignals = LatestASTSignals; return LatestPreamble ? *LatestPreamble : nullptr; } void ASTWorker::waitForFirstPreamble() const { std::unique_lock Lock(Mutex); PreambleCV.wait(Lock, [this] { return LatestPreamble || Done; }); } tooling::CompileCommand ASTWorker::getCurrentCompileCommand() const { std::unique_lock Lock(Mutex); return FileInputs.CompileCommand; } TUScheduler::FileStats ASTWorker::stats() const { TUScheduler::FileStats Result; Result.ASTBuilds = ASTBuildCount; Result.PreambleBuilds = PreambleBuildCount; // 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. Result.UsedBytesAST = IdleASTs.getUsedBytes(this); if (auto Preamble = getPossiblyStalePreamble()) Result.UsedBytesPreamble = Preamble->Preamble.getSize(); return Result; } bool ASTWorker::isASTCached() const { return IdleASTs.getUsedBytes(this) != 0; } void ASTWorker::stop() { { std::lock_guard Lock(PublishMu); CanPublishResults = false; } { std::lock_guard Lock(Mutex); assert(!Done && "stop() called twice"); Done = true; } PreamblePeer.stop(); // We are no longer going to build any preambles, let the waiters know that. PreambleCV.notify_all(); Status.stop(); RequestsCV.notify_all(); } void ASTWorker::runTask(llvm::StringRef Name, llvm::function_ref Task) { ThreadCrashReporter ScopedReporter([this, Name]() { llvm::errs() << "Signalled during AST worker action: " << Name << "\n"; crashDumpParseInputs(llvm::errs(), FileInputs); }); trace::Span Tracer(Name); WithContext WithProvidedContext(ContextProvider(FileName)); Task(); } void ASTWorker::startTask(llvm::StringRef Name, llvm::unique_function Task, std::optional Update, TUScheduler::ASTActionInvalidation Invalidation) { if (RunSync) { assert(!Done && "running a task after stop()"); runTask(Name, Task); return; } { std::lock_guard Lock(Mutex); assert(!Done && "running a task after stop()"); // Cancel any requests invalidated by this request. if (Update && Update->ContentChanged) { for (auto &R : llvm::reverse(Requests)) { if (R.InvalidationPolicy == TUScheduler::InvalidateOnUpdate) R.Invalidate(); if (R.Update && R.Update->ContentChanged) break; // Older requests were already invalidated by the older update. } } // Allow this request to be cancelled if invalidated. Context Ctx = Context::current().derive(FileBeingProcessed, FileName); Canceler Invalidate = nullptr; if (Invalidation) { WithContext WC(std::move(Ctx)); std::tie(Ctx, Invalidate) = cancelableTask( /*Reason=*/static_cast(ErrorCode::ContentModified)); } // Trace the time the request spends in the queue, and the requests that // it's going to wait for. std::optional QueueCtx; if (trace::enabled()) { // Tracers that follow threads and need strict nesting will see a tiny // instantaneous event "we're enqueueing", and sometime later it runs. WithContext WC(Ctx.clone()); trace::Span Tracer("Queued:" + Name); if (Tracer.Args) { if (CurrentRequest) SPAN_ATTACH(Tracer, "CurrentRequest", CurrentRequest->Name); llvm::json::Array PreambleRequestsNames; for (const auto &Req : PreambleRequests) PreambleRequestsNames.push_back(Req.Name); SPAN_ATTACH(Tracer, "PreambleRequestsNames", std::move(PreambleRequestsNames)); llvm::json::Array RequestsNames; for (const auto &Req : Requests) RequestsNames.push_back(Req.Name); SPAN_ATTACH(Tracer, "RequestsNames", std::move(RequestsNames)); } // For tracers that follow contexts, keep the trace span's context alive // until we dequeue the request, so they see the full duration. QueueCtx = Context::current().clone(); } Requests.push_back({std::move(Task), std::string(Name), steady_clock::now(), std::move(Ctx), std::move(QueueCtx), Update, Invalidation, std::move(Invalidate)}); } RequestsCV.notify_all(); } 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()) { assert(PreambleRequests.empty() && "Preamble updates should be scheduled immediately"); if (Done) { if (Requests.empty()) return; // Even though Done is set, finish pending requests. break; // However, skip delays to shutdown fast. } // Tracing: we have a next request, attribute this sleep to it. std::optional Ctx; std::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())) { Status.update([&](TUStatus &Status) { Status.ASTActivity.K = ASTAction::Queued; Status.ASTActivity.Name = Requests.front().Name; }); SPAN_ATTACH(*Tracer, "sleep_ms", std::chrono::duration_cast( Wait.time() - steady_clock::now()) .count()); } } wait(Lock, RequestsCV, Wait); } // Any request in ReceivedPreambles is at least as old as the // Requests.front(), so prefer them first to preserve LSP order. if (!PreambleRequests.empty()) { CurrentRequest = std::move(PreambleRequests.front()); PreambleRequests.pop_front(); } else { CurrentRequest = std::move(Requests.front()); Requests.pop_front(); } } // unlock Mutex // Inform tracing that the request was dequeued. CurrentRequest->QueueCtx.reset(); // 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()) { Status.update([&](TUStatus &Status) { Status.ASTActivity.K = ASTAction::Queued; Status.ASTActivity.Name = CurrentRequest->Name; }); Lock.lock(); } WithContext Guard(std::move(CurrentRequest->Ctx)); Status.update([&](TUStatus &Status) { Status.ASTActivity.K = ASTAction::RunningAction; Status.ASTActivity.Name = CurrentRequest->Name; }); runTask(CurrentRequest->Name, CurrentRequest->Action); } bool IsEmpty = false; { std::lock_guard Lock(Mutex); CurrentRequest.reset(); IsEmpty = Requests.empty() && PreambleRequests.empty(); } if (IsEmpty) { Status.update([&](TUStatus &Status) { Status.ASTActivity.K = ASTAction::Idle; Status.ASTActivity.Name = ""; }); } RequestsCV.notify_all(); } } Deadline ASTWorker::scheduleLocked() { // Process new preambles immediately. if (!PreambleRequests.empty()) return Deadline::zero(); 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->Update == std::nullopt) break; continue; } // Cancelled reads are moved to the front of the queue and run immediately. if (I->Update == std::nullopt) { 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->Update->Diagnostics == WantDiagnostics::Yes) I->Update->Diagnostics = WantDiagnostics::Auto; } 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.Update == std::nullopt || R.Update->Diagnostics == 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; } // 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 Update = Next->Update; if (!Update) // 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->Update) return false; // The other way an update can be live is if its diagnostics might be used. switch (Update->Diagnostics) { 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->Update && Next->Update->Diagnostics != WantDiagnostics::No) return true; // Prefer later diagnostics. return false; } llvm_unreachable("Unknown WantDiagnostics"); } bool ASTWorker::blockUntilIdle(Deadline Timeout) const { auto WaitUntilASTWorkerIsIdle = [&] { std::unique_lock Lock(Mutex); return wait(Lock, RequestsCV, Timeout, [&] { return PreambleRequests.empty() && Requests.empty() && !CurrentRequest; }); }; // Make sure ASTWorker has processed all requests, which might issue new // updates to PreamblePeer. if (!WaitUntilASTWorkerIsIdle()) return false; // Now that ASTWorker processed all requests, ensure PreamblePeer has served // all update requests. This might create new PreambleRequests for the // ASTWorker. if (!PreamblePeer.blockUntilIdle(Timeout)) return false; assert(Requests.empty() && "No new normal tasks can be scheduled concurrently with " "blockUntilIdle(): ASTWorker isn't threadsafe"); // Finally make sure ASTWorker has processed all of the preamble updates. return WaitUntilASTWorkerIsIdle(); } // Render a TUAction to a user-facing string representation. // TUAction represents clangd-internal states, we don't intend to expose them // to users (say C++ programmers) directly to avoid confusion, we use terms that // are familiar by C++ programmers. std::string renderTUAction(const PreambleAction PA, const ASTAction &AA) { llvm::SmallVector Result; switch (PA) { case PreambleAction::Building: Result.push_back("parsing includes"); break; case PreambleAction::Queued: Result.push_back("includes are queued"); break; case PreambleAction::Idle: // We handle idle specially below. break; } switch (AA.K) { case ASTAction::Queued: Result.push_back("file is queued"); break; case ASTAction::RunningAction: Result.push_back("running " + AA.Name); break; case ASTAction::Building: Result.push_back("parsing main file"); break; case ASTAction::Idle: // We handle idle specially below. break; } if (Result.empty()) return "idle"; return llvm::join(Result, ", "); } } // namespace unsigned getDefaultAsyncThreadsCount() { return llvm::heavyweight_hardware_concurrency().compute_thread_count(); } FileStatus TUStatus::render(PathRef File) const { FileStatus FStatus; FStatus.uri = URIForFile::canonicalize(File, /*TUPath=*/File); FStatus.state = renderTUAction(PreambleActivity, ASTActivity); return FStatus; } struct TUScheduler::FileData { /// Latest inputs, passed to TUScheduler::update(). std::string Contents; ASTWorkerHandle Worker; }; TUScheduler::TUScheduler(const GlobalCompilationDatabase &CDB, const Options &Opts, std::unique_ptr Callbacks) : CDB(CDB), Opts(Opts), Callbacks(Callbacks ? std::move(Callbacks) : std::make_unique()), Barrier(Opts.AsyncThreadsCount), QuickRunBarrier(Opts.AsyncThreadsCount), IdleASTs( std::make_unique(Opts.RetentionPolicy.MaxRetainedASTs)), HeaderIncluders(std::make_unique()) { // Avoid null checks everywhere. if (!Opts.ContextProvider) { this->Opts.ContextProvider = [](llvm::StringRef) { return Context::current().clone(); }; } if (0 < Opts.AsyncThreadsCount) { PreambleTasks.emplace(); WorkerThreads.emplace(); } } TUScheduler::~TUScheduler() { // Notify all workers that they need to stop. Files.clear(); // Wait for all in-flight tasks to finish. if (PreambleTasks) PreambleTasks->wait(); if (WorkerThreads) WorkerThreads->wait(); } bool TUScheduler::blockUntilIdle(Deadline D) const { for (auto &File : Files) if (!File.getValue()->Worker->blockUntilIdle(D)) return false; if (PreambleTasks) if (!PreambleTasks->wait(D)) return false; return true; } bool TUScheduler::update(PathRef File, ParseInputs Inputs, WantDiagnostics WantDiags) { std::unique_ptr &FD = Files[File]; bool NewFile = FD == nullptr; bool ContentChanged = false; if (!FD) { // Create a new worker to process the AST-related tasks. ASTWorkerHandle Worker = ASTWorker::create( File, CDB, *IdleASTs, *HeaderIncluders, WorkerThreads ? &*WorkerThreads : nullptr, Barrier, Opts, *Callbacks); FD = std::unique_ptr( new FileData{Inputs.Contents, std::move(Worker)}); ContentChanged = true; } else if (FD->Contents != Inputs.Contents) { ContentChanged = true; FD->Contents = Inputs.Contents; } FD->Worker->update(std::move(Inputs), WantDiags, ContentChanged); // There might be synthetic update requests, don't change the LastActiveFile // in such cases. if (ContentChanged) LastActiveFile = File.str(); return NewFile; } void TUScheduler::remove(PathRef File) { bool Removed = Files.erase(File); if (!Removed) elog("Trying to remove file from TUScheduler that is not tracked: {0}", File); // We don't call HeaderIncluders.remove(File) here. // If we did, we'd avoid potentially stale header/mainfile associations. // However, it would mean that closing a mainfile could invalidate the // preamble of several open headers. } void TUScheduler::run(llvm::StringRef Name, llvm::StringRef Path, llvm::unique_function Action) { runWithSemaphore(Name, Path, std::move(Action), Barrier); } void TUScheduler::runQuick(llvm::StringRef Name, llvm::StringRef Path, llvm::unique_function Action) { // Use QuickRunBarrier to serialize quick tasks: we are ignoring // the parallelism level set by the user, don't abuse it runWithSemaphore(Name, Path, std::move(Action), QuickRunBarrier); } void TUScheduler::runWithSemaphore(llvm::StringRef Name, llvm::StringRef Path, llvm::unique_function Action, Semaphore &Sem) { if (Path.empty()) Path = LastActiveFile; else LastActiveFile = Path.str(); if (!PreambleTasks) { WithContext WithProvidedContext(Opts.ContextProvider(Path)); return Action(); } PreambleTasks->runAsync(Name, [this, &Sem, Ctx = Context::current().clone(), Path(Path.str()), Action = std::move(Action)]() mutable { std::lock_guard BarrierLock(Sem); WithContext WC(std::move(Ctx)); WithContext WithProvidedContext(Opts.ContextProvider(Path)); Action(); }); } void TUScheduler::runWithAST( llvm::StringRef Name, PathRef File, llvm::unique_function)> Action, TUScheduler::ASTActionInvalidation Invalidation) { auto It = Files.find(File); if (It == Files.end()) { Action(llvm::make_error( "trying to get AST for non-added document", ErrorCode::InvalidParams)); return; } LastActiveFile = File.str(); It->second->Worker->runWithAST(Name, std::move(Action), Invalidation); } void TUScheduler::runWithPreamble(llvm::StringRef Name, PathRef File, PreambleConsistency Consistency, Callback Action) { auto It = Files.find(File); if (It == Files.end()) { Action(llvm::make_error( "trying to get preamble for non-added document", ErrorCode::InvalidParams)); return; } LastActiveFile = File.str(); if (!PreambleTasks) { trace::Span Tracer(Name); SPAN_ATTACH(Tracer, "file", File); std::shared_ptr Signals; std::shared_ptr Preamble = It->second->Worker->getPossiblyStalePreamble(&Signals); WithContext WithProvidedContext(Opts.ContextProvider(File)); Action(InputsAndPreamble{It->second->Contents, It->second->Worker->getCurrentCompileCommand(), Preamble.get(), Signals.get()}); return; } 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(), Ctx = Context::current().derive(FileBeingProcessed, std::string(File)), Action = std::move(Action), this]() mutable { ThreadCrashReporter ScopedReporter([&Name, &Contents, &Command]() { llvm::errs() << "Signalled during preamble action: " << Name << "\n"; crashDumpCompileCommand(llvm::errs(), Command); crashDumpFileContents(llvm::errs(), Contents); }); std::shared_ptr Preamble; if (Consistency == PreambleConsistency::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. Worker->waitForFirstPreamble(); } std::shared_ptr Signals; Preamble = Worker->getPossiblyStalePreamble(&Signals); std::lock_guard BarrierLock(Barrier); WithContext Guard(std::move(Ctx)); trace::Span Tracer(Name); SPAN_ATTACH(Tracer, "file", File); WithContext WithProvidedContext(Opts.ContextProvider(File)); Action(InputsAndPreamble{Contents, Command, Preamble.get(), Signals.get()}); }; PreambleTasks->runAsync("task:" + llvm::sys::path::filename(File), std::move(Task)); } llvm::StringMap TUScheduler::fileStats() const { llvm::StringMap Result; for (const auto &PathAndFile : Files) Result.try_emplace(PathAndFile.first(), PathAndFile.second->Worker->stats()); return Result; } std::vector TUScheduler::getFilesWithCachedAST() const { std::vector Result; for (auto &&PathAndFile : Files) { if (!PathAndFile.second->Worker->isASTCached()) continue; Result.push_back(std::string(PathAndFile.first())); } return Result; } DebouncePolicy::clock::duration DebouncePolicy::compute(llvm::ArrayRef History) const { assert(Min <= Max && "Invalid policy"); if (History.empty()) return Max; // Arbitrary. // Base the result on the median rebuild. // nth_element needs a mutable array, take the chance to bound the data size. History = History.take_back(15); llvm::SmallVector Recent(History.begin(), History.end()); auto *Median = Recent.begin() + Recent.size() / 2; std::nth_element(Recent.begin(), Median, Recent.end()); clock::duration Target = std::chrono::duration_cast(RebuildRatio * *Median); if (Target > Max) return Max; if (Target < Min) return Min; return Target; } DebouncePolicy DebouncePolicy::fixed(clock::duration T) { DebouncePolicy P; P.Min = P.Max = T; return P; } void TUScheduler::profile(MemoryTree &MT) const { for (const auto &Elem : fileStats()) { MT.detail(Elem.first()) .child("preamble") .addUsage(Opts.StorePreamblesInMemory ? Elem.second.UsedBytesPreamble : 0); MT.detail(Elem.first()).child("ast").addUsage(Elem.second.UsedBytesAST); MT.child("header_includer_cache").addUsage(HeaderIncluders->getUsedBytes()); } } } // namespace clangd } // namespace clang