Skip to content

Commit

Permalink
[clangd] Use multiple working threads in clangd.
Browse files Browse the repository at this point in the history
Reviewers: bkramer, krasimir, klimek

Reviewed By: klimek

Subscribers: arphaman, cfe-commits

Differential Revision: https://reviews.llvm.org/D36261

llvm-svn: 310821
  • Loading branch information
ilya-biryukov committed Aug 14, 2017
1 parent a7c80a4 commit db8b2d7
Show file tree
Hide file tree
Showing 6 changed files with 407 additions and 100 deletions.
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/ClangdLSPServer.cpp
Expand Up @@ -220,10 +220,10 @@ void ClangdLSPServer::LSPProtocolCallbacks::onGoToDefinition(
R"(,"result":[)" + Locations + R"(]})");
}

ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously,
ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
llvm::Optional<StringRef> ResourceDir)
: Out(Out), DiagConsumer(*this),
Server(CDB, DiagConsumer, FSProvider, RunSynchronously, ResourceDir) {}
Server(CDB, DiagConsumer, FSProvider, AsyncThreadsCount, ResourceDir) {}

void ClangdLSPServer::run(std::istream &In) {
assert(!IsDone && "Run was called before");
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/ClangdLSPServer.h
Expand Up @@ -26,7 +26,7 @@ class JSONOutput;
/// dispatch and ClangdServer together.
class ClangdLSPServer {
public:
ClangdLSPServer(JSONOutput &Out, bool RunSynchronously,
ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
llvm::Optional<StringRef> ResourceDir);

/// Run LSP server loop, receiving input for it from \p In. \p In must be
Expand Down
80 changes: 47 additions & 33 deletions clang-tools-extra/clangd/ClangdServer.cpp
Expand Up @@ -78,40 +78,52 @@ RealFileSystemProvider::getTaggedFileSystem(PathRef File) {
return make_tagged(vfs::getRealFileSystem(), VFSTag());
}

ClangdScheduler::ClangdScheduler(bool RunSynchronously)
: RunSynchronously(RunSynchronously) {
unsigned clangd::getDefaultAsyncThreadsCount() {
unsigned HardwareConcurrency = std::thread::hardware_concurrency();
// C++ standard says that hardware_concurrency()
// may return 0, fallback to 1 worker thread in
// that case.
if (HardwareConcurrency == 0)
return 1;
return HardwareConcurrency;
}

ClangdScheduler::ClangdScheduler(unsigned AsyncThreadsCount)
: RunSynchronously(AsyncThreadsCount == 0) {
if (RunSynchronously) {
// Don't start the worker thread if we're running synchronously
return;
}

// Initialize Worker in ctor body, rather than init list to avoid potentially
// using not-yet-initialized members
Worker = std::thread([this]() {
while (true) {
std::future<void> Request;

// Pick request from the queue
{
std::unique_lock<std::mutex> Lock(Mutex);
// Wait for more requests.
RequestCV.wait(Lock, [this] { return !RequestQueue.empty() || Done; });
if (Done)
return;

assert(!RequestQueue.empty() && "RequestQueue was empty");

// We process requests starting from the front of the queue. Users of
// ClangdScheduler have a way to prioritise their requests by putting
// them to the either side of the queue (using either addToEnd or
// addToFront).
Request = std::move(RequestQueue.front());
RequestQueue.pop_front();
} // unlock Mutex

Request.get();
}
});
Workers.reserve(AsyncThreadsCount);
for (unsigned I = 0; I < AsyncThreadsCount; ++I) {
Workers.push_back(std::thread([this]() {
while (true) {
std::future<void> Request;

// Pick request from the queue
{
std::unique_lock<std::mutex> Lock(Mutex);
// Wait for more requests.
RequestCV.wait(Lock,
[this] { return !RequestQueue.empty() || Done; });
if (Done)
return;

assert(!RequestQueue.empty() && "RequestQueue was empty");

// We process requests starting from the front of the queue. Users of
// ClangdScheduler have a way to prioritise their requests by putting
// them to the either side of the queue (using either addToEnd or
// addToFront).
Request = std::move(RequestQueue.front());
RequestQueue.pop_front();
} // unlock Mutex

Request.get();
}
}));
}
}

ClangdScheduler::~ClangdScheduler() {
Expand All @@ -123,19 +135,21 @@ ClangdScheduler::~ClangdScheduler() {
// Wake up the worker thread
Done = true;
} // unlock Mutex
RequestCV.notify_one();
Worker.join();
RequestCV.notify_all();

for (auto &Worker : Workers)
Worker.join();
}

ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB,
DiagnosticsConsumer &DiagConsumer,
FileSystemProvider &FSProvider,
bool RunSynchronously,
unsigned AsyncThreadsCount,
llvm::Optional<StringRef> ResourceDir)
: CDB(CDB), DiagConsumer(DiagConsumer), FSProvider(FSProvider),
ResourceDir(ResourceDir ? ResourceDir->str() : getStandardResourceDir()),
PCHs(std::make_shared<PCHContainerOperations>()),
WorkScheduler(RunSynchronously) {}
WorkScheduler(AsyncThreadsCount) {}

std::future<void> ClangdServer::addDocument(PathRef File, StringRef Contents) {
DocVersion Version = DraftMgr.updateDraft(File, Contents);
Expand Down
51 changes: 28 additions & 23 deletions clang-tools-extra/clangd/ClangdServer.h
Expand Up @@ -101,11 +101,20 @@ class RealFileSystemProvider : public FileSystemProvider {

class ClangdServer;

/// Handles running WorkerRequests of ClangdServer on a separate threads.
/// Currently runs only one worker thread.
/// Returns a number of a default async threads to use for ClangdScheduler.
/// Returned value is always >= 1 (i.e. will not cause requests to be processed
/// synchronously).
unsigned getDefaultAsyncThreadsCount();

/// Handles running WorkerRequests of ClangdServer on a number of worker
/// threads.
class ClangdScheduler {
public:
ClangdScheduler(bool RunSynchronously);
/// If \p AsyncThreadsCount is 0, requests added using addToFront and addToEnd
/// will be processed synchronously on the calling thread.
// Otherwise, \p AsyncThreadsCount threads will be created to schedule the
// requests.
ClangdScheduler(unsigned AsyncThreadsCount);
~ClangdScheduler();

/// Add a new request to run function \p F with args \p As to the start of the
Expand Down Expand Up @@ -146,17 +155,16 @@ class ClangdScheduler {
private:
bool RunSynchronously;
std::mutex Mutex;
/// We run some tasks on a separate threads(parsing, CppFile cleanup).
/// This thread looks into RequestQueue to find requests to handle and
/// terminates when Done is set to true.
std::thread Worker;
/// Setting Done to true will make the worker thread terminate.
/// We run some tasks on separate threads(parsing, CppFile cleanup).
/// These threads looks into RequestQueue to find requests to handle and
/// terminate when Done is set to true.
std::vector<std::thread> Workers;
/// Setting Done to true will make the worker threads terminate.
bool Done = false;
/// A queue of requests.
/// FIXME(krasimir): code completion should always have priority over parsing
/// for diagnostics.
/// A queue of requests. Elements of this vector are async computations (i.e.
/// results of calling std::async(std::launch::deferred, ...)).
std::deque<std::future<void>> RequestQueue;
/// Condition variable to wake up the worker thread.
/// Condition variable to wake up worker threads.
std::condition_variable RequestCV;
};

Expand All @@ -165,22 +173,19 @@ class ClangdScheduler {
/// diagnostics for tracked files).
class ClangdServer {
public:
/// Creates a new ClangdServer. If \p RunSynchronously is false, no worker
/// thread will be created and all requests will be completed synchronously on
/// the calling thread (this is mostly used for tests). If \p RunSynchronously
/// is true, a worker thread will be created to parse files in the background
/// and provide diagnostics results via DiagConsumer.onDiagnosticsReady
/// callback. File accesses for each instance of parsing will be conducted via
/// a vfs::FileSystem provided by \p FSProvider. Results of code
/// completion/diagnostics also include a tag, that \p FSProvider returns
/// along with the vfs::FileSystem.
/// When \p ResourceDir is set, it will be used to search for internal headers
/// Creates a new ClangdServer. To server parsing requests ClangdScheduler,
/// that spawns \p AsyncThreadsCount worker threads will be created (when \p
/// AsyncThreadsCount is 0, requests will be processed on the calling thread).
/// instance of parsing will be conducted via a vfs::FileSystem provided by \p
/// FSProvider. Results of code completion/diagnostics also include a tag,
/// that \p FSProvider returns along with the vfs::FileSystem. When \p
/// ResourceDir is set, it will be used to search for internal headers
/// (overriding defaults and -resource-dir compiler flag, if set). If \p
/// ResourceDir is None, ClangdServer will attempt to set it to a standard
/// location, obtained via CompilerInvocation::GetResourcePath.
ClangdServer(GlobalCompilationDatabase &CDB,
DiagnosticsConsumer &DiagConsumer,
FileSystemProvider &FSProvider, bool RunSynchronously,
FileSystemProvider &FSProvider, unsigned AsyncThreadsCount,
llvm::Optional<StringRef> ResourceDir = llvm::None);

/// Add a \p File to the list of tracked C++ files or update the contents if
Expand Down
28 changes: 23 additions & 5 deletions clang-tools-extra/clangd/tool/ClangdMain.cpp
Expand Up @@ -16,14 +16,20 @@
#include <iostream>
#include <memory>
#include <string>
#include <thread>

using namespace clang;
using namespace clang::clangd;

static llvm::cl::opt<bool>
RunSynchronously("run-synchronously",
llvm::cl::desc("Parse on main thread"),
llvm::cl::init(false), llvm::cl::Hidden);
static llvm::cl::opt<unsigned>
WorkerThreadsCount("j",
llvm::cl::desc("Number of async workers used by clangd"),
llvm::cl::init(getDefaultAsyncThreadsCount()));

static llvm::cl::opt<bool> RunSynchronously(
"run-synchronously",
llvm::cl::desc("Parse on main thread. If set, -j is ignored"),
llvm::cl::init(false), llvm::cl::Hidden);

static llvm::cl::opt<std::string>
ResourceDir("resource-dir",
Expand All @@ -33,6 +39,17 @@ static llvm::cl::opt<std::string>
int main(int argc, char *argv[]) {
llvm::cl::ParseCommandLineOptions(argc, argv, "clangd");

if (!RunSynchronously && WorkerThreadsCount == 0) {
llvm::errs() << "A number of worker threads cannot be 0. Did you mean to "
"specify -run-synchronously?";
return 1;
}

// Ignore -j option if -run-synchonously is used.
// FIXME: a warning should be shown here.
if (RunSynchronously)
WorkerThreadsCount = 0;

llvm::raw_ostream &Outs = llvm::outs();
llvm::raw_ostream &Logs = llvm::errs();
JSONOutput Out(Outs, Logs);
Expand All @@ -43,6 +60,7 @@ int main(int argc, char *argv[]) {
llvm::Optional<StringRef> ResourceDirRef = None;
if (!ResourceDir.empty())
ResourceDirRef = ResourceDir;
ClangdLSPServer LSPServer(Out, RunSynchronously, ResourceDirRef);

ClangdLSPServer LSPServer(Out, WorkerThreadsCount, ResourceDirRef);
LSPServer.run(std::cin);
}

0 comments on commit db8b2d7

Please sign in to comment.