//===--- Threading.h - Abstractions for multithreading -----------*- 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 // //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_THREADING_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_THREADING_H #include "support/Context.h" #include "llvm/ADT/FunctionExtras.h" #include "llvm/ADT/Twine.h" #include #include #include #include #include #include #include namespace clang { namespace clangd { /// A threadsafe flag that is initially clear. class Notification { public: // Sets the flag. No-op if already set. void notify(); // Blocks until flag is set. void wait() const; private: bool Notified = false; mutable std::condition_variable CV; mutable std::mutex Mu; }; /// Limits the number of threads that can acquire the lock at the same time. class Semaphore { public: Semaphore(std::size_t MaxLocks); bool try_lock(); void lock(); void unlock(); private: std::mutex Mutex; std::condition_variable SlotsChanged; std::size_t FreeSlots; }; /// A point in time we can wait for. /// Can be zero (don't wait) or infinity (wait forever). /// (Not time_point::max(), because many std::chrono implementations overflow). class Deadline { public: Deadline(std::chrono::steady_clock::time_point Time) : Type(Finite), Time(Time) {} static Deadline zero() { return Deadline(Zero); } static Deadline infinity() { return Deadline(Infinite); } std::chrono::steady_clock::time_point time() const { assert(Type == Finite); return Time; } bool expired() const { return (Type == Zero) || (Type == Finite && Time < std::chrono::steady_clock::now()); } bool operator==(const Deadline &Other) const { return (Type == Other.Type) && (Type != Finite || Time == Other.Time); } private: enum Type { Zero, Infinite, Finite }; Deadline(enum Type Type) : Type(Type) {} enum Type Type; std::chrono::steady_clock::time_point Time; }; /// Makes a deadline from a timeout in seconds. None means wait forever. Deadline timeoutSeconds(llvm::Optional Seconds); /// Wait once on CV for the specified duration. void wait(std::unique_lock &Lock, std::condition_variable &CV, Deadline D); /// Waits on a condition variable until F() is true or D expires. template LLVM_NODISCARD bool wait(std::unique_lock &Lock, std::condition_variable &CV, Deadline D, Func F) { while (!F()) { if (D.expired()) return false; wait(Lock, CV, D); } return true; } /// Runs tasks on separate (detached) threads and wait for all tasks to finish. /// Objects that need to spawn threads can own an AsyncTaskRunner to ensure they /// all complete on destruction. class AsyncTaskRunner { public: /// Destructor waits for all pending tasks to finish. ~AsyncTaskRunner(); void wait() const { (void)wait(Deadline::infinity()); } 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); private: mutable std::mutex Mutex; mutable std::condition_variable TasksReachedZero; std::size_t InFlightTasks = 0; }; /// Runs \p Action asynchronously with a new std::thread. The context will be /// propagated. template std::future runAsync(llvm::unique_function Action) { return std::async( std::launch::async, [](llvm::unique_function &&Action, Context Ctx) { WithContext WithCtx(std::move(Ctx)); return Action(); }, std::move(Action), Context::current().clone()); } /// Memoize is a cache to store and reuse computation results based on a key. /// /// Memoize> PrimeCache; /// for (int I : RepetitiveNumbers) /// if (PrimeCache.get(I, [&] { return expensiveIsPrime(I); })) /// llvm::errs() << "Prime: " << I << "\n"; /// /// The computation will only be run once for each key. /// This class is threadsafe. Concurrent calls for the same key may run the /// computation multiple times, but each call will return the same result. template class Memoize { mutable Container Cache; std::unique_ptr Mu; public: Memoize() : Mu(std::make_unique()) {} template typename Container::mapped_type get(T &&Key, Func Compute) const { { std::lock_guard Lock(*Mu); auto It = Cache.find(Key); if (It != Cache.end()) return It->second; } // Don't hold the mutex while computing. auto V = Compute(); { std::lock_guard Lock(*Mu); auto R = Cache.try_emplace(std::forward(Key), V); // Insert into cache may fail if we raced with another thread. if (!R.second) return R.first->second; // Canonical value, from other thread. } return V; } }; } // namespace clangd } // namespace clang #endif