#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/ADT/Optional.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/Errc.h"
+#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Threading.h"
#include <algorithm>
#include <memory>
#include <mutex>
#include <queue>
+#include <string>
#include <thread>
namespace clang {
};
namespace {
+/// 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<void(TUStatus &)> Mutator) {
+ std::lock_guard<std::mutex> Lock(StatusMu);
+ Mutator(Status);
+ emitStatusLocked();
+ }
+
+ /// Prevents emitting of further updates.
+ void stop() {
+ std::lock_guard<std::mutex> 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;
+};
+
/// Responsible for building and providing access to the preamble of a TU.
/// 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
class PreambleThread {
public:
PreambleThread(llvm::StringRef FileName, ParsingCallbacks &Callbacks,
- bool StorePreambleInMemory, bool RunSync)
+ bool StorePreambleInMemory, bool RunSync,
+ SynchronizedTUStatus &Status)
: FileName(FileName), Callbacks(Callbacks),
- StoreInMemory(StorePreambleInMemory), RunSync(RunSync) {}
+ StoreInMemory(StorePreambleInMemory), RunSync(RunSync), Status(Status) {
+ }
size_t getUsedBytes() const {
auto Preamble = latest();
void update(CompilerInvocation *CI, ParseInputs PI) {
// If compiler invocation was broken, just fail out early.
if (!CI) {
- TUStatus::BuildDetails Details;
- Details.BuildFailed = true;
- std::string TaskName = llvm::formatv("Update ({0})", PI.Version);
- emitTUStatus({TUAction::BuildingPreamble, std::move(TaskName)}, &Details);
// Make sure anyone waiting for the preamble gets notified it could not be
// built.
BuiltFirst.notify();
Request Req = {std::make_unique<CompilerInvocation>(*CI), std::move(PI)};
if (RunSync) {
build(std::move(Req));
+ Status.update([](TUStatus &Status) {
+ Status.PreambleActivity = PreambleAction::Idle;
+ });
return;
}
{
}
// Build the preamble and let the waiters know about it.
build(std::move(*CurrentReq));
+ bool IsEmpty = false;
{
std::lock_guard<std::mutex> Lock(Mutex);
CurrentReq.reset();
+ IsEmpty = !NextReq.hasValue();
+ }
+ 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();
}
return Done;
}
- /// Updates the TUStatus and emits it. Only called in the worker thread.
- void emitTUStatus(TUAction Action,
- const TUStatus::BuildDetails *Details = nullptr) {
- // Do not emit TU statuses when the worker is shutting down.
- if (isDone())
- return;
- TUStatus Status({std::move(Action), {}});
- if (Details)
- Status.Details = *Details;
- Callbacks.onFileUpdated(FileName, Status);
- }
-
/// Builds a preamble for Req and caches it. Might re-use the latest built
/// preamble if it is valid for Req. Also signals waiters about the build.
/// FIXME: We shouldn't cache failed preambles, if we've got a successful
std::shared_ptr<const PreambleData> OldPreamble =
Inputs.ForceRebuild ? nullptr : latest();
- std::string TaskName = llvm::formatv("Update ({0})", Inputs.Version);
- emitTUStatus({TUAction::BuildingPreamble, std::move(TaskName)});
+ Status.update([&](TUStatus &Status) {
+ Status.PreambleActivity = PreambleAction::Building;
+ });
auto Preamble = clang::clangd::buildPreamble(
FileName, std::move(*Req.CI), OldPreamble, Inputs, StoreInMemory,
ParsingCallbacks &Callbacks;
const bool StoreInMemory;
const bool RunSync;
+
+ SynchronizedTUStatus &Status;
};
class ASTWorkerHandle;
void startTask(llvm::StringRef Name, llvm::unique_function<void()> Task,
llvm::Optional<WantDiagnostics> UpdateType,
TUScheduler::ASTActionInvalidation);
- /// Updates the TUStatus and emits it. Only called in the worker thread.
- void emitTUStatus(TUAction FAction,
- const TUStatus::BuildDetails *Detail = nullptr);
/// Determines the next action to perform.
/// All actions that should never run are discarded.
const GlobalCompilationDatabase &CDB;
/// Callback invoked when preamble or main file AST is built.
ParsingCallbacks &Callbacks;
- /// Only accessed by the worker thread.
- TUStatus Status;
Semaphore &Barrier;
/// Whether the 'onMainAST' callback ran for the current FileInputs.
// any results to the user.
bool CanPublishResults = true; /* GUARDED_BY(PublishMu) */
+ SynchronizedTUStatus Status;
PreambleThread PW;
};
bool RunSync, DebouncePolicy UpdateDebounce,
bool StorePreamblesInMemory, ParsingCallbacks &Callbacks)
: IdleASTs(LRUCache), RunSync(RunSync), UpdateDebounce(UpdateDebounce),
- FileName(FileName), CDB(CDB),
- Callbacks(Callbacks), Status{TUAction(TUAction::Idle, ""),
- TUStatus::BuildDetails()},
- Barrier(Barrier), Done(false),
+ FileName(FileName), CDB(CDB), Callbacks(Callbacks), Barrier(Barrier),
+ Done(false), Status(FileName, Callbacks),
// FIXME: Run preambleworker async.
- PW(FileName, Callbacks, StorePreamblesInMemory, /*RunSync=*/true) {
+ PW(FileName, Callbacks, StorePreamblesInMemory, /*RunSync=*/true,
+ Status) {
auto Inputs = std::make_shared<ParseInputs>();
// Set a fallback command because compile command can be accessed before
// `Inputs` is initialized. Other fields are only used after initialization
// to the old preamble, so it can be freed if there are no other references
// to it.
OldPreamble.reset();
- emitTUStatus({TUAction::BuildingFile, TaskName});
+ Status.update([&](TUStatus &Status) {
+ Status.ASTActivity.K = ASTAction::Building;
+ Status.ASTActivity.Name = std::move(TaskName);
+ });
if (!CanReuseAST) {
IdleASTs.take(this); // Remove the old AST if it's still in cache.
} else {
// current file at this point?
log("Skipping rebuild of the AST for {0}, inputs are the same.",
FileName);
- TUStatus::BuildDetails Details;
- Details.ReuseAST = true;
- emitTUStatus({TUAction::BuildingFile, TaskName}, &Details);
+
+ Status.update([](TUStatus &Status) {
+ Status.Details.ReuseAST = true;
+ Status.Details.BuildFailed = false;
+ });
return;
}
}
llvm::Optional<ParsedAST> NewAST =
buildAST(FileName, std::move(Invocation), CompilerInvocationDiags,
Inputs, NewPreamble);
+ // buildAST fails.
+ Status.update([&](TUStatus &Status) {
+ Status.Details.ReuseAST = false;
+ Status.Details.BuildFailed = !NewAST.hasValue();
+ });
AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
- if (!(*AST)) { // buildAST fails.
- TUStatus::BuildDetails Details;
- Details.BuildFailed = true;
- emitTUStatus({TUAction::BuildingFile, TaskName}, &Details);
- }
} else {
// We are reusing the AST.
- TUStatus::BuildDetails Details;
- Details.ReuseAST = true;
- emitTUStatus({TUAction::BuildingFile, TaskName}, &Details);
+ Status.update([](TUStatus &Status) {
+ Status.Details.ReuseAST = true;
+ Status.Details.BuildFailed = false;
+ });
}
// We want to report the diagnostics even if this update was cancelled.
assert(!Done && "stop() called twice");
Done = true;
}
+ Status.stop();
RequestsCV.notify_all();
}
RequestsCV.notify_all();
}
-void ASTWorker::emitTUStatus(TUAction Action,
- const TUStatus::BuildDetails *Details) {
- Status.Action = std::move(Action);
- if (Details)
- Status.Details = *Details;
- std::lock_guard<std::mutex> Lock(PublishMu);
- // Do not emit TU statuses when the ASTWorker is shutting down.
- if (CanPublishResults) {
- Callbacks.onFileUpdated(FileName, Status);
- }
-}
-
void ASTWorker::run() {
auto _ = llvm::make_scope_exit([this] { PW.stop(); });
while (true) {
Tracer.emplace("Debounce");
SPAN_ATTACH(*Tracer, "next_request", Requests.front().Name);
if (!(Wait == Deadline::infinity())) {
- emitTUStatus({TUAction::Queued, Requests.front().Name});
+ Status.update([&](TUStatus &Status) {
+ Status.ASTActivity.K = ASTAction::Queued;
+ Status.ASTActivity.Name = Requests.front().Name;
+ });
SPAN_ATTACH(*Tracer, "sleep_ms",
std::chrono::duration_cast<std::chrono::milliseconds>(
Wait.time() - steady_clock::now())
{
std::unique_lock<Semaphore> Lock(Barrier, std::try_to_lock);
if (!Lock.owns_lock()) {
- emitTUStatus({TUAction::Queued, CurrentRequest->Name});
+ Status.update([&](TUStatus &Status) {
+ Status.ASTActivity.K = ASTAction::Queued;
+ Status.ASTActivity.Name = CurrentRequest->Name;
+ });
Lock.lock();
}
WithContext Guard(std::move(CurrentRequest->Ctx));
trace::Span Tracer(CurrentRequest->Name);
- emitTUStatus({TUAction::RunningAction, CurrentRequest->Name});
+ Status.update([&](TUStatus &Status) {
+ Status.ASTActivity.K = ASTAction::RunningAction;
+ Status.ASTActivity.Name = CurrentRequest->Name;
+ });
CurrentRequest->Action();
}
CurrentRequest.reset();
IsEmpty = Requests.empty();
}
- if (IsEmpty)
- emitTUStatus({TUAction::Idle, /*Name*/ ""});
+ if (IsEmpty) {
+ Status.update([&](TUStatus &Status) {
+ Status.ASTActivity.K = ASTAction::Idle;
+ Status.ASTActivity.Name = "";
+ });
+ }
RequestsCV.notify_all();
}
}
// 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 TUAction &Action) {
- std::string Result;
- llvm::raw_string_ostream OS(Result);
- switch (Action.S) {
- case TUAction::Queued:
- OS << "file is queued";
+std::string renderTUAction(const PreambleAction PA, const ASTAction &AA) {
+ llvm::SmallVector<std::string, 2> Result;
+ switch (PA) {
+ case PreambleAction::Building:
+ Result.push_back("parsing includes");
break;
- case TUAction::RunningAction:
- OS << "running " << Action.Name;
+ case PreambleAction::Idle:
+ // We handle idle specially below.
+ break;
+ }
+ switch (AA.K) {
+ case ASTAction::Queued:
+ Result.push_back("file is queued");
break;
- case TUAction::BuildingPreamble:
- OS << "parsing includes";
+ case ASTAction::RunningAction:
+ Result.push_back("running " + AA.Name);
break;
- case TUAction::BuildingFile:
- OS << "parsing main file";
+ case ASTAction::Building:
+ Result.push_back("parsing main file");
break;
- case TUAction::Idle:
- OS << "idle";
+ case ASTAction::Idle:
+ // We handle idle specially below.
break;
}
- return OS.str();
+ if (Result.empty())
+ return "idle";
+ return llvm::join(Result, ",");
}
} // namespace
FileStatus TUStatus::render(PathRef File) const {
FileStatus FStatus;
FStatus.uri = URIForFile::canonicalize(File, /*TUPath=*/File);
- FStatus.state = renderTUAction(Action);
+ FStatus.state = renderTUAction(PreambleActivity, ASTActivity);
return FStatus;
}
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <algorithm>
+#include <bits/stdint-uintn.h>
#include <chrono>
#include <utility>
using ::testing::Pointee;
using ::testing::UnorderedElementsAre;
-MATCHER_P2(TUState, State, ActionName, "") {
- if (arg.Action.S != State) {
- *result_listener << "state is " << arg.Action.S;
+MATCHER_P2(TUState, PreambleActivity, ASTActivity, "") {
+ if (arg.PreambleActivity != PreambleActivity) {
+ *result_listener << "preamblestate is "
+ << static_cast<uint8_t>(arg.PreambleActivity);
return false;
}
- if (arg.Action.Name != ActionName) {
- *result_listener << "name is " << arg.Action.Name;
+ if (arg.ASTActivity.K != ASTActivity) {
+ *result_listener << "aststate is " << arg.ASTActivity.K;
return false;
}
return true;
// Update the source contents, which should trigger an initial build with
// the header file missing.
- updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes,
- [](std::vector<Diag> Diags) {
- EXPECT_THAT(
- Diags,
- ElementsAre(
- Field(&Diag::Message, "'foo.h' file not found"),
- Field(&Diag::Message, "use of undeclared identifier 'a'")));
- });
+ updateWithDiags(
+ S, Source, Inputs, WantDiagnostics::Yes, [](std::vector<Diag> Diags) {
+ EXPECT_THAT(Diags,
+ ElementsAre(Field(&Diag::Message, "'foo.h' file not found"),
+ Field(&Diag::Message,
+ "use of undeclared identifier 'a'")));
+ });
// Add the header file. We need to recreate the inputs since we changed a
// file from underneath the test FS.
// The addition of the missing header file shouldn't trigger a rebuild since
// we don't track missing files.
- updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes,
- [](std::vector<Diag> Diags) {
- ADD_FAILURE() << "Did not expect diagnostics for missing header update";
- });
+ updateWithDiags(
+ S, Source, Inputs, WantDiagnostics::Yes, [](std::vector<Diag> Diags) {
+ ADD_FAILURE() << "Did not expect diagnostics for missing header update";
+ });
// Forcing the reload should should cause a rebuild which no longer has any
// errors.
Inputs.ForceRebuild = true;
- updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes,
- [](std::vector<Diag> Diags) {
- EXPECT_THAT(Diags, IsEmpty());
- });
+ updateWithDiags(
+ S, Source, Inputs, WantDiagnostics::Yes,
+ [](std::vector<Diag> Diags) { EXPECT_THAT(Diags, IsEmpty()); });
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
}
EXPECT_THAT(CaptureTUStatus.allStatus(),
ElementsAre(
- // Statuses of "Update" action.
- TUState(TUAction::RunningAction, "Update (1)"),
- TUState(TUAction::BuildingPreamble, "Update (1)"),
- TUState(TUAction::BuildingFile, "Update (1)"),
-
- // Statuses of "Definitions" action
- TUState(TUAction::RunningAction, "Definitions"),
- TUState(TUAction::Idle, /*No action*/ "")));
+ // Everything starts with ASTWorker starting to execute an
+ // update
+ TUState(PreambleAction::Idle, ASTAction::RunningAction),
+ // We build the preamble
+ TUState(PreambleAction::Building, ASTAction::RunningAction),
+ // Preamble worker goes idle
+ TUState(PreambleAction::Idle, ASTAction::RunningAction),
+ // We start building the ast
+ TUState(PreambleAction::Idle, ASTAction::Building),
+ // Built finished succesffully
+ TUState(PreambleAction::Idle, ASTAction::Building),
+ // Rnning go to def
+ TUState(PreambleAction::Idle, ASTAction::RunningAction),
+ // both workers go idle
+ TUState(PreambleAction::Idle, ASTAction::Idle)));
}
TEST_F(TUSchedulerTests, CommandLineErrors) {
TUScheduler S(CDB, optsForTest(), captureDiags());
std::vector<Diag> Diagnostics;
updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
- WantDiagnostics::Yes,
- [&](std::vector<Diag> D) {
+ WantDiagnostics::Yes, [&](std::vector<Diag> D) {
Diagnostics = std::move(D);
Ready.notify();
});
TUScheduler S(CDB, optsForTest(), captureDiags());
std::vector<Diag> Diagnostics;
updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
- WantDiagnostics::Yes,
- [&](std::vector<Diag> D) {
+ WantDiagnostics::Yes, [&](std::vector<Diag> D) {
Diagnostics = std::move(D);
Ready.notify();
});