205 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===--- 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 <atomic>
 | |
| #include <cassert>
 | |
| #include <condition_variable>
 | |
| #include <future>
 | |
| #include <memory>
 | |
| #include <mutex>
 | |
| #include <thread>
 | |
| #include <vector>
 | |
| 
 | |
| 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<double> Seconds);
 | |
| /// Wait once on CV for the specified duration.
 | |
| void wait(std::unique_lock<std::mutex> &Lock, std::condition_variable &CV,
 | |
|           Deadline D);
 | |
| /// Waits on a condition variable until F() is true or D expires.
 | |
| template <typename Func>
 | |
| LLVM_NODISCARD bool wait(std::unique_lock<std::mutex> &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<void()> 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 <typename T>
 | |
| std::future<T> runAsync(llvm::unique_function<T()> Action) {
 | |
|   return std::async(
 | |
|       std::launch::async,
 | |
|       [](llvm::unique_function<T()> &&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<DenseMap<int, bool>> 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 <typename Container> class Memoize {
 | |
|   mutable Container Cache;
 | |
|   std::unique_ptr<std::mutex> Mu;
 | |
| 
 | |
| public:
 | |
|   Memoize() : Mu(std::make_unique<std::mutex>()) {}
 | |
| 
 | |
|   template <typename T, typename Func>
 | |
|   typename Container::mapped_type get(T &&Key, Func Compute) const {
 | |
|     {
 | |
|       std::lock_guard<std::mutex> 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<std::mutex> Lock(*Mu);
 | |
|       auto R = Cache.try_emplace(std::forward<T>(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;
 | |
|   }
 | |
| };
 | |
| 
 | |
| /// Used to guard an operation that should run at most every N seconds.
 | |
| ///
 | |
| /// Usage:
 | |
| ///   mutable PeriodicThrottler ShouldLog(std::chrono::seconds(1));
 | |
| ///   void calledFrequently() {
 | |
| ///     if (ShouldLog())
 | |
| ///       log("this is not spammy");
 | |
| ///   }
 | |
| ///
 | |
| /// This class is threadsafe. If multiple threads are involved, then the guarded
 | |
| /// operation still needs to be threadsafe!
 | |
| class PeriodicThrottler {
 | |
|   using Stopwatch = std::chrono::steady_clock;
 | |
|   using Rep = Stopwatch::duration::rep;
 | |
| 
 | |
|   Rep Period;
 | |
|   std::atomic<Rep> Next;
 | |
| 
 | |
| public:
 | |
|   /// If Period is zero, the throttler will return true every time.
 | |
|   PeriodicThrottler(Stopwatch::duration Period, Stopwatch::duration Delay = {})
 | |
|       : Period(Period.count()),
 | |
|         Next((Stopwatch::now() + Delay).time_since_epoch().count()) {}
 | |
| 
 | |
|   /// Returns whether the operation should run at this time.
 | |
|   /// operator() is safe to call concurrently.
 | |
|   bool operator()();
 | |
| };
 | |
| 
 | |
| } // namespace clangd
 | |
| } // namespace clang
 | |
| #endif
 |