2018-01-31 16:51:16 +08:00
|
|
|
//===-- TUSchedulerTests.cpp ------------------------------------*- C++ -*-===//
|
|
|
|
//
|
2019-01-19 16:50:56 +08:00
|
|
|
// 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
|
2018-01-31 16:51:16 +08:00
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2018-12-06 17:41:04 +08:00
|
|
|
#include "Annotations.h"
|
[clangd] Pass Context implicitly using TLS.
Summary:
Instead of passing Context explicitly around, we now have a thread-local
Context object `Context::current()` which is an implicit argument to
every function.
Most manipulation of this should use the WithContextValue helper, which
augments the current Context to add a single KV pair, and restores the
old context on destruction.
Advantages are:
- less boilerplate in functions that just propagate contexts
- reading most code doesn't require understanding context at all, and
using context as values in fewer places still
- fewer options to pass the "wrong" context when it changes within a
scope (e.g. when using Span)
- contexts pass through interfaces we can't modify, such as VFS
- propagating contexts across threads was slightly tricky (e.g.
copy vs move, no move-init in lambdas), and is now encapsulated in
the threadpool
Disadvantages are all the usual TLS stuff - hidden magic, and
potential for higher memory usage on threads that don't use the
context. (In practice, it's just one pointer)
Reviewers: ilya-biryukov
Subscribers: klimek, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D42517
llvm-svn: 323872
2018-01-31 21:40:48 +08:00
|
|
|
#include "Context.h"
|
2019-07-19 21:51:01 +08:00
|
|
|
#include "Diagnostics.h"
|
2018-11-23 00:20:12 +08:00
|
|
|
#include "Matchers.h"
|
2019-09-04 17:46:06 +08:00
|
|
|
#include "ParsedAST.h"
|
2019-07-19 21:51:01 +08:00
|
|
|
#include "Path.h"
|
2019-09-04 15:35:00 +08:00
|
|
|
#include "Preamble.h"
|
2018-01-31 16:51:16 +08:00
|
|
|
#include "TUScheduler.h"
|
|
|
|
#include "TestFS.h"
|
[clangd] Surface errors from command-line parsing
Summary:
Those errors are exposed at the first character of a file,
for a lack of a better place.
Previously, all errors were stored inside the AST and report
accordingly. However, errors in command-line argument parsing could
result in failure to produce the AST, so we need an alternative ways to
report those errors.
We take the following approach in this patch:
- buildCompilerInvocation() now requires an explicit DiagnosticConsumer.
- TUScheduler and TestTU now collect the diagnostics produced when
parsing command line arguments.
If pasing of the AST failed, diagnostics are reported via a new
ParsingCallbacks::onFailedAST method.
If parsing of the AST succeeded, any errors produced during
command-line parsing are stored alongside the AST inside the
ParsedAST instance and reported as previously by calling the
ParsingCallbacks::onMainAST method;
- The client code that uses ClangdServer's DiagnosticConsumer
does not need to change, it will receive new diagnostics in the
onDiagnosticsReady() callback
Errors produced when parsing command-line arguments are collected using
the same StoreDiags class that is used to collect all other errors. They
are recognized by their location being invalid. IIUC, the location is
invalid as there is no source manager at this point, it is created at a
later stage.
Although technically we might also get diagnostics that mention the
command-line arguments FileID with after the source manager was created
(and they have valid source locations), we choose to not handle those
and they are dropped as not coming from the main file. AFAICT, those
diagnostics should always be notes, therefore it's safe to drop them
without loosing too much information.
Reviewers: kadircet
Reviewed By: kadircet
Subscribers: nridge, javed.absar, MaskRay, jkorous, arphaman, cfe-commits, gribozavr
Tags: #clang
Differential Revision: https://reviews.llvm.org/D66759
llvm-svn: 370177
2019-08-28 17:24:55 +08:00
|
|
|
#include "Threading.h"
|
|
|
|
#include "clang/Basic/DiagnosticDriver.h"
|
|
|
|
#include "llvm/ADT/ArrayRef.h"
|
2019-07-19 21:51:01 +08:00
|
|
|
#include "llvm/ADT/STLExtras.h"
|
2018-12-06 17:41:04 +08:00
|
|
|
#include "llvm/ADT/ScopeExit.h"
|
2019-01-03 21:28:05 +08:00
|
|
|
#include "gmock/gmock.h"
|
2018-01-31 16:51:16 +08:00
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
namespace clang {
|
|
|
|
namespace clangd {
|
[clangd] Add callbacks on parsed AST in addition to parsed preambles
Summary:
Will be used for updating the dynamic index on updates to the open files.
Currently we collect only information coming from the preamble
AST. This has a bunch of limitations:
- Dynamic index misses important information from the body of the
file, e.g. locations of definitions.
- XRefs cannot be collected at all, since we can only obtain full
information for the current file (preamble is parsed with skipped
function bodies, therefore not reliable).
This patch only adds the new callback, actually updates to the index
will be done in a follow-up patch.
Reviewers: hokein
Reviewed By: hokein
Subscribers: kadircet, javed.absar, ioeric, MaskRay, jkorous, arphaman, cfe-commits
Differential Revision: https://reviews.llvm.org/D50847
llvm-svn: 340401
2018-08-22 19:39:16 +08:00
|
|
|
namespace {
|
2018-01-31 16:51:16 +08:00
|
|
|
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
using ::testing::AnyOf;
|
[clangd] Add callbacks on parsed AST in addition to parsed preambles
Summary:
Will be used for updating the dynamic index on updates to the open files.
Currently we collect only information coming from the preamble
AST. This has a bunch of limitations:
- Dynamic index misses important information from the body of the
file, e.g. locations of definitions.
- XRefs cannot be collected at all, since we can only obtain full
information for the current file (preamble is parsed with skipped
function bodies, therefore not reliable).
This patch only adds the new callback, actually updates to the index
will be done in a follow-up patch.
Reviewers: hokein
Reviewed By: hokein
Subscribers: kadircet, javed.absar, ioeric, MaskRay, jkorous, arphaman, cfe-commits
Differential Revision: https://reviews.llvm.org/D50847
llvm-svn: 340401
2018-08-22 19:39:16 +08:00
|
|
|
using ::testing::Each;
|
2018-08-30 23:07:34 +08:00
|
|
|
using ::testing::ElementsAre;
|
[clangd] Surface errors from command-line parsing
Summary:
Those errors are exposed at the first character of a file,
for a lack of a better place.
Previously, all errors were stored inside the AST and report
accordingly. However, errors in command-line argument parsing could
result in failure to produce the AST, so we need an alternative ways to
report those errors.
We take the following approach in this patch:
- buildCompilerInvocation() now requires an explicit DiagnosticConsumer.
- TUScheduler and TestTU now collect the diagnostics produced when
parsing command line arguments.
If pasing of the AST failed, diagnostics are reported via a new
ParsingCallbacks::onFailedAST method.
If parsing of the AST succeeded, any errors produced during
command-line parsing are stored alongside the AST inside the
ParsedAST instance and reported as previously by calling the
ParsingCallbacks::onMainAST method;
- The client code that uses ClangdServer's DiagnosticConsumer
does not need to change, it will receive new diagnostics in the
onDiagnosticsReady() callback
Errors produced when parsing command-line arguments are collected using
the same StoreDiags class that is used to collect all other errors. They
are recognized by their location being invalid. IIUC, the location is
invalid as there is no source manager at this point, it is created at a
later stage.
Although technically we might also get diagnostics that mention the
command-line arguments FileID with after the source manager was created
(and they have valid source locations), we choose to not handle those
and they are dropped as not coming from the main file. AFAICT, those
diagnostics should always be notes, therefore it's safe to drop them
without loosing too much information.
Reviewers: kadircet
Reviewed By: kadircet
Subscribers: nridge, javed.absar, MaskRay, jkorous, arphaman, cfe-commits, gribozavr
Tags: #clang
Differential Revision: https://reviews.llvm.org/D66759
llvm-svn: 370177
2019-08-28 17:24:55 +08:00
|
|
|
using ::testing::Eq;
|
|
|
|
using ::testing::Field;
|
|
|
|
using ::testing::IsEmpty;
|
2018-01-31 16:51:16 +08:00
|
|
|
using ::testing::Pointee;
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
using ::testing::UnorderedElementsAre;
|
2018-01-31 16:51:16 +08:00
|
|
|
|
2018-12-06 17:41:04 +08:00
|
|
|
MATCHER_P2(TUState, State, ActionName, "") {
|
|
|
|
return arg.Action.S == State && arg.Action.Name == ActionName;
|
|
|
|
}
|
|
|
|
|
2018-01-31 16:51:16 +08:00
|
|
|
class TUSchedulerTests : public ::testing::Test {
|
|
|
|
protected:
|
|
|
|
ParseInputs getInputs(PathRef File, std::string Contents) {
|
2019-01-28 22:01:55 +08:00
|
|
|
ParseInputs Inputs;
|
|
|
|
Inputs.CompileCommand = *CDB.getCompileCommand(File);
|
|
|
|
Inputs.FS = buildTestFS(Files, Timestamps);
|
|
|
|
Inputs.Contents = std::move(Contents);
|
|
|
|
Inputs.Opts = ParseOptions();
|
|
|
|
return Inputs;
|
2018-01-31 16:51:16 +08:00
|
|
|
}
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
void updateWithCallback(TUScheduler &S, PathRef File,
|
|
|
|
llvm::StringRef Contents, WantDiagnostics WD,
|
2018-11-23 01:27:08 +08:00
|
|
|
llvm::unique_function<void()> CB) {
|
|
|
|
WithContextValue Ctx(llvm::make_scope_exit(std::move(CB)));
|
|
|
|
S.update(File, getInputs(File, Contents), WD);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
|
|
|
|
DiagsCallbackKey;
|
|
|
|
|
|
|
|
/// A diagnostics callback that should be passed to TUScheduler when it's used
|
|
|
|
/// in updateWithDiags.
|
|
|
|
static std::unique_ptr<ParsingCallbacks> captureDiags() {
|
|
|
|
class CaptureDiags : public ParsingCallbacks {
|
[clangd] Surface errors from command-line parsing
Summary:
Those errors are exposed at the first character of a file,
for a lack of a better place.
Previously, all errors were stored inside the AST and report
accordingly. However, errors in command-line argument parsing could
result in failure to produce the AST, so we need an alternative ways to
report those errors.
We take the following approach in this patch:
- buildCompilerInvocation() now requires an explicit DiagnosticConsumer.
- TUScheduler and TestTU now collect the diagnostics produced when
parsing command line arguments.
If pasing of the AST failed, diagnostics are reported via a new
ParsingCallbacks::onFailedAST method.
If parsing of the AST succeeded, any errors produced during
command-line parsing are stored alongside the AST inside the
ParsedAST instance and reported as previously by calling the
ParsingCallbacks::onMainAST method;
- The client code that uses ClangdServer's DiagnosticConsumer
does not need to change, it will receive new diagnostics in the
onDiagnosticsReady() callback
Errors produced when parsing command-line arguments are collected using
the same StoreDiags class that is used to collect all other errors. They
are recognized by their location being invalid. IIUC, the location is
invalid as there is no source manager at this point, it is created at a
later stage.
Although technically we might also get diagnostics that mention the
command-line arguments FileID with after the source manager was created
(and they have valid source locations), we choose to not handle those
and they are dropped as not coming from the main file. AFAICT, those
diagnostics should always be notes, therefore it's safe to drop them
without loosing too much information.
Reviewers: kadircet
Reviewed By: kadircet
Subscribers: nridge, javed.absar, MaskRay, jkorous, arphaman, cfe-commits, gribozavr
Tags: #clang
Differential Revision: https://reviews.llvm.org/D66759
llvm-svn: 370177
2019-08-28 17:24:55 +08:00
|
|
|
public:
|
2019-07-19 21:51:01 +08:00
|
|
|
void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish) override {
|
[clangd] Surface errors from command-line parsing
Summary:
Those errors are exposed at the first character of a file,
for a lack of a better place.
Previously, all errors were stored inside the AST and report
accordingly. However, errors in command-line argument parsing could
result in failure to produce the AST, so we need an alternative ways to
report those errors.
We take the following approach in this patch:
- buildCompilerInvocation() now requires an explicit DiagnosticConsumer.
- TUScheduler and TestTU now collect the diagnostics produced when
parsing command line arguments.
If pasing of the AST failed, diagnostics are reported via a new
ParsingCallbacks::onFailedAST method.
If parsing of the AST succeeded, any errors produced during
command-line parsing are stored alongside the AST inside the
ParsedAST instance and reported as previously by calling the
ParsingCallbacks::onMainAST method;
- The client code that uses ClangdServer's DiagnosticConsumer
does not need to change, it will receive new diagnostics in the
onDiagnosticsReady() callback
Errors produced when parsing command-line arguments are collected using
the same StoreDiags class that is used to collect all other errors. They
are recognized by their location being invalid. IIUC, the location is
invalid as there is no source manager at this point, it is created at a
later stage.
Although technically we might also get diagnostics that mention the
command-line arguments FileID with after the source manager was created
(and they have valid source locations), we choose to not handle those
and they are dropped as not coming from the main file. AFAICT, those
diagnostics should always be notes, therefore it's safe to drop them
without loosing too much information.
Reviewers: kadircet
Reviewed By: kadircet
Subscribers: nridge, javed.absar, MaskRay, jkorous, arphaman, cfe-commits, gribozavr
Tags: #clang
Differential Revision: https://reviews.llvm.org/D66759
llvm-svn: 370177
2019-08-28 17:24:55 +08:00
|
|
|
reportDiagnostics(File, AST.getDiagnostics(), Publish);
|
|
|
|
}
|
|
|
|
|
|
|
|
void onFailedAST(PathRef File, std::vector<Diag> Diags,
|
|
|
|
PublishFn Publish) override {
|
|
|
|
reportDiagnostics(File, Diags, Publish);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void reportDiagnostics(PathRef File, llvm::ArrayRef<Diag> Diags,
|
|
|
|
PublishFn Publish) {
|
2018-11-23 01:27:08 +08:00
|
|
|
auto D = Context::current().get(DiagsCallbackKey);
|
|
|
|
if (!D)
|
|
|
|
return;
|
2019-07-19 21:51:01 +08:00
|
|
|
Publish([&]() {
|
|
|
|
const_cast<
|
|
|
|
llvm::unique_function<void(PathRef, std::vector<Diag>)> &> (*D)(
|
|
|
|
File, std::move(Diags));
|
|
|
|
});
|
2018-11-23 01:27:08 +08:00
|
|
|
}
|
|
|
|
};
|
2019-08-15 07:52:23 +08:00
|
|
|
return std::make_unique<CaptureDiags>();
|
2018-11-23 01:27:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Schedule an update and call \p CB with the diagnostics it produces, if
|
|
|
|
/// any. The TUScheduler should be created with captureDiags as a
|
|
|
|
/// DiagsCallback for this to work.
|
|
|
|
void updateWithDiags(TUScheduler &S, PathRef File, ParseInputs Inputs,
|
|
|
|
WantDiagnostics WD,
|
|
|
|
llvm::unique_function<void(std::vector<Diag>)> CB) {
|
|
|
|
Path OrigFile = File.str();
|
2019-08-15 22:16:06 +08:00
|
|
|
WithContextValue Ctx(DiagsCallbackKey,
|
|
|
|
[OrigFile, CB = std::move(CB)](
|
|
|
|
PathRef File, std::vector<Diag> Diags) mutable {
|
|
|
|
assert(File == OrigFile);
|
|
|
|
CB(std::move(Diags));
|
|
|
|
});
|
2018-11-23 01:27:08 +08:00
|
|
|
S.update(File, std::move(Inputs), WD);
|
|
|
|
}
|
|
|
|
|
|
|
|
void updateWithDiags(TUScheduler &S, PathRef File, llvm::StringRef Contents,
|
|
|
|
WantDiagnostics WD,
|
|
|
|
llvm::unique_function<void(std::vector<Diag>)> CB) {
|
|
|
|
return updateWithDiags(S, File, getInputs(File, Contents), WD,
|
|
|
|
std::move(CB));
|
|
|
|
}
|
|
|
|
|
2019-01-07 23:45:19 +08:00
|
|
|
llvm::StringMap<std::string> Files;
|
|
|
|
llvm::StringMap<time_t> Timestamps;
|
2018-01-31 16:51:16 +08:00
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
};
|
|
|
|
|
2018-11-23 01:27:08 +08:00
|
|
|
Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
|
|
|
|
TUSchedulerTests::DiagsCallbackKey;
|
|
|
|
|
2018-01-31 16:51:16 +08:00
|
|
|
TEST_F(TUSchedulerTests, MissingFiles) {
|
2019-04-15 20:32:28 +08:00
|
|
|
TUScheduler S(CDB, getDefaultAsyncThreadsCount(),
|
2018-09-04 00:37:59 +08:00
|
|
|
/*StorePreamblesInMemory=*/true, /*ASTCallbacks=*/nullptr,
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
2018-01-31 16:51:16 +08:00
|
|
|
|
2018-02-16 17:41:43 +08:00
|
|
|
auto Added = testPath("added.cpp");
|
2019-06-19 15:29:05 +08:00
|
|
|
Files[Added] = "x";
|
2018-01-31 16:51:16 +08:00
|
|
|
|
2018-02-16 17:41:43 +08:00
|
|
|
auto Missing = testPath("missing.cpp");
|
|
|
|
Files[Missing] = "";
|
2018-01-31 16:51:16 +08:00
|
|
|
|
2019-06-19 15:29:05 +08:00
|
|
|
EXPECT_EQ(S.getContents(Added), "");
|
|
|
|
S.update(Added, getInputs(Added, "x"), WantDiagnostics::No);
|
|
|
|
EXPECT_EQ(S.getContents(Added), "x");
|
2018-01-31 16:51:16 +08:00
|
|
|
|
2019-07-19 21:51:01 +08:00
|
|
|
// Assert each operation for missing file is an error (even if it's
|
|
|
|
// available in VFS).
|
2018-11-23 00:20:12 +08:00
|
|
|
S.runWithAST("", Missing,
|
|
|
|
[&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
|
|
|
|
S.runWithPreamble(
|
|
|
|
"", Missing, TUScheduler::Stale,
|
|
|
|
[&](Expected<InputsAndPreamble> Preamble) { EXPECT_ERROR(Preamble); });
|
2018-02-08 15:37:35 +08:00
|
|
|
// remove() shouldn't crash on missing files.
|
|
|
|
S.remove(Missing);
|
2018-01-31 16:51:16 +08:00
|
|
|
|
|
|
|
// Assert there aren't any errors for added file.
|
2018-10-20 23:30:37 +08:00
|
|
|
S.runWithAST("", Added,
|
|
|
|
[&](Expected<InputsAndAST> AST) { EXPECT_TRUE(bool(AST)); });
|
2018-08-30 23:07:34 +08:00
|
|
|
S.runWithPreamble("", Added, TUScheduler::Stale,
|
2018-10-20 23:30:37 +08:00
|
|
|
[&](Expected<InputsAndPreamble> Preamble) {
|
2018-08-30 23:07:34 +08:00
|
|
|
EXPECT_TRUE(bool(Preamble));
|
|
|
|
});
|
2019-06-19 15:29:05 +08:00
|
|
|
EXPECT_EQ(S.getContents(Added), "x");
|
2018-02-08 15:37:35 +08:00
|
|
|
S.remove(Added);
|
2019-06-19 15:29:05 +08:00
|
|
|
EXPECT_EQ(S.getContents(Added), "");
|
2018-01-31 16:51:16 +08:00
|
|
|
|
|
|
|
// Assert that all operations fail after removing the file.
|
2018-11-23 00:20:12 +08:00
|
|
|
S.runWithAST("", Added,
|
|
|
|
[&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
|
2018-08-30 23:07:34 +08:00
|
|
|
S.runWithPreamble("", Added, TUScheduler::Stale,
|
2018-10-20 23:30:37 +08:00
|
|
|
[&](Expected<InputsAndPreamble> Preamble) {
|
2018-08-30 23:07:34 +08:00
|
|
|
ASSERT_FALSE(bool(Preamble));
|
2018-11-23 00:20:12 +08:00
|
|
|
llvm::consumeError(Preamble.takeError());
|
2018-08-30 23:07:34 +08:00
|
|
|
});
|
2018-02-08 15:37:35 +08:00
|
|
|
// remove() shouldn't crash on missing files.
|
|
|
|
S.remove(Added);
|
2018-01-31 16:51:16 +08:00
|
|
|
}
|
|
|
|
|
2018-02-22 21:11:12 +08:00
|
|
|
TEST_F(TUSchedulerTests, WantDiagnostics) {
|
|
|
|
std::atomic<int> CallbackCount(0);
|
|
|
|
{
|
2018-02-22 23:33:33 +08:00
|
|
|
// To avoid a racy test, don't allow tasks to actualy run on the worker
|
|
|
|
// thread until we've scheduled them all.
|
|
|
|
Notification Ready;
|
2018-03-02 16:56:37 +08:00
|
|
|
TUScheduler S(
|
2019-04-15 20:32:28 +08:00
|
|
|
CDB, getDefaultAsyncThreadsCount(),
|
2018-11-23 01:27:08 +08:00
|
|
|
/*StorePreamblesInMemory=*/true, captureDiags(),
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
2018-02-22 21:11:12 +08:00
|
|
|
auto Path = testPath("foo.cpp");
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithDiags(S, Path, "", WantDiagnostics::Yes,
|
|
|
|
[&](std::vector<Diag>) { Ready.wait(); });
|
|
|
|
updateWithDiags(S, Path, "request diags", WantDiagnostics::Yes,
|
|
|
|
[&](std::vector<Diag>) { ++CallbackCount; });
|
|
|
|
updateWithDiags(S, Path, "auto (clobbered)", WantDiagnostics::Auto,
|
|
|
|
[&](std::vector<Diag>) {
|
|
|
|
ADD_FAILURE()
|
|
|
|
<< "auto should have been cancelled by auto";
|
|
|
|
});
|
|
|
|
updateWithDiags(S, Path, "request no diags", WantDiagnostics::No,
|
|
|
|
[&](std::vector<Diag>) {
|
|
|
|
ADD_FAILURE() << "no diags should not be called back";
|
|
|
|
});
|
|
|
|
updateWithDiags(S, Path, "auto (produces)", WantDiagnostics::Auto,
|
|
|
|
[&](std::vector<Diag>) { ++CallbackCount; });
|
2018-02-22 21:11:12 +08:00
|
|
|
Ready.notify();
|
[clangd] Cleanup: make diagnostics callbacks from TUScheduler non-racy
Summary:
Previously, removeDoc followed by an addDoc to TUScheduler resulted in
racy diagnostic responses, i.e. the old dianostics could be delivered
to the client after the new ones by TUScheduler.
To workaround this, we tracked a version number in ClangdServer and
discarded stale diagnostics. After this commit, the TUScheduler will
stop delivering diagnostics for removed files and the workaround in
ClangdServer is not required anymore.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: javed.absar, ioeric, MaskRay, jkorous, arphaman, jfb, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54829
llvm-svn: 347468
2018-11-22 23:39:54 +08:00
|
|
|
|
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
2018-02-22 21:11:12 +08:00
|
|
|
}
|
|
|
|
EXPECT_EQ(2, CallbackCount);
|
|
|
|
}
|
|
|
|
|
2018-03-02 16:56:37 +08:00
|
|
|
TEST_F(TUSchedulerTests, Debounce) {
|
|
|
|
std::atomic<int> CallbackCount(0);
|
|
|
|
{
|
2019-04-15 20:32:28 +08:00
|
|
|
TUScheduler S(CDB, getDefaultAsyncThreadsCount(),
|
2018-11-23 01:27:08 +08:00
|
|
|
/*StorePreamblesInMemory=*/true, captureDiags(),
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
/*UpdateDebounce=*/std::chrono::seconds(1),
|
|
|
|
ASTRetentionPolicy());
|
2018-03-03 02:23:41 +08:00
|
|
|
// FIXME: we could probably use timeouts lower than 1 second here.
|
2018-03-02 16:56:37 +08:00
|
|
|
auto Path = testPath("foo.cpp");
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithDiags(S, Path, "auto (debounced)", WantDiagnostics::Auto,
|
|
|
|
[&](std::vector<Diag>) {
|
|
|
|
ADD_FAILURE()
|
|
|
|
<< "auto should have been debounced and canceled";
|
|
|
|
});
|
2018-03-03 02:23:41 +08:00
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithDiags(S, Path, "auto (timed out)", WantDiagnostics::Auto,
|
|
|
|
[&](std::vector<Diag>) { ++CallbackCount; });
|
2018-03-03 02:23:41 +08:00
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithDiags(S, Path, "auto (shut down)", WantDiagnostics::Auto,
|
|
|
|
[&](std::vector<Diag>) { ++CallbackCount; });
|
[clangd] Cleanup: make diagnostics callbacks from TUScheduler non-racy
Summary:
Previously, removeDoc followed by an addDoc to TUScheduler resulted in
racy diagnostic responses, i.e. the old dianostics could be delivered
to the client after the new ones by TUScheduler.
To workaround this, we tracked a version number in ClangdServer and
discarded stale diagnostics. After this commit, the TUScheduler will
stop delivering diagnostics for removed files and the workaround in
ClangdServer is not required anymore.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: javed.absar, ioeric, MaskRay, jkorous, arphaman, jfb, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54829
llvm-svn: 347468
2018-11-22 23:39:54 +08:00
|
|
|
|
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
2018-03-02 16:56:37 +08:00
|
|
|
}
|
|
|
|
EXPECT_EQ(2, CallbackCount);
|
|
|
|
}
|
|
|
|
|
2018-08-30 23:07:34 +08:00
|
|
|
static std::vector<std::string> includes(const PreambleData *Preamble) {
|
|
|
|
std::vector<std::string> Result;
|
|
|
|
if (Preamble)
|
|
|
|
for (const auto &Inclusion : Preamble->Includes.MainFileIncludes)
|
|
|
|
Result.push_back(Inclusion.Written);
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(TUSchedulerTests, PreambleConsistency) {
|
|
|
|
std::atomic<int> CallbackCount(0);
|
|
|
|
{
|
|
|
|
Notification InconsistentReadDone; // Must live longest.
|
|
|
|
TUScheduler S(
|
2019-04-15 20:32:28 +08:00
|
|
|
CDB, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true,
|
2018-09-04 00:37:59 +08:00
|
|
|
/*ASTCallbacks=*/nullptr,
|
2018-08-30 23:07:34 +08:00
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
|
|
|
auto Path = testPath("foo.cpp");
|
|
|
|
// Schedule two updates (A, B) and two preamble reads (stale, consistent).
|
|
|
|
// The stale read should see A, and the consistent read should see B.
|
|
|
|
// (We recognize the preambles by their included files).
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithCallback(S, Path, "#include <A>", WantDiagnostics::Yes, [&]() {
|
|
|
|
// This callback runs in between the two preamble updates.
|
|
|
|
|
|
|
|
// This blocks update B, preventing it from winning the race
|
|
|
|
// against the stale read.
|
|
|
|
// If the first read was instead consistent, this would deadlock.
|
|
|
|
InconsistentReadDone.wait();
|
|
|
|
// This delays update B, preventing it from winning a race
|
|
|
|
// against the consistent read. The consistent read sees B
|
|
|
|
// only because it waits for it.
|
|
|
|
// If the second read was stale, it would usually see A.
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
});
|
|
|
|
S.update(Path, getInputs(Path, "#include <B>"), WantDiagnostics::Yes);
|
2018-08-30 23:07:34 +08:00
|
|
|
|
|
|
|
S.runWithPreamble("StaleRead", Path, TUScheduler::Stale,
|
2018-10-20 23:30:37 +08:00
|
|
|
[&](Expected<InputsAndPreamble> Pre) {
|
2018-08-30 23:07:34 +08:00
|
|
|
ASSERT_TRUE(bool(Pre));
|
|
|
|
assert(bool(Pre));
|
|
|
|
EXPECT_THAT(includes(Pre->Preamble),
|
|
|
|
ElementsAre("<A>"));
|
|
|
|
InconsistentReadDone.notify();
|
|
|
|
++CallbackCount;
|
|
|
|
});
|
|
|
|
S.runWithPreamble("ConsistentRead", Path, TUScheduler::Consistent,
|
2018-10-20 23:30:37 +08:00
|
|
|
[&](Expected<InputsAndPreamble> Pre) {
|
2018-08-30 23:07:34 +08:00
|
|
|
ASSERT_TRUE(bool(Pre));
|
|
|
|
EXPECT_THAT(includes(Pre->Preamble),
|
|
|
|
ElementsAre("<B>"));
|
|
|
|
++CallbackCount;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
EXPECT_EQ(2, CallbackCount);
|
|
|
|
}
|
|
|
|
|
[clangd] Respect task cancellation in TUScheduler.
Summary:
- Reads are never executed if canceled before ready-to run.
In practice, we finalize cancelled reads eagerly and out-of-order.
- Cancelled reads don't prevent prior updates from being elided, as they don't
actually depend on the result of the update.
- Updates are downgraded from WantDiagnostics::Yes to WantDiagnostics::Auto when
cancelled, which allows them to be elided when all dependent reads are
cancelled and there are subsequent writes. (e.g. when the queue is backed up
with cancelled requests).
The queue operations aren't optimal (we scan the whole queue for cancelled
tasks every time the scheduler runs, and check cancellation twice in the end).
However I believe these costs are still trivial in practice (compared to any
AST operation) and the logic can be cleanly separated from the rest of the
scheduler.
Reviewers: ilya-biryukov
Subscribers: javed.absar, ioeric, MaskRay, jkorous, arphaman, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54746
llvm-svn: 347450
2018-11-22 18:22:16 +08:00
|
|
|
TEST_F(TUSchedulerTests, Cancellation) {
|
|
|
|
// We have the following update/read sequence
|
|
|
|
// U0
|
|
|
|
// U1(WantDiags=Yes) <-- cancelled
|
|
|
|
// R1 <-- cancelled
|
|
|
|
// U2(WantDiags=Yes) <-- cancelled
|
|
|
|
// R2A <-- cancelled
|
|
|
|
// R2B
|
|
|
|
// U3(WantDiags=Yes)
|
|
|
|
// R3 <-- cancelled
|
|
|
|
std::vector<std::string> DiagsSeen, ReadsSeen, ReadsCanceled;
|
|
|
|
{
|
2018-11-22 20:54:25 +08:00
|
|
|
Notification Proceed; // Ensure we schedule everything.
|
[clangd] Respect task cancellation in TUScheduler.
Summary:
- Reads are never executed if canceled before ready-to run.
In practice, we finalize cancelled reads eagerly and out-of-order.
- Cancelled reads don't prevent prior updates from being elided, as they don't
actually depend on the result of the update.
- Updates are downgraded from WantDiagnostics::Yes to WantDiagnostics::Auto when
cancelled, which allows them to be elided when all dependent reads are
cancelled and there are subsequent writes. (e.g. when the queue is backed up
with cancelled requests).
The queue operations aren't optimal (we scan the whole queue for cancelled
tasks every time the scheduler runs, and check cancellation twice in the end).
However I believe these costs are still trivial in practice (compared to any
AST operation) and the logic can be cleanly separated from the rest of the
scheduler.
Reviewers: ilya-biryukov
Subscribers: javed.absar, ioeric, MaskRay, jkorous, arphaman, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54746
llvm-svn: 347450
2018-11-22 18:22:16 +08:00
|
|
|
TUScheduler S(
|
2019-04-15 20:32:28 +08:00
|
|
|
CDB, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true,
|
2018-11-23 01:27:08 +08:00
|
|
|
/*ASTCallbacks=*/captureDiags(),
|
[clangd] Respect task cancellation in TUScheduler.
Summary:
- Reads are never executed if canceled before ready-to run.
In practice, we finalize cancelled reads eagerly and out-of-order.
- Cancelled reads don't prevent prior updates from being elided, as they don't
actually depend on the result of the update.
- Updates are downgraded from WantDiagnostics::Yes to WantDiagnostics::Auto when
cancelled, which allows them to be elided when all dependent reads are
cancelled and there are subsequent writes. (e.g. when the queue is backed up
with cancelled requests).
The queue operations aren't optimal (we scan the whole queue for cancelled
tasks every time the scheduler runs, and check cancellation twice in the end).
However I believe these costs are still trivial in practice (compared to any
AST operation) and the logic can be cleanly separated from the rest of the
scheduler.
Reviewers: ilya-biryukov
Subscribers: javed.absar, ioeric, MaskRay, jkorous, arphaman, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54746
llvm-svn: 347450
2018-11-22 18:22:16 +08:00
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
|
|
|
auto Path = testPath("foo.cpp");
|
|
|
|
// Helper to schedule a named update and return a function to cancel it.
|
|
|
|
auto Update = [&](std::string ID) -> Canceler {
|
|
|
|
auto T = cancelableTask();
|
|
|
|
WithContext C(std::move(T.first));
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithDiags(
|
|
|
|
S, Path, "//" + ID, WantDiagnostics::Yes,
|
|
|
|
[&, ID](std::vector<Diag> Diags) { DiagsSeen.push_back(ID); });
|
[clangd] Respect task cancellation in TUScheduler.
Summary:
- Reads are never executed if canceled before ready-to run.
In practice, we finalize cancelled reads eagerly and out-of-order.
- Cancelled reads don't prevent prior updates from being elided, as they don't
actually depend on the result of the update.
- Updates are downgraded from WantDiagnostics::Yes to WantDiagnostics::Auto when
cancelled, which allows them to be elided when all dependent reads are
cancelled and there are subsequent writes. (e.g. when the queue is backed up
with cancelled requests).
The queue operations aren't optimal (we scan the whole queue for cancelled
tasks every time the scheduler runs, and check cancellation twice in the end).
However I believe these costs are still trivial in practice (compared to any
AST operation) and the logic can be cleanly separated from the rest of the
scheduler.
Reviewers: ilya-biryukov
Subscribers: javed.absar, ioeric, MaskRay, jkorous, arphaman, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54746
llvm-svn: 347450
2018-11-22 18:22:16 +08:00
|
|
|
return std::move(T.second);
|
|
|
|
};
|
|
|
|
// Helper to schedule a named read and return a function to cancel it.
|
|
|
|
auto Read = [&](std::string ID) -> Canceler {
|
|
|
|
auto T = cancelableTask();
|
|
|
|
WithContext C(std::move(T.first));
|
|
|
|
S.runWithAST(ID, Path, [&, ID](llvm::Expected<InputsAndAST> E) {
|
|
|
|
if (auto Err = E.takeError()) {
|
|
|
|
if (Err.isA<CancelledError>()) {
|
|
|
|
ReadsCanceled.push_back(ID);
|
|
|
|
consumeError(std::move(Err));
|
|
|
|
} else {
|
|
|
|
ADD_FAILURE() << "Non-cancelled error for " << ID << ": "
|
|
|
|
<< llvm::toString(std::move(Err));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ReadsSeen.push_back(ID);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return std::move(T.second);
|
|
|
|
};
|
|
|
|
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithCallback(S, Path, "", WantDiagnostics::Yes,
|
|
|
|
[&]() { Proceed.wait(); });
|
[clangd] Respect task cancellation in TUScheduler.
Summary:
- Reads are never executed if canceled before ready-to run.
In practice, we finalize cancelled reads eagerly and out-of-order.
- Cancelled reads don't prevent prior updates from being elided, as they don't
actually depend on the result of the update.
- Updates are downgraded from WantDiagnostics::Yes to WantDiagnostics::Auto when
cancelled, which allows them to be elided when all dependent reads are
cancelled and there are subsequent writes. (e.g. when the queue is backed up
with cancelled requests).
The queue operations aren't optimal (we scan the whole queue for cancelled
tasks every time the scheduler runs, and check cancellation twice in the end).
However I believe these costs are still trivial in practice (compared to any
AST operation) and the logic can be cleanly separated from the rest of the
scheduler.
Reviewers: ilya-biryukov
Subscribers: javed.absar, ioeric, MaskRay, jkorous, arphaman, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54746
llvm-svn: 347450
2018-11-22 18:22:16 +08:00
|
|
|
// The second parens indicate cancellation, where present.
|
|
|
|
Update("U1")();
|
|
|
|
Read("R1")();
|
|
|
|
Update("U2")();
|
|
|
|
Read("R2A")();
|
|
|
|
Read("R2B");
|
|
|
|
Update("U3");
|
|
|
|
Read("R3")();
|
|
|
|
Proceed.notify();
|
[clangd] Cleanup: make diagnostics callbacks from TUScheduler non-racy
Summary:
Previously, removeDoc followed by an addDoc to TUScheduler resulted in
racy diagnostic responses, i.e. the old dianostics could be delivered
to the client after the new ones by TUScheduler.
To workaround this, we tracked a version number in ClangdServer and
discarded stale diagnostics. After this commit, the TUScheduler will
stop delivering diagnostics for removed files and the workaround in
ClangdServer is not required anymore.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: javed.absar, ioeric, MaskRay, jkorous, arphaman, jfb, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54829
llvm-svn: 347468
2018-11-22 23:39:54 +08:00
|
|
|
|
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
[clangd] Respect task cancellation in TUScheduler.
Summary:
- Reads are never executed if canceled before ready-to run.
In practice, we finalize cancelled reads eagerly and out-of-order.
- Cancelled reads don't prevent prior updates from being elided, as they don't
actually depend on the result of the update.
- Updates are downgraded from WantDiagnostics::Yes to WantDiagnostics::Auto when
cancelled, which allows them to be elided when all dependent reads are
cancelled and there are subsequent writes. (e.g. when the queue is backed up
with cancelled requests).
The queue operations aren't optimal (we scan the whole queue for cancelled
tasks every time the scheduler runs, and check cancellation twice in the end).
However I believe these costs are still trivial in practice (compared to any
AST operation) and the logic can be cleanly separated from the rest of the
scheduler.
Reviewers: ilya-biryukov
Subscribers: javed.absar, ioeric, MaskRay, jkorous, arphaman, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54746
llvm-svn: 347450
2018-11-22 18:22:16 +08:00
|
|
|
}
|
|
|
|
EXPECT_THAT(DiagsSeen, ElementsAre("U2", "U3"))
|
|
|
|
<< "U1 and all dependent reads were cancelled. "
|
|
|
|
"U2 has a dependent read R2A. "
|
|
|
|
"U3 was not cancelled.";
|
|
|
|
EXPECT_THAT(ReadsSeen, ElementsAre("R2B"))
|
|
|
|
<< "All reads other than R2B were cancelled";
|
|
|
|
EXPECT_THAT(ReadsCanceled, ElementsAre("R1", "R2A", "R3"))
|
|
|
|
<< "All reads other than R2B were cancelled";
|
|
|
|
}
|
|
|
|
|
2018-01-31 16:51:16 +08:00
|
|
|
TEST_F(TUSchedulerTests, ManyUpdates) {
|
|
|
|
const int FilesCount = 3;
|
|
|
|
const int UpdatesPerFile = 10;
|
|
|
|
|
|
|
|
std::mutex Mut;
|
|
|
|
int TotalASTReads = 0;
|
|
|
|
int TotalPreambleReads = 0;
|
|
|
|
int TotalUpdates = 0;
|
|
|
|
|
|
|
|
// Run TUScheduler and collect some stats.
|
|
|
|
{
|
2019-04-15 20:32:28 +08:00
|
|
|
TUScheduler S(CDB, getDefaultAsyncThreadsCount(),
|
2018-11-23 01:27:08 +08:00
|
|
|
/*StorePreamblesInMemory=*/true, captureDiags(),
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
/*UpdateDebounce=*/std::chrono::milliseconds(50),
|
|
|
|
ASTRetentionPolicy());
|
2018-01-31 16:51:16 +08:00
|
|
|
|
|
|
|
std::vector<std::string> Files;
|
|
|
|
for (int I = 0; I < FilesCount; ++I) {
|
2018-02-16 17:41:43 +08:00
|
|
|
std::string Name = "foo" + std::to_string(I) + ".cpp";
|
|
|
|
Files.push_back(testPath(Name));
|
|
|
|
this->Files[Files.back()] = "";
|
2018-01-31 16:51:16 +08:00
|
|
|
}
|
|
|
|
|
2018-10-20 23:30:37 +08:00
|
|
|
StringRef Contents1 = R"cpp(int a;)cpp";
|
|
|
|
StringRef Contents2 = R"cpp(int main() { return 1; })cpp";
|
|
|
|
StringRef Contents3 = R"cpp(int a; int b; int sum() { return a + b; })cpp";
|
2018-01-31 16:51:16 +08:00
|
|
|
|
2018-10-20 23:30:37 +08:00
|
|
|
StringRef AllContents[] = {Contents1, Contents2, Contents3};
|
2018-01-31 16:51:16 +08:00
|
|
|
const int AllContentsSize = 3;
|
|
|
|
|
2019-07-19 21:51:01 +08:00
|
|
|
// Scheduler may run tasks asynchronously, but should propagate the
|
|
|
|
// context. We stash a nonce in the context, and verify it in the task.
|
[clangd] Pass Context implicitly using TLS.
Summary:
Instead of passing Context explicitly around, we now have a thread-local
Context object `Context::current()` which is an implicit argument to
every function.
Most manipulation of this should use the WithContextValue helper, which
augments the current Context to add a single KV pair, and restores the
old context on destruction.
Advantages are:
- less boilerplate in functions that just propagate contexts
- reading most code doesn't require understanding context at all, and
using context as values in fewer places still
- fewer options to pass the "wrong" context when it changes within a
scope (e.g. when using Span)
- contexts pass through interfaces we can't modify, such as VFS
- propagating contexts across threads was slightly tricky (e.g.
copy vs move, no move-init in lambdas), and is now encapsulated in
the threadpool
Disadvantages are all the usual TLS stuff - hidden magic, and
potential for higher memory usage on threads that don't use the
context. (In practice, it's just one pointer)
Reviewers: ilya-biryukov
Subscribers: klimek, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D42517
llvm-svn: 323872
2018-01-31 21:40:48 +08:00
|
|
|
static Key<int> NonceKey;
|
|
|
|
int Nonce = 0;
|
|
|
|
|
2018-01-31 16:51:16 +08:00
|
|
|
for (int FileI = 0; FileI < FilesCount; ++FileI) {
|
|
|
|
for (int UpdateI = 0; UpdateI < UpdatesPerFile; ++UpdateI) {
|
|
|
|
auto Contents = AllContents[(FileI + UpdateI) % AllContentsSize];
|
|
|
|
|
|
|
|
auto File = Files[FileI];
|
|
|
|
auto Inputs = getInputs(File, Contents.str());
|
[clangd] Pass Context implicitly using TLS.
Summary:
Instead of passing Context explicitly around, we now have a thread-local
Context object `Context::current()` which is an implicit argument to
every function.
Most manipulation of this should use the WithContextValue helper, which
augments the current Context to add a single KV pair, and restores the
old context on destruction.
Advantages are:
- less boilerplate in functions that just propagate contexts
- reading most code doesn't require understanding context at all, and
using context as values in fewer places still
- fewer options to pass the "wrong" context when it changes within a
scope (e.g. when using Span)
- contexts pass through interfaces we can't modify, such as VFS
- propagating contexts across threads was slightly tricky (e.g.
copy vs move, no move-init in lambdas), and is now encapsulated in
the threadpool
Disadvantages are all the usual TLS stuff - hidden magic, and
potential for higher memory usage on threads that don't use the
context. (In practice, it's just one pointer)
Reviewers: ilya-biryukov
Subscribers: klimek, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D42517
llvm-svn: 323872
2018-01-31 21:40:48 +08:00
|
|
|
{
|
|
|
|
WithContextValue WithNonce(NonceKey, ++Nonce);
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithDiags(
|
|
|
|
S, File, Inputs, WantDiagnostics::Auto,
|
|
|
|
[File, Nonce, &Mut, &TotalUpdates](std::vector<Diag>) {
|
|
|
|
EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
|
[clangd] Pass Context implicitly using TLS.
Summary:
Instead of passing Context explicitly around, we now have a thread-local
Context object `Context::current()` which is an implicit argument to
every function.
Most manipulation of this should use the WithContextValue helper, which
augments the current Context to add a single KV pair, and restores the
old context on destruction.
Advantages are:
- less boilerplate in functions that just propagate contexts
- reading most code doesn't require understanding context at all, and
using context as values in fewer places still
- fewer options to pass the "wrong" context when it changes within a
scope (e.g. when using Span)
- contexts pass through interfaces we can't modify, such as VFS
- propagating contexts across threads was slightly tricky (e.g.
copy vs move, no move-init in lambdas), and is now encapsulated in
the threadpool
Disadvantages are all the usual TLS stuff - hidden magic, and
potential for higher memory usage on threads that don't use the
context. (In practice, it's just one pointer)
Reviewers: ilya-biryukov
Subscribers: klimek, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D42517
llvm-svn: 323872
2018-01-31 21:40:48 +08:00
|
|
|
|
2018-11-23 01:27:08 +08:00
|
|
|
std::lock_guard<std::mutex> Lock(Mut);
|
|
|
|
++TotalUpdates;
|
|
|
|
EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
|
|
|
|
});
|
|
|
|
}
|
[clangd] Pass Context implicitly using TLS.
Summary:
Instead of passing Context explicitly around, we now have a thread-local
Context object `Context::current()` which is an implicit argument to
every function.
Most manipulation of this should use the WithContextValue helper, which
augments the current Context to add a single KV pair, and restores the
old context on destruction.
Advantages are:
- less boilerplate in functions that just propagate contexts
- reading most code doesn't require understanding context at all, and
using context as values in fewer places still
- fewer options to pass the "wrong" context when it changes within a
scope (e.g. when using Span)
- contexts pass through interfaces we can't modify, such as VFS
- propagating contexts across threads was slightly tricky (e.g.
copy vs move, no move-init in lambdas), and is now encapsulated in
the threadpool
Disadvantages are all the usual TLS stuff - hidden magic, and
potential for higher memory usage on threads that don't use the
context. (In practice, it's just one pointer)
Reviewers: ilya-biryukov
Subscribers: klimek, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D42517
llvm-svn: 323872
2018-01-31 21:40:48 +08:00
|
|
|
{
|
|
|
|
WithContextValue WithNonce(NonceKey, ++Nonce);
|
2019-01-03 21:28:05 +08:00
|
|
|
S.runWithAST(
|
|
|
|
"CheckAST", File,
|
|
|
|
[File, Inputs, Nonce, &Mut,
|
|
|
|
&TotalASTReads](Expected<InputsAndAST> AST) {
|
|
|
|
EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
|
|
|
|
|
|
|
|
ASSERT_TRUE((bool)AST);
|
|
|
|
EXPECT_EQ(AST->Inputs.FS, Inputs.FS);
|
|
|
|
EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents);
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> Lock(Mut);
|
|
|
|
++TotalASTReads;
|
|
|
|
EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
|
|
|
|
});
|
[clangd] Pass Context implicitly using TLS.
Summary:
Instead of passing Context explicitly around, we now have a thread-local
Context object `Context::current()` which is an implicit argument to
every function.
Most manipulation of this should use the WithContextValue helper, which
augments the current Context to add a single KV pair, and restores the
old context on destruction.
Advantages are:
- less boilerplate in functions that just propagate contexts
- reading most code doesn't require understanding context at all, and
using context as values in fewer places still
- fewer options to pass the "wrong" context when it changes within a
scope (e.g. when using Span)
- contexts pass through interfaces we can't modify, such as VFS
- propagating contexts across threads was slightly tricky (e.g.
copy vs move, no move-init in lambdas), and is now encapsulated in
the threadpool
Disadvantages are all the usual TLS stuff - hidden magic, and
potential for higher memory usage on threads that don't use the
context. (In practice, it's just one pointer)
Reviewers: ilya-biryukov
Subscribers: klimek, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D42517
llvm-svn: 323872
2018-01-31 21:40:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
WithContextValue WithNonce(NonceKey, ++Nonce);
|
2018-08-09 17:05:45 +08:00
|
|
|
S.runWithPreamble(
|
2018-08-30 23:07:34 +08:00
|
|
|
"CheckPreamble", File, TUScheduler::Stale,
|
2018-10-20 23:30:37 +08:00
|
|
|
[File, Inputs, Nonce, &Mut,
|
|
|
|
&TotalPreambleReads](Expected<InputsAndPreamble> Preamble) {
|
2018-08-09 17:05:45 +08:00
|
|
|
EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
|
|
|
|
|
|
|
|
ASSERT_TRUE((bool)Preamble);
|
|
|
|
EXPECT_EQ(Preamble->Contents, Inputs.Contents);
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> Lock(Mut);
|
|
|
|
++TotalPreambleReads;
|
|
|
|
EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
|
|
|
|
});
|
[clangd] Pass Context implicitly using TLS.
Summary:
Instead of passing Context explicitly around, we now have a thread-local
Context object `Context::current()` which is an implicit argument to
every function.
Most manipulation of this should use the WithContextValue helper, which
augments the current Context to add a single KV pair, and restores the
old context on destruction.
Advantages are:
- less boilerplate in functions that just propagate contexts
- reading most code doesn't require understanding context at all, and
using context as values in fewer places still
- fewer options to pass the "wrong" context when it changes within a
scope (e.g. when using Span)
- contexts pass through interfaces we can't modify, such as VFS
- propagating contexts across threads was slightly tricky (e.g.
copy vs move, no move-init in lambdas), and is now encapsulated in
the threadpool
Disadvantages are all the usual TLS stuff - hidden magic, and
potential for higher memory usage on threads that don't use the
context. (In practice, it's just one pointer)
Reviewers: ilya-biryukov
Subscribers: klimek, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D42517
llvm-svn: 323872
2018-01-31 21:40:48 +08:00
|
|
|
}
|
2018-01-31 16:51:16 +08:00
|
|
|
}
|
|
|
|
}
|
[clangd] Cleanup: make diagnostics callbacks from TUScheduler non-racy
Summary:
Previously, removeDoc followed by an addDoc to TUScheduler resulted in
racy diagnostic responses, i.e. the old dianostics could be delivered
to the client after the new ones by TUScheduler.
To workaround this, we tracked a version number in ClangdServer and
discarded stale diagnostics. After this commit, the TUScheduler will
stop delivering diagnostics for removed files and the workaround in
ClangdServer is not required anymore.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: javed.absar, ioeric, MaskRay, jkorous, arphaman, jfb, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D54829
llvm-svn: 347468
2018-11-22 23:39:54 +08:00
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
2018-01-31 16:51:16 +08:00
|
|
|
} // TUScheduler destructor waits for all operations to finish.
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> Lock(Mut);
|
|
|
|
EXPECT_EQ(TotalUpdates, FilesCount * UpdatesPerFile);
|
|
|
|
EXPECT_EQ(TotalASTReads, FilesCount * UpdatesPerFile);
|
|
|
|
EXPECT_EQ(TotalPreambleReads, FilesCount * UpdatesPerFile);
|
|
|
|
}
|
|
|
|
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
TEST_F(TUSchedulerTests, EvictedAST) {
|
2018-07-21 02:45:25 +08:00
|
|
|
std::atomic<int> BuiltASTCounter(0);
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
ASTRetentionPolicy Policy;
|
|
|
|
Policy.MaxRetainedASTs = 2;
|
2019-04-15 20:32:28 +08:00
|
|
|
TUScheduler S(CDB,
|
|
|
|
/*AsyncThreadsCount=*/1, /*StorePreambleInMemory=*/true,
|
|
|
|
/*ASTCallbacks=*/nullptr,
|
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
Policy);
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
|
|
|
|
llvm::StringLiteral SourceContents = R"cpp(
|
|
|
|
int* a;
|
|
|
|
double* b = a;
|
|
|
|
)cpp";
|
2018-07-26 17:21:07 +08:00
|
|
|
llvm::StringLiteral OtherSourceContents = R"cpp(
|
|
|
|
int* a;
|
|
|
|
double* b = a + 0;
|
|
|
|
)cpp";
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
|
|
|
|
auto Foo = testPath("foo.cpp");
|
|
|
|
auto Bar = testPath("bar.cpp");
|
|
|
|
auto Baz = testPath("baz.cpp");
|
|
|
|
|
|
|
|
// Build one file in advance. We will not access it later, so it will be the
|
|
|
|
// one that the cache will evict.
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithCallback(S, Foo, SourceContents, WantDiagnostics::Yes,
|
|
|
|
[&BuiltASTCounter]() { ++BuiltASTCounter; });
|
2018-08-23 18:25:07 +08:00
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
ASSERT_EQ(BuiltASTCounter.load(), 1);
|
|
|
|
|
2019-07-19 21:51:01 +08:00
|
|
|
// Build two more files. Since we can retain only 2 ASTs, these should be
|
|
|
|
// the ones we see in the cache later.
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithCallback(S, Bar, SourceContents, WantDiagnostics::Yes,
|
|
|
|
[&BuiltASTCounter]() { ++BuiltASTCounter; });
|
|
|
|
updateWithCallback(S, Baz, SourceContents, WantDiagnostics::Yes,
|
|
|
|
[&BuiltASTCounter]() { ++BuiltASTCounter; });
|
2018-08-23 18:25:07 +08:00
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
ASSERT_EQ(BuiltASTCounter.load(), 3);
|
|
|
|
|
|
|
|
// Check only the last two ASTs are retained.
|
|
|
|
ASSERT_THAT(S.getFilesWithCachedAST(), UnorderedElementsAre(Bar, Baz));
|
|
|
|
|
|
|
|
// Access the old file again.
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithCallback(S, Foo, OtherSourceContents, WantDiagnostics::Yes,
|
|
|
|
[&BuiltASTCounter]() { ++BuiltASTCounter; });
|
2018-08-23 18:25:07 +08:00
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
ASSERT_EQ(BuiltASTCounter.load(), 4);
|
|
|
|
|
|
|
|
// Check the AST for foo.cpp is retained now and one of the others got
|
|
|
|
// evicted.
|
|
|
|
EXPECT_THAT(S.getFilesWithCachedAST(),
|
|
|
|
UnorderedElementsAre(Foo, AnyOf(Bar, Baz)));
|
|
|
|
}
|
|
|
|
|
2018-08-17 22:55:57 +08:00
|
|
|
TEST_F(TUSchedulerTests, EmptyPreamble) {
|
2019-04-15 20:32:28 +08:00
|
|
|
TUScheduler S(CDB,
|
|
|
|
/*AsyncThreadsCount=*/4, /*StorePreambleInMemory=*/true,
|
|
|
|
/*ASTCallbacks=*/nullptr,
|
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
2018-08-17 22:55:57 +08:00
|
|
|
|
|
|
|
auto Foo = testPath("foo.cpp");
|
|
|
|
auto Header = testPath("foo.h");
|
|
|
|
|
|
|
|
Files[Header] = "void foo()";
|
|
|
|
Timestamps[Header] = time_t(0);
|
|
|
|
auto WithPreamble = R"cpp(
|
|
|
|
#include "foo.h"
|
|
|
|
int main() {}
|
|
|
|
)cpp";
|
|
|
|
auto WithEmptyPreamble = R"cpp(int main() {})cpp";
|
2018-11-23 01:27:08 +08:00
|
|
|
S.update(Foo, getInputs(Foo, WithPreamble), WantDiagnostics::Auto);
|
2019-01-03 21:28:05 +08:00
|
|
|
S.runWithPreamble(
|
|
|
|
"getNonEmptyPreamble", Foo, TUScheduler::Stale,
|
|
|
|
[&](Expected<InputsAndPreamble> Preamble) {
|
|
|
|
// We expect to get a non-empty preamble.
|
|
|
|
EXPECT_GT(
|
|
|
|
cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
|
|
|
|
0u);
|
|
|
|
});
|
2018-08-17 22:55:57 +08:00
|
|
|
// Wait for the preamble is being built.
|
2018-08-23 18:25:07 +08:00
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
2018-08-17 22:55:57 +08:00
|
|
|
|
|
|
|
// Update the file which results in an empty preamble.
|
2018-11-23 01:27:08 +08:00
|
|
|
S.update(Foo, getInputs(Foo, WithEmptyPreamble), WantDiagnostics::Auto);
|
2018-08-17 22:55:57 +08:00
|
|
|
// Wait for the preamble is being built.
|
2018-08-23 18:25:07 +08:00
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
2019-01-03 21:28:05 +08:00
|
|
|
S.runWithPreamble(
|
|
|
|
"getEmptyPreamble", Foo, TUScheduler::Stale,
|
|
|
|
[&](Expected<InputsAndPreamble> Preamble) {
|
|
|
|
// We expect to get an empty preamble.
|
|
|
|
EXPECT_EQ(
|
|
|
|
cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
|
|
|
|
0u);
|
|
|
|
});
|
2018-08-17 22:55:57 +08:00
|
|
|
}
|
|
|
|
|
2018-07-09 18:45:33 +08:00
|
|
|
TEST_F(TUSchedulerTests, RunWaitsForPreamble) {
|
|
|
|
// Testing strategy: we update the file and schedule a few preamble reads at
|
|
|
|
// the same time. All reads should get the same non-null preamble.
|
2019-04-15 20:32:28 +08:00
|
|
|
TUScheduler S(CDB,
|
|
|
|
/*AsyncThreadsCount=*/4, /*StorePreambleInMemory=*/true,
|
|
|
|
/*ASTCallbacks=*/nullptr,
|
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
2018-07-09 18:45:33 +08:00
|
|
|
auto Foo = testPath("foo.cpp");
|
|
|
|
auto NonEmptyPreamble = R"cpp(
|
|
|
|
#define FOO 1
|
|
|
|
#define BAR 2
|
|
|
|
|
|
|
|
int main() {}
|
|
|
|
)cpp";
|
|
|
|
constexpr int ReadsToSchedule = 10;
|
|
|
|
std::mutex PreamblesMut;
|
|
|
|
std::vector<const void *> Preambles(ReadsToSchedule, nullptr);
|
2018-11-23 01:27:08 +08:00
|
|
|
S.update(Foo, getInputs(Foo, NonEmptyPreamble), WantDiagnostics::Auto);
|
2018-07-09 18:45:33 +08:00
|
|
|
for (int I = 0; I < ReadsToSchedule; ++I) {
|
|
|
|
S.runWithPreamble(
|
2018-08-30 23:07:34 +08:00
|
|
|
"test", Foo, TUScheduler::Stale,
|
2018-10-20 23:30:37 +08:00
|
|
|
[I, &PreamblesMut, &Preambles](Expected<InputsAndPreamble> IP) {
|
2018-07-09 18:45:33 +08:00
|
|
|
std::lock_guard<std::mutex> Lock(PreamblesMut);
|
|
|
|
Preambles[I] = cantFail(std::move(IP)).Preamble;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
|
|
|
// Check all actions got the same non-null preamble.
|
|
|
|
std::lock_guard<std::mutex> Lock(PreamblesMut);
|
|
|
|
ASSERT_NE(Preambles[0], nullptr);
|
|
|
|
ASSERT_THAT(Preambles, Each(Preambles[0]));
|
|
|
|
}
|
|
|
|
|
2018-07-26 17:21:07 +08:00
|
|
|
TEST_F(TUSchedulerTests, NoopOnEmptyChanges) {
|
2019-04-15 20:32:28 +08:00
|
|
|
TUScheduler S(CDB,
|
|
|
|
/*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
|
|
|
|
/*StorePreambleInMemory=*/true, captureDiags(),
|
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
2018-07-26 17:21:07 +08:00
|
|
|
|
|
|
|
auto Source = testPath("foo.cpp");
|
|
|
|
auto Header = testPath("foo.h");
|
|
|
|
|
|
|
|
Files[Header] = "int a;";
|
|
|
|
Timestamps[Header] = time_t(0);
|
|
|
|
|
|
|
|
auto SourceContents = R"cpp(
|
|
|
|
#include "foo.h"
|
|
|
|
int b = a;
|
|
|
|
)cpp";
|
|
|
|
|
|
|
|
// Return value indicates if the updated callback was received.
|
2018-11-23 01:27:08 +08:00
|
|
|
auto DoUpdate = [&](std::string Contents) -> bool {
|
2018-07-26 17:21:07 +08:00
|
|
|
std::atomic<bool> Updated(false);
|
|
|
|
Updated = false;
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithDiags(S, Source, Contents, WantDiagnostics::Yes,
|
|
|
|
[&Updated](std::vector<Diag>) { Updated = true; });
|
2018-08-23 18:25:07 +08:00
|
|
|
bool UpdateFinished = S.blockUntilIdle(timeoutSeconds(10));
|
2018-07-26 17:21:07 +08:00
|
|
|
if (!UpdateFinished)
|
|
|
|
ADD_FAILURE() << "Updated has not finished in one second. Threading bug?";
|
|
|
|
return Updated;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Test that subsequent updates with the same inputs do not cause rebuilds.
|
2018-11-23 01:27:08 +08:00
|
|
|
ASSERT_TRUE(DoUpdate(SourceContents));
|
|
|
|
ASSERT_FALSE(DoUpdate(SourceContents));
|
2018-07-26 17:21:07 +08:00
|
|
|
|
|
|
|
// Update to a header should cause a rebuild, though.
|
2018-12-06 16:55:24 +08:00
|
|
|
Timestamps[Header] = time_t(1);
|
2018-11-23 01:27:08 +08:00
|
|
|
ASSERT_TRUE(DoUpdate(SourceContents));
|
|
|
|
ASSERT_FALSE(DoUpdate(SourceContents));
|
2018-07-26 17:21:07 +08:00
|
|
|
|
|
|
|
// Update to the contents should cause a rebuild.
|
|
|
|
auto OtherSourceContents = R"cpp(
|
|
|
|
#include "foo.h"
|
|
|
|
int c = d;
|
|
|
|
)cpp";
|
2018-11-23 01:27:08 +08:00
|
|
|
ASSERT_TRUE(DoUpdate(OtherSourceContents));
|
|
|
|
ASSERT_FALSE(DoUpdate(OtherSourceContents));
|
2018-07-26 17:21:07 +08:00
|
|
|
|
|
|
|
// Update to the compile commands should also cause a rebuild.
|
|
|
|
CDB.ExtraClangFlags.push_back("-DSOMETHING");
|
2018-11-23 01:27:08 +08:00
|
|
|
ASSERT_TRUE(DoUpdate(OtherSourceContents));
|
|
|
|
ASSERT_FALSE(DoUpdate(OtherSourceContents));
|
2018-07-26 17:21:07 +08:00
|
|
|
}
|
|
|
|
|
2018-07-31 19:47:52 +08:00
|
|
|
TEST_F(TUSchedulerTests, NoChangeDiags) {
|
2019-04-15 20:32:28 +08:00
|
|
|
TUScheduler S(CDB,
|
|
|
|
/*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
|
|
|
|
/*StorePreambleInMemory=*/true, captureDiags(),
|
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
2018-07-31 19:47:52 +08:00
|
|
|
|
|
|
|
auto FooCpp = testPath("foo.cpp");
|
|
|
|
auto Contents = "int a; int b;";
|
|
|
|
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithDiags(
|
|
|
|
S, FooCpp, Contents, WantDiagnostics::No,
|
|
|
|
[](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
|
2018-10-20 23:30:37 +08:00
|
|
|
S.runWithAST("touchAST", FooCpp, [](Expected<InputsAndAST> IA) {
|
2018-07-31 19:47:52 +08:00
|
|
|
// Make sure the AST was actually built.
|
|
|
|
cantFail(std::move(IA));
|
|
|
|
});
|
2018-08-23 18:25:07 +08:00
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
2018-07-31 19:47:52 +08:00
|
|
|
|
|
|
|
// Even though the inputs didn't change and AST can be reused, we need to
|
|
|
|
// report the diagnostics, as they were not reported previously.
|
|
|
|
std::atomic<bool> SeenDiags(false);
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithDiags(S, FooCpp, Contents, WantDiagnostics::Auto,
|
|
|
|
[&](std::vector<Diag>) { SeenDiags = true; });
|
2018-08-23 18:25:07 +08:00
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
2018-07-31 19:47:52 +08:00
|
|
|
ASSERT_TRUE(SeenDiags);
|
|
|
|
|
|
|
|
// Subsequent request does not get any diagnostics callback because the same
|
|
|
|
// diags have previously been reported and the inputs didn't change.
|
2018-11-23 01:27:08 +08:00
|
|
|
updateWithDiags(
|
|
|
|
S, FooCpp, Contents, WantDiagnostics::Auto,
|
2018-07-31 19:47:52 +08:00
|
|
|
[&](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
|
2018-08-23 18:25:07 +08:00
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
2018-07-31 19:47:52 +08:00
|
|
|
}
|
|
|
|
|
2018-10-25 22:19:14 +08:00
|
|
|
TEST_F(TUSchedulerTests, Run) {
|
2019-04-15 20:32:28 +08:00
|
|
|
TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
|
2018-10-25 22:19:14 +08:00
|
|
|
/*StorePreambleInMemory=*/true, /*ASTCallbacks=*/nullptr,
|
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
|
|
|
std::atomic<int> Counter(0);
|
|
|
|
S.run("add 1", [&] { ++Counter; });
|
|
|
|
S.run("add 2", [&] { Counter += 2; });
|
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
|
|
|
|
EXPECT_EQ(Counter.load(), 3);
|
|
|
|
}
|
|
|
|
|
2018-12-06 17:41:04 +08:00
|
|
|
TEST_F(TUSchedulerTests, TUStatus) {
|
|
|
|
class CaptureTUStatus : public DiagnosticsConsumer {
|
|
|
|
public:
|
|
|
|
void onDiagnosticsReady(PathRef File,
|
|
|
|
std::vector<Diag> Diagnostics) override {}
|
|
|
|
|
|
|
|
void onFileUpdated(PathRef File, const TUStatus &Status) override {
|
|
|
|
std::lock_guard<std::mutex> Lock(Mutex);
|
|
|
|
AllStatus.push_back(Status);
|
|
|
|
}
|
|
|
|
|
2019-05-09 20:21:28 +08:00
|
|
|
std::vector<TUStatus> allStatus() {
|
|
|
|
std::lock_guard<std::mutex> Lock(Mutex);
|
|
|
|
return AllStatus;
|
|
|
|
}
|
2018-12-06 17:41:04 +08:00
|
|
|
|
|
|
|
private:
|
|
|
|
std::mutex Mutex;
|
2019-05-09 20:21:28 +08:00
|
|
|
std::vector<TUStatus> AllStatus;
|
2018-12-06 17:41:04 +08:00
|
|
|
} CaptureTUStatus;
|
|
|
|
MockFSProvider FS;
|
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
ClangdServer Server(CDB, FS, CaptureTUStatus, ClangdServer::optsForTest());
|
|
|
|
Annotations Code("int m^ain () {}");
|
|
|
|
|
|
|
|
// We schedule the following tasks in the queue:
|
|
|
|
// [Update] [GoToDefinition]
|
|
|
|
Server.addDocument(testPath("foo.cpp"), Code.code(), WantDiagnostics::Yes);
|
[clangd] Implement textDocument/declaration from LSP 3.14
Summary:
LSP now reflects the declaration/definition distinction.
Language server changes:
- textDocument/definition now returns a definition if one is found, otherwise
the declaration. It no longer returns declaration + definition if they are
distinct.
- textDocument/declaration returns the best declaration we can find.
- For macros, the active macro definition is returned for both methods.
- For include directive, the top of the target file is returned for both.
There doesn't appear to be a discovery mechanism (we can't return everything to
clients that only know about definition), so this changes existing behavior.
In practice, it should greatly reduce the fraction of the time we need to show
the user a menu of options.
C++ API changes:
- findDefinitions is replaced by locateSymbolAt, which returns a
vector<LocatedSymbol> - one for each symbol under the cursor.
- this contains the preferred declaration, the definition (if found), and
the symbol name
This API enables some potentially-neat extensions, like swapping between decl
and def, and exposing the symbol name to the UI in the case of multiple symbols.
Reviewers: hokein
Subscribers: ilya-biryukov, javed.absar, ioeric, MaskRay, jkorous, arphaman, kadircet, cfe-commits
Differential Revision: https://reviews.llvm.org/D57388
llvm-svn: 352864
2019-02-01 19:26:13 +08:00
|
|
|
Server.locateSymbolAt(testPath("foo.cpp"), Code.point(),
|
|
|
|
[](Expected<std::vector<LocatedSymbol>> Result) {
|
|
|
|
ASSERT_TRUE((bool)Result);
|
|
|
|
});
|
2018-12-06 17:41:04 +08:00
|
|
|
|
|
|
|
ASSERT_TRUE(Server.blockUntilIdleForTest());
|
|
|
|
|
2019-05-09 20:21:28 +08:00
|
|
|
EXPECT_THAT(CaptureTUStatus.allStatus(),
|
2018-12-06 17:41:04 +08:00
|
|
|
ElementsAre(
|
|
|
|
// Statuses of "Update" action.
|
|
|
|
TUState(TUAction::RunningAction, "Update"),
|
|
|
|
TUState(TUAction::BuildingPreamble, "Update"),
|
|
|
|
TUState(TUAction::BuildingFile, "Update"),
|
|
|
|
|
|
|
|
// Statuses of "Definitions" action
|
|
|
|
TUState(TUAction::RunningAction, "Definitions"),
|
|
|
|
TUState(TUAction::Idle, /*No action*/ "")));
|
|
|
|
}
|
|
|
|
|
[clangd] Surface errors from command-line parsing
Summary:
Those errors are exposed at the first character of a file,
for a lack of a better place.
Previously, all errors were stored inside the AST and report
accordingly. However, errors in command-line argument parsing could
result in failure to produce the AST, so we need an alternative ways to
report those errors.
We take the following approach in this patch:
- buildCompilerInvocation() now requires an explicit DiagnosticConsumer.
- TUScheduler and TestTU now collect the diagnostics produced when
parsing command line arguments.
If pasing of the AST failed, diagnostics are reported via a new
ParsingCallbacks::onFailedAST method.
If parsing of the AST succeeded, any errors produced during
command-line parsing are stored alongside the AST inside the
ParsedAST instance and reported as previously by calling the
ParsingCallbacks::onMainAST method;
- The client code that uses ClangdServer's DiagnosticConsumer
does not need to change, it will receive new diagnostics in the
onDiagnosticsReady() callback
Errors produced when parsing command-line arguments are collected using
the same StoreDiags class that is used to collect all other errors. They
are recognized by their location being invalid. IIUC, the location is
invalid as there is no source manager at this point, it is created at a
later stage.
Although technically we might also get diagnostics that mention the
command-line arguments FileID with after the source manager was created
(and they have valid source locations), we choose to not handle those
and they are dropped as not coming from the main file. AFAICT, those
diagnostics should always be notes, therefore it's safe to drop them
without loosing too much information.
Reviewers: kadircet
Reviewed By: kadircet
Subscribers: nridge, javed.absar, MaskRay, jkorous, arphaman, cfe-commits, gribozavr
Tags: #clang
Differential Revision: https://reviews.llvm.org/D66759
llvm-svn: 370177
2019-08-28 17:24:55 +08:00
|
|
|
TEST_F(TUSchedulerTests, CommandLineErrors) {
|
|
|
|
// We should see errors from command-line parsing inside the main file.
|
|
|
|
CDB.ExtraClangFlags = {"-fsome-unknown-flag"};
|
|
|
|
|
2019-09-03 21:56:03 +08:00
|
|
|
// (!) 'Ready' must live longer than TUScheduler.
|
|
|
|
Notification Ready;
|
|
|
|
|
[clangd] Surface errors from command-line parsing
Summary:
Those errors are exposed at the first character of a file,
for a lack of a better place.
Previously, all errors were stored inside the AST and report
accordingly. However, errors in command-line argument parsing could
result in failure to produce the AST, so we need an alternative ways to
report those errors.
We take the following approach in this patch:
- buildCompilerInvocation() now requires an explicit DiagnosticConsumer.
- TUScheduler and TestTU now collect the diagnostics produced when
parsing command line arguments.
If pasing of the AST failed, diagnostics are reported via a new
ParsingCallbacks::onFailedAST method.
If parsing of the AST succeeded, any errors produced during
command-line parsing are stored alongside the AST inside the
ParsedAST instance and reported as previously by calling the
ParsingCallbacks::onMainAST method;
- The client code that uses ClangdServer's DiagnosticConsumer
does not need to change, it will receive new diagnostics in the
onDiagnosticsReady() callback
Errors produced when parsing command-line arguments are collected using
the same StoreDiags class that is used to collect all other errors. They
are recognized by their location being invalid. IIUC, the location is
invalid as there is no source manager at this point, it is created at a
later stage.
Although technically we might also get diagnostics that mention the
command-line arguments FileID with after the source manager was created
(and they have valid source locations), we choose to not handle those
and they are dropped as not coming from the main file. AFAICT, those
diagnostics should always be notes, therefore it's safe to drop them
without loosing too much information.
Reviewers: kadircet
Reviewed By: kadircet
Subscribers: nridge, javed.absar, MaskRay, jkorous, arphaman, cfe-commits, gribozavr
Tags: #clang
Differential Revision: https://reviews.llvm.org/D66759
llvm-svn: 370177
2019-08-28 17:24:55 +08:00
|
|
|
TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
|
|
|
|
/*StorePreambleInMemory=*/true, /*ASTCallbacks=*/captureDiags(),
|
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
|
|
|
|
|
|
|
std::vector<Diag> Diagnostics;
|
|
|
|
updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
|
|
|
|
WantDiagnostics::Yes, [&](std::vector<Diag> D) {
|
|
|
|
Diagnostics = std::move(D);
|
|
|
|
Ready.notify();
|
|
|
|
});
|
|
|
|
Ready.wait();
|
|
|
|
|
|
|
|
EXPECT_THAT(
|
|
|
|
Diagnostics,
|
|
|
|
ElementsAre(AllOf(
|
|
|
|
Field(&Diag::ID, Eq(diag::err_drv_unknown_argument)),
|
|
|
|
Field(&Diag::Name, Eq("drv_unknown_argument")),
|
|
|
|
Field(&Diag::Message, "unknown argument: '-fsome-unknown-flag'"))));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(TUSchedulerTests, CommandLineWarnings) {
|
|
|
|
// We should not see warnings from command-line parsing.
|
|
|
|
CDB.ExtraClangFlags = {"-Wsome-unknown-warning"};
|
|
|
|
|
|
|
|
TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
|
|
|
|
/*StorePreambleInMemory=*/true, /*ASTCallbacks=*/captureDiags(),
|
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
|
|
|
|
|
|
|
Notification Ready;
|
|
|
|
std::vector<Diag> Diagnostics;
|
|
|
|
updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
|
|
|
|
WantDiagnostics::Yes, [&](std::vector<Diag> D) {
|
|
|
|
Diagnostics = std::move(D);
|
|
|
|
Ready.notify();
|
|
|
|
});
|
|
|
|
Ready.wait();
|
|
|
|
|
|
|
|
EXPECT_THAT(Diagnostics, IsEmpty());
|
|
|
|
}
|
|
|
|
|
[clangd] Add callbacks on parsed AST in addition to parsed preambles
Summary:
Will be used for updating the dynamic index on updates to the open files.
Currently we collect only information coming from the preamble
AST. This has a bunch of limitations:
- Dynamic index misses important information from the body of the
file, e.g. locations of definitions.
- XRefs cannot be collected at all, since we can only obtain full
information for the current file (preamble is parsed with skipped
function bodies, therefore not reliable).
This patch only adds the new callback, actually updates to the index
will be done in a follow-up patch.
Reviewers: hokein
Reviewed By: hokein
Subscribers: kadircet, javed.absar, ioeric, MaskRay, jkorous, arphaman, cfe-commits
Differential Revision: https://reviews.llvm.org/D50847
llvm-svn: 340401
2018-08-22 19:39:16 +08:00
|
|
|
} // namespace
|
2018-01-31 16:51:16 +08:00
|
|
|
} // namespace clangd
|
|
|
|
} // namespace clang
|