2018-01-31 16:51:16 +08:00
|
|
|
//===-- TUSchedulerTests.cpp ------------------------------------*- C++ -*-===//
|
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
[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"
|
2018-01-31 16:51:16 +08:00
|
|
|
#include "TUScheduler.h"
|
|
|
|
#include "TestFS.h"
|
|
|
|
#include "gmock/gmock.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
namespace clang {
|
|
|
|
namespace clangd {
|
|
|
|
|
[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::_;
|
2018-07-09 18:45:33 +08:00
|
|
|
using ::testing::Each;
|
[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;
|
2018-01-31 16:51:16 +08:00
|
|
|
using ::testing::Pair;
|
|
|
|
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-03-12 23:28:22 +08:00
|
|
|
void ignoreUpdate(llvm::Optional<std::vector<Diag>>) {}
|
2018-01-31 16:51:16 +08:00
|
|
|
void ignoreError(llvm::Error Err) {
|
|
|
|
handleAllErrors(std::move(Err), [](const llvm::ErrorInfoBase &) {});
|
|
|
|
}
|
|
|
|
|
|
|
|
class TUSchedulerTests : public ::testing::Test {
|
|
|
|
protected:
|
|
|
|
ParseInputs getInputs(PathRef File, std::string Contents) {
|
2018-07-26 17:21:07 +08:00
|
|
|
return ParseInputs{*CDB.getCompileCommand(File),
|
|
|
|
buildTestFS(Files, Timestamps), std::move(Contents)};
|
2018-01-31 16:51:16 +08:00
|
|
|
}
|
|
|
|
|
2018-02-16 17:41:43 +08:00
|
|
|
llvm::StringMap<std::string> Files;
|
2018-07-26 17:21:07 +08:00
|
|
|
llvm::StringMap<time_t> Timestamps;
|
2018-01-31 16:51:16 +08:00
|
|
|
MockCompilationDatabase CDB;
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_F(TUSchedulerTests, MissingFiles) {
|
|
|
|
TUScheduler S(getDefaultAsyncThreadsCount(),
|
|
|
|
/*StorePreamblesInMemory=*/true,
|
2018-05-24 23:50:15 +08:00
|
|
|
/*PreambleParsedCallback=*/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");
|
|
|
|
Files[Added] = "";
|
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
|
|
|
|
2018-02-22 21:11:12 +08:00
|
|
|
S.update(Added, getInputs(Added, ""), WantDiagnostics::No, ignoreUpdate);
|
2018-01-31 16:51:16 +08:00
|
|
|
|
|
|
|
// Assert each operation for missing file is an error (even if it's available
|
|
|
|
// in VFS).
|
2018-02-19 17:56:28 +08:00
|
|
|
S.runWithAST("", Missing, [&](llvm::Expected<InputsAndAST> AST) {
|
2018-01-31 16:51:16 +08:00
|
|
|
ASSERT_FALSE(bool(AST));
|
|
|
|
ignoreError(AST.takeError());
|
|
|
|
});
|
2018-02-19 17:56:28 +08:00
|
|
|
S.runWithPreamble("", Missing,
|
|
|
|
[&](llvm::Expected<InputsAndPreamble> Preamble) {
|
|
|
|
ASSERT_FALSE(bool(Preamble));
|
|
|
|
ignoreError(Preamble.takeError());
|
|
|
|
});
|
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-02-19 17:56:28 +08:00
|
|
|
S.runWithAST("", Added, [&](llvm::Expected<InputsAndAST> AST) {
|
|
|
|
EXPECT_TRUE(bool(AST));
|
|
|
|
});
|
|
|
|
S.runWithPreamble("", Added, [&](llvm::Expected<InputsAndPreamble> Preamble) {
|
2018-01-31 16:51:16 +08:00
|
|
|
EXPECT_TRUE(bool(Preamble));
|
|
|
|
});
|
2018-02-08 15:37:35 +08:00
|
|
|
S.remove(Added);
|
2018-01-31 16:51:16 +08:00
|
|
|
|
|
|
|
// Assert that all operations fail after removing the file.
|
2018-02-19 17:56:28 +08:00
|
|
|
S.runWithAST("", Added, [&](llvm::Expected<InputsAndAST> AST) {
|
2018-01-31 16:51:16 +08:00
|
|
|
ASSERT_FALSE(bool(AST));
|
|
|
|
ignoreError(AST.takeError());
|
|
|
|
});
|
2018-02-19 17:56:28 +08:00
|
|
|
S.runWithPreamble("", Added, [&](llvm::Expected<InputsAndPreamble> Preamble) {
|
2018-01-31 16:51:16 +08:00
|
|
|
ASSERT_FALSE(bool(Preamble));
|
|
|
|
ignoreError(Preamble.takeError());
|
|
|
|
});
|
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(
|
|
|
|
getDefaultAsyncThreadsCount(),
|
|
|
|
/*StorePreamblesInMemory=*/true,
|
2018-05-24 23:50:15 +08:00
|
|
|
/*PreambleParsedCallback=*/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-02-22 21:11:12 +08:00
|
|
|
auto Path = testPath("foo.cpp");
|
|
|
|
S.update(Path, getInputs(Path, ""), WantDiagnostics::Yes,
|
2018-03-12 23:28:22 +08:00
|
|
|
[&](std::vector<Diag>) { Ready.wait(); });
|
2018-02-22 21:11:12 +08:00
|
|
|
|
|
|
|
S.update(Path, getInputs(Path, "request diags"), WantDiagnostics::Yes,
|
2018-03-12 23:28:22 +08:00
|
|
|
[&](std::vector<Diag> Diags) { ++CallbackCount; });
|
2018-02-22 21:11:12 +08:00
|
|
|
S.update(Path, getInputs(Path, "auto (clobbered)"), WantDiagnostics::Auto,
|
2018-03-12 23:28:22 +08:00
|
|
|
[&](std::vector<Diag> Diags) {
|
2018-02-22 21:11:12 +08:00
|
|
|
ADD_FAILURE() << "auto should have been cancelled by auto";
|
|
|
|
});
|
|
|
|
S.update(Path, getInputs(Path, "request no diags"), WantDiagnostics::No,
|
2018-03-12 23:28:22 +08:00
|
|
|
[&](std::vector<Diag> Diags) {
|
2018-02-22 21:11:12 +08:00
|
|
|
ADD_FAILURE() << "no diags should not be called back";
|
|
|
|
});
|
|
|
|
S.update(Path, getInputs(Path, "auto (produces)"), WantDiagnostics::Auto,
|
2018-03-12 23:28:22 +08:00
|
|
|
[&](std::vector<Diag> Diags) { ++CallbackCount; });
|
2018-02-22 21:11:12 +08:00
|
|
|
Ready.notify();
|
|
|
|
}
|
|
|
|
EXPECT_EQ(2, CallbackCount);
|
|
|
|
}
|
|
|
|
|
2018-03-02 16:56:37 +08:00
|
|
|
TEST_F(TUSchedulerTests, Debounce) {
|
|
|
|
std::atomic<int> CallbackCount(0);
|
|
|
|
{
|
|
|
|
TUScheduler S(getDefaultAsyncThreadsCount(),
|
|
|
|
/*StorePreamblesInMemory=*/true,
|
2018-05-24 23:50:15 +08:00
|
|
|
/*PreambleParsedCallback=*/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::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");
|
|
|
|
S.update(Path, getInputs(Path, "auto (debounced)"), WantDiagnostics::Auto,
|
2018-03-12 23:28:22 +08:00
|
|
|
[&](std::vector<Diag> Diags) {
|
2018-03-02 16:56:37 +08:00
|
|
|
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-03-02 16:56:37 +08:00
|
|
|
S.update(Path, getInputs(Path, "auto (timed out)"), WantDiagnostics::Auto,
|
2018-03-12 23:28:22 +08:00
|
|
|
[&](std::vector<Diag> Diags) { ++CallbackCount; });
|
2018-03-03 02:23:41 +08:00
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
2018-03-02 16:56:37 +08:00
|
|
|
S.update(Path, getInputs(Path, "auto (shut down)"), WantDiagnostics::Auto,
|
2018-03-12 23:28:22 +08:00
|
|
|
[&](std::vector<Diag> Diags) { ++CallbackCount; });
|
2018-03-02 16:56:37 +08:00
|
|
|
}
|
|
|
|
EXPECT_EQ(2, CallbackCount);
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
{
|
|
|
|
TUScheduler S(getDefaultAsyncThreadsCount(),
|
|
|
|
/*StorePreamblesInMemory=*/true,
|
2018-05-24 23:50:15 +08:00
|
|
|
/*PreambleParsedCallback=*/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::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
|
|
|
}
|
|
|
|
|
|
|
|
llvm::StringRef Contents1 = R"cpp(int a;)cpp";
|
|
|
|
llvm::StringRef Contents2 = R"cpp(int main() { return 1; })cpp";
|
|
|
|
llvm::StringRef Contents3 =
|
|
|
|
R"cpp(int a; int b; int sum() { return a + b; })cpp";
|
|
|
|
|
|
|
|
llvm::StringRef AllContents[] = {Contents1, Contents2, Contents3};
|
|
|
|
const int AllContentsSize = 3;
|
|
|
|
|
[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
|
|
|
// Scheduler may run tasks asynchronously, but should propagate the context.
|
|
|
|
// We stash a nonce in the context, and verify it in the task.
|
|
|
|
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-02-22 21:11:12 +08:00
|
|
|
S.update(File, Inputs, WantDiagnostics::Auto,
|
2018-08-09 17:05:45 +08:00
|
|
|
[File, Nonce, &Mut,
|
2018-03-12 23:28:22 +08:00
|
|
|
&TotalUpdates](llvm::Optional<std::vector<Diag>> Diags) {
|
[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
|
|
|
EXPECT_THAT(Context::current().get(NonceKey),
|
|
|
|
Pointee(Nonce));
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> Lock(Mut);
|
|
|
|
++TotalUpdates;
|
2018-08-09 17:05:45 +08:00
|
|
|
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-02-19 17:56:28 +08:00
|
|
|
S.runWithAST("CheckAST", File,
|
2018-08-09 17:05:45 +08:00
|
|
|
[File, Inputs, Nonce, &Mut,
|
2018-02-19 17:56:28 +08:00
|
|
|
&TotalASTReads](llvm::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;
|
2018-08-09 17:05:45 +08:00
|
|
|
EXPECT_EQ(
|
|
|
|
File,
|
|
|
|
*TUScheduler::getFileBeingProcessedInContext());
|
2018-02-19 17:56:28 +08:00
|
|
|
});
|
[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(
|
|
|
|
"CheckPreamble", File,
|
|
|
|
[File, Inputs, Nonce, &Mut, &TotalPreambleReads](
|
|
|
|
llvm::Expected<InputsAndPreamble> Preamble) {
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
} // 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;
|
|
|
|
TUScheduler S(
|
|
|
|
/*AsyncThreadsCount=*/1, /*StorePreambleInMemory=*/true,
|
|
|
|
PreambleParsedCallback(),
|
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), Policy);
|
|
|
|
|
|
|
|
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.
|
|
|
|
S.update(Foo, getInputs(Foo, SourceContents), WantDiagnostics::Yes,
|
|
|
|
[&BuiltASTCounter](std::vector<Diag> Diags) { ++BuiltASTCounter; });
|
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1)));
|
|
|
|
ASSERT_EQ(BuiltASTCounter.load(), 1);
|
|
|
|
|
|
|
|
// Build two more files. Since we can retain only 2 ASTs, these should be the
|
|
|
|
// ones we see in the cache later.
|
|
|
|
S.update(Bar, getInputs(Bar, SourceContents), WantDiagnostics::Yes,
|
|
|
|
[&BuiltASTCounter](std::vector<Diag> Diags) { ++BuiltASTCounter; });
|
|
|
|
S.update(Baz, getInputs(Baz, SourceContents), WantDiagnostics::Yes,
|
|
|
|
[&BuiltASTCounter](std::vector<Diag> Diags) { ++BuiltASTCounter; });
|
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1)));
|
|
|
|
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-07-26 17:21:07 +08:00
|
|
|
S.update(Foo, getInputs(Foo, OtherSourceContents), WantDiagnostics::Yes,
|
[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
|
|
|
[&BuiltASTCounter](std::vector<Diag> Diags) { ++BuiltASTCounter; });
|
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1)));
|
|
|
|
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-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.
|
|
|
|
TUScheduler S(
|
|
|
|
/*AsyncThreadsCount=*/4, /*StorePreambleInMemory=*/true,
|
|
|
|
PreambleParsedCallback(),
|
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
|
|
|
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);
|
|
|
|
S.update(Foo, getInputs(Foo, NonEmptyPreamble), WantDiagnostics::Auto,
|
|
|
|
[](std::vector<Diag>) {});
|
|
|
|
for (int I = 0; I < ReadsToSchedule; ++I) {
|
|
|
|
S.runWithPreamble(
|
|
|
|
"test", Foo,
|
|
|
|
[I, &PreamblesMut, &Preambles](llvm::Expected<InputsAndPreamble> IP) {
|
|
|
|
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) {
|
|
|
|
TUScheduler S(
|
|
|
|
/*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
|
|
|
|
/*StorePreambleInMemory=*/true, PreambleParsedCallback(),
|
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
|
|
|
|
|
|
|
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.
|
|
|
|
auto DoUpdate = [&](ParseInputs Inputs) -> bool {
|
|
|
|
std::atomic<bool> Updated(false);
|
|
|
|
Updated = false;
|
|
|
|
S.update(Source, std::move(Inputs), WantDiagnostics::Yes,
|
|
|
|
[&Updated](std::vector<Diag>) { Updated = true; });
|
|
|
|
bool UpdateFinished = S.blockUntilIdle(timeoutSeconds(1));
|
|
|
|
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.
|
|
|
|
ASSERT_TRUE(DoUpdate(getInputs(Source, SourceContents)));
|
|
|
|
ASSERT_FALSE(DoUpdate(getInputs(Source, SourceContents)));
|
|
|
|
|
|
|
|
// Update to a header should cause a rebuild, though.
|
|
|
|
Files[Header] = time_t(1);
|
|
|
|
ASSERT_TRUE(DoUpdate(getInputs(Source, SourceContents)));
|
|
|
|
ASSERT_FALSE(DoUpdate(getInputs(Source, SourceContents)));
|
|
|
|
|
|
|
|
// Update to the contents should cause a rebuild.
|
|
|
|
auto OtherSourceContents = R"cpp(
|
|
|
|
#include "foo.h"
|
|
|
|
int c = d;
|
|
|
|
)cpp";
|
|
|
|
ASSERT_TRUE(DoUpdate(getInputs(Source, OtherSourceContents)));
|
|
|
|
ASSERT_FALSE(DoUpdate(getInputs(Source, OtherSourceContents)));
|
|
|
|
|
|
|
|
// Update to the compile commands should also cause a rebuild.
|
|
|
|
CDB.ExtraClangFlags.push_back("-DSOMETHING");
|
|
|
|
ASSERT_TRUE(DoUpdate(getInputs(Source, OtherSourceContents)));
|
|
|
|
ASSERT_FALSE(DoUpdate(getInputs(Source, OtherSourceContents)));
|
|
|
|
}
|
|
|
|
|
2018-07-31 19:47:52 +08:00
|
|
|
TEST_F(TUSchedulerTests, NoChangeDiags) {
|
|
|
|
TUScheduler S(
|
|
|
|
/*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
|
|
|
|
/*StorePreambleInMemory=*/true, PreambleParsedCallback(),
|
|
|
|
/*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
|
|
|
|
ASTRetentionPolicy());
|
|
|
|
|
|
|
|
auto FooCpp = testPath("foo.cpp");
|
|
|
|
auto Contents = "int a; int b;";
|
|
|
|
|
|
|
|
S.update(FooCpp, getInputs(FooCpp, Contents), WantDiagnostics::No,
|
|
|
|
[](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
|
|
|
|
S.runWithAST("touchAST", FooCpp, [](llvm::Expected<InputsAndAST> IA) {
|
|
|
|
// Make sure the AST was actually built.
|
|
|
|
cantFail(std::move(IA));
|
|
|
|
});
|
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1)));
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
S.update(FooCpp, getInputs(FooCpp, Contents), WantDiagnostics::Auto,
|
|
|
|
[&](std::vector<Diag>) { SeenDiags = true; });
|
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1)));
|
|
|
|
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.
|
|
|
|
S.update(
|
|
|
|
FooCpp, getInputs(FooCpp, Contents), WantDiagnostics::Auto,
|
|
|
|
[&](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
|
|
|
|
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1)));
|
|
|
|
}
|
|
|
|
|
2018-01-31 16:51:16 +08:00
|
|
|
} // namespace clangd
|
|
|
|
} // namespace clang
|