2c675be9b2
This is part of an effort to migrate from llvm::Optional to std::optional: https://discourse.llvm.org/t/deprecating-llvm-optional-x-hasvalue-getvalue-getvalueor/63716
207 lines
6.2 KiB
C++
207 lines
6.2 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 <optional>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
|
|
/// 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(std::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>
|
|
[[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;
|
|
}
|
|
|
|
/// 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 { (void)wait(Deadline::infinity()); }
|
|
[[nodiscard]] bool wait(Deadline D) const;
|
|
|
|
private:
|
|
bool Notified = false;
|
|
mutable std::condition_variable CV;
|
|
mutable std::mutex Mu;
|
|
};
|
|
|
|
/// 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()); }
|
|
[[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
|