[clang-tools-extra] 7d20e80 - [clangd] Show background index status using LSP 3.15 work-done progress notifications
Sam McCall via cfe-commits
cfe-commits at lists.llvm.org
Fri Jan 24 03:21:17 PST 2020
Author: Sam McCall
Date: 2020-01-24T12:21:08+01:00
New Revision: 7d20e80225b3e4f3f5a8c57929061bef55f3d855
URL: https://github.com/llvm/llvm-project/commit/7d20e80225b3e4f3f5a8c57929061bef55f3d855
DIFF: https://github.com/llvm/llvm-project/commit/7d20e80225b3e4f3f5a8c57929061bef55f3d855.diff
LOG: [clangd] Show background index status using LSP 3.15 work-done progress notifications
Summary:
It simply shows the completed/total items on the background queue, e.g.
indexing: 233/1000
The denominator is reset to zero every time the queue goes idle.
The protocol is fairly complicated here (requires creating a remote "progress"
resource before sending updates). We implement the full protocol, but I've added
an extension allowing it to be skipped to reduce the burden on clients - in
particular the lit test takes this shortcut.
The addition of background index progress to DiagnosticConsumer seems ridiculous
at first glance, but I believe that interface is trending in the direction of
"ClangdServer callbacks" anyway. It's due for a rename, but otherwise actually
fits.
Reviewers: kadircet, usaxena95
Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, jfb, cfe-commits, llvm-commits
Tags: #clang, #llvm
Differential Revision: https://reviews.llvm.org/D73218
Added:
Modified:
clang-tools-extra/clangd/ClangdLSPServer.cpp
clang-tools-extra/clangd/ClangdLSPServer.h
clang-tools-extra/clangd/ClangdServer.cpp
clang-tools-extra/clangd/ClangdServer.h
clang-tools-extra/clangd/Protocol.cpp
clang-tools-extra/clangd/Protocol.h
clang-tools-extra/clangd/index/Background.cpp
clang-tools-extra/clangd/index/Background.h
clang-tools-extra/clangd/index/BackgroundQueue.cpp
clang-tools-extra/clangd/test/Inputs/background-index/definition.jsonrpc
clang-tools-extra/clangd/test/background-index.test
clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp
llvm/include/llvm/Support/JSON.h
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 69b4308a1c9e..633c61e681f9 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -33,6 +33,7 @@
#include "llvm/Support/ScopedPrinter.h"
#include <cstddef>
#include <memory>
+#include <mutex>
#include <string>
#include <vector>
@@ -522,6 +523,9 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
SupportFileStatus = Params.initializationOptions.FileStatus;
HoverContentFormat = Params.capabilities.HoverContentFormat;
SupportsOffsetsInSignatureHelp = Params.capabilities.OffsetsInSignatureHelp;
+ if (Params.capabilities.WorkDoneProgress)
+ BackgroundIndexProgressState = BackgroundIndexProgress::Empty;
+ BackgroundIndexSkipCreate = Params.capabilities.ImplicitProgressCreation;
// Per LSP, renameProvider can be either boolean or RenameOptions.
// RenameOptions will be specified if the client states it supports prepare.
@@ -1378,6 +1382,74 @@ void ClangdLSPServer::onDiagnosticsReady(PathRef File,
publishDiagnostics(URI, std::move(LSPDiagnostics));
}
+void ClangdLSPServer::onBackgroundIndexProgress(
+ const BackgroundQueue::Stats &Stats) {
+ static const char ProgressToken[] = "backgroundIndexProgress";
+ std::lock_guard<std::mutex> Lock(BackgroundIndexProgressMutex);
+
+ auto NotifyProgress = [this](const BackgroundQueue::Stats &Stats) {
+ if (BackgroundIndexProgressState != BackgroundIndexProgress::Live) {
+ WorkDoneProgressBegin Begin;
+ Begin.percentage = true;
+ Begin.title = "indexing";
+ progress(ProgressToken, std::move(Begin));
+ BackgroundIndexProgressState = BackgroundIndexProgress::Live;
+ }
+
+ if (Stats.Completed < Stats.Enqueued) {
+ assert(Stats.Enqueued > Stats.LastIdle);
+ WorkDoneProgressReport Report;
+ Report.percentage = 100.0 * (Stats.Completed - Stats.LastIdle) /
+ (Stats.Enqueued - Stats.LastIdle);
+ Report.message =
+ llvm::formatv("{0}/{1}", Stats.Completed - Stats.LastIdle,
+ Stats.Enqueued - Stats.LastIdle);
+ progress(ProgressToken, std::move(Report));
+ } else {
+ assert(Stats.Completed == Stats.Enqueued);
+ progress(ProgressToken, WorkDoneProgressEnd());
+ BackgroundIndexProgressState = BackgroundIndexProgress::Empty;
+ }
+ };
+
+ switch (BackgroundIndexProgressState) {
+ case BackgroundIndexProgress::Unsupported:
+ return;
+ case BackgroundIndexProgress::Creating:
+ // Cache this update for when the progress bar is available.
+ PendingBackgroundIndexProgress = Stats;
+ return;
+ case BackgroundIndexProgress::Empty: {
+ if (BackgroundIndexSkipCreate) {
+ NotifyProgress(Stats);
+ break;
+ }
+ // Cache this update for when the progress bar is available.
+ PendingBackgroundIndexProgress = Stats;
+ BackgroundIndexProgressState = BackgroundIndexProgress::Creating;
+ WorkDoneProgressCreateParams CreateRequest;
+ CreateRequest.token = ProgressToken;
+ call<std::nullptr_t>(
+ "window/workDoneProgress/create", CreateRequest,
+ [this, NotifyProgress](llvm::Expected<std::nullptr_t> E) {
+ std::lock_guard<std::mutex> Lock(BackgroundIndexProgressMutex);
+ if (E) {
+ NotifyProgress(this->PendingBackgroundIndexProgress);
+ } else {
+ elog("Failed to create background index progress bar: {0}",
+ E.takeError());
+ // give up forever rather than thrashing about
+ BackgroundIndexProgressState = BackgroundIndexProgress::Unsupported;
+ }
+ });
+ break;
+ }
+ case BackgroundIndexProgress::Live:
+ NotifyProgress(Stats);
+ break;
+ }
+}
+
void ClangdLSPServer::onFileUpdated(PathRef File, const TUStatus &Status) {
if (!SupportFileStatus)
return;
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 6f6d017b7f51..82fc49a66d06 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -60,6 +60,7 @@ class ClangdLSPServer : private DiagnosticsConsumer {
void
onHighlightingsReady(PathRef File,
std::vector<HighlightingToken> Highlightings) override;
+ void onBackgroundIndexProgress(const BackgroundQueue::Stats &Stats) override;
// LSP methods. Notifications have signature void(const Params&).
// Calls have signature void(const Params&, Callback<Response>).
@@ -185,6 +186,12 @@ class ClangdLSPServer : private DiagnosticsConsumer {
void callRaw(StringRef Method, llvm::json::Value Params,
Callback<llvm::json::Value> CB);
void notify(StringRef Method, llvm::json::Value Params);
+ template <typename T> void progress(const llvm::json::Value &Token, T Value) {
+ ProgressParams<T> Params;
+ Params.token = Token;
+ Params.value = std::move(Value);
+ notify("$/progress", Params);
+ }
const FileSystemProvider &FSProvider;
/// Options used for code completion
@@ -205,6 +212,24 @@ class ClangdLSPServer : private DiagnosticsConsumer {
MarkupKind HoverContentFormat = MarkupKind::PlainText;
/// Whether the client supports offsets for parameter info labels.
bool SupportsOffsetsInSignatureHelp = false;
+ std::mutex BackgroundIndexProgressMutex;
+ enum class BackgroundIndexProgress {
+ // Client doesn't support reporting progress. No transitions possible.
+ Unsupported,
+ // The queue is idle, and the client has no progress bar.
+ // Can transition to Creating when we have some activity.
+ Empty,
+ // We've requested the client to create a progress bar.
+ // Meanwhile, the state is buffered in PendingBackgroundIndexProgress.
+ Creating,
+ // The client has a progress bar, and we can send it updates immediately.
+ Live,
+ } BackgroundIndexProgressState = BackgroundIndexProgress::Unsupported;
+ // The progress to send when the progress bar is created.
+ // Only valid in state Creating.
+ BackgroundQueue::Stats PendingBackgroundIndexProgress;
+ /// LSP extension: skip WorkDoneProgressCreate, just send progress streams.
+ bool BackgroundIndexSkipCreate = false;
// Store of the current versions of the open documents.
DraftStore DraftMgr;
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index c71a8a0d566b..fb02c1371ed3 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -147,7 +147,10 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
Context::current().clone(), FSProvider, CDB,
BackgroundIndexStorage::createDiskBackedStorageFactory(
[&CDB](llvm::StringRef File) { return CDB.getProjectInfo(File); }),
- std::max(Opts.AsyncThreadsCount, 1u));
+ std::max(Opts.AsyncThreadsCount, 1u),
+ [&DiagConsumer](BackgroundQueue::Stats S) {
+ DiagConsumer.onBackgroundIndexProgress(S);
+ });
AddIndex(BackgroundIdx.get());
}
if (DynamicIdx)
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 4574d6c35034..142ca9bc507f 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -56,6 +56,9 @@ class DiagnosticsConsumer {
virtual void
onHighlightingsReady(PathRef File,
std::vector<HighlightingToken> Highlightings) {}
+
+ // Called when background indexing tasks are enqueued, started, or completed.
+ virtual void onBackgroundIndexProgress(const BackgroundQueue::Stats &Stats) {}
};
/// When set, used by ClangdServer to get clang-tidy options for each particular
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 29ceb1da5456..87f76565147e 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -347,6 +347,12 @@ bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R) {
}
}
}
+ if (auto *Window = O->getObject("window")) {
+ if (auto WorkDoneProgress = Window->getBoolean("workDoneProgress"))
+ R.WorkDoneProgress = *WorkDoneProgress;
+ if (auto Implicit = Window->getBoolean("implicitWorkDoneProgressCreate"))
+ R.ImplicitProgressCreation = *Implicit;
+ }
if (auto *OffsetEncoding = O->get("offsetEncoding")) {
R.offsetEncoding.emplace();
if (!fromJSON(*OffsetEncoding, *R.offsetEncoding))
@@ -370,6 +376,40 @@ bool fromJSON(const llvm::json::Value &Params, InitializeParams &R) {
return true;
}
+llvm::json::Value toJSON(const WorkDoneProgressCreateParams &P) {
+ return llvm::json::Object{{"token", P.token}};
+}
+
+llvm::json::Value toJSON(const WorkDoneProgressBegin &P) {
+ llvm::json::Object Result{
+ {"kind", "begin"},
+ {"title", P.title},
+ };
+ if (P.cancellable)
+ Result["cancellable"] = true;
+ if (P.percentage)
+ Result["percentage"] = 0;
+ return Result;
+}
+
+llvm::json::Value toJSON(const WorkDoneProgressReport &P) {
+ llvm::json::Object Result{{"kind", "report"}};
+ if (P.cancellable)
+ Result["cancellable"] = *P.cancellable;
+ if (P.message)
+ Result["message"] = *P.message;
+ if (P.percentage)
+ Result["percentage"] = *P.percentage;
+ return Result;
+}
+
+llvm::json::Value toJSON(const WorkDoneProgressEnd &P) {
+ llvm::json::Object Result{{"kind", "end"}};
+ if (P.message)
+ Result["message"] = *P.message;
+ return Result;
+}
+
llvm::json::Value toJSON(const MessageType &R) {
return static_cast<int64_t>(R);
}
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 1b999f1131d4..255629f0a0ec 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -427,6 +427,16 @@ struct ClientCapabilities {
/// The client supports testing for validity of rename operations
/// before execution.
bool RenamePrepareSupport = false;
+
+ /// The client supports progress notifications.
+ /// window.workDoneProgress
+ bool WorkDoneProgress = false;
+
+ /// The client supports implicit $/progress work-done progress streams,
+ /// without a preceding window/workDoneProgress/create.
+ /// This is a clangd extension.
+ /// window.implicitWorkDoneProgressCreate
+ bool ImplicitProgressCreation = false;
};
bool fromJSON(const llvm::json::Value &, ClientCapabilities &);
@@ -499,6 +509,89 @@ struct InitializeParams {
};
bool fromJSON(const llvm::json::Value &, InitializeParams &);
+struct WorkDoneProgressCreateParams {
+ /// The token to be used to report progress.
+ llvm::json::Value token = nullptr;
+};
+llvm::json::Value toJSON(const WorkDoneProgressCreateParams &P);
+
+template <typename T> struct ProgressParams {
+ /// The progress token provided by the client or server.
+ llvm::json::Value token = nullptr;
+
+ /// The progress data.
+ T value;
+};
+template <typename T> llvm::json::Value toJSON(const ProgressParams<T> &P) {
+ return llvm::json::Object{{"token", P.token}, {"value", P.value}};
+}
+/// To start progress reporting a $/progress notification with the following
+/// payload must be sent.
+struct WorkDoneProgressBegin {
+ /// Mandatory title of the progress operation. Used to briefly inform about
+ /// the kind of operation being performed.
+ ///
+ /// Examples: "Indexing" or "Linking dependencies".
+ std::string title;
+
+ /// Controls if a cancel button should show to allow the user to cancel the
+ /// long-running operation. Clients that don't support cancellation are
+ /// allowed to ignore the setting.
+ bool cancellable = false;
+
+ /// Optional progress percentage to display (value 100 is considered 100%).
+ /// If not provided infinite progress is assumed and clients are allowed
+ /// to ignore the `percentage` value in subsequent in report notifications.
+ ///
+ /// The value should be steadily rising. Clients are free to ignore values
+ /// that are not following this rule.
+ ///
+ /// Clangd implementation note: we only send nonzero percentages in
+ /// the WorkProgressReport. 'true' here means percentages will be used.
+ bool percentage = false;
+};
+llvm::json::Value toJSON(const WorkDoneProgressBegin &);
+
+/// Reporting progress is done using the following payload.
+struct WorkDoneProgressReport {
+ /// Mandatory title of the progress operation. Used to briefly inform about
+ /// the kind of operation being performed.
+ ///
+ /// Examples: "Indexing" or "Linking dependencies".
+ std::string title;
+
+ /// Controls enablement state of a cancel button. This property is only valid
+ /// if a cancel button got requested in the `WorkDoneProgressStart` payload.
+ ///
+ /// Clients that don't support cancellation or don't support control
+ /// the button's enablement state are allowed to ignore the setting.
+ llvm::Optional<bool> cancellable;
+
+ /// Optional, more detailed associated progress message. Contains
+ /// complementary information to the `title`.
+ ///
+ /// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep".
+ /// If unset, the previous progress message (if any) is still valid.
+ llvm::Optional<std::string> message;
+
+ /// Optional progress percentage to display (value 100 is considered 100%).
+ /// If not provided infinite progress is assumed and clients are allowed
+ /// to ignore the `percentage` value in subsequent in report notifications.
+ ///
+ /// The value should be steadily rising. Clients are free to ignore values
+ /// that are not following this rule.
+ llvm::Optional<double> percentage;
+};
+llvm::json::Value toJSON(const WorkDoneProgressReport &);
+//
+/// Signals the end of progress reporting.
+struct WorkDoneProgressEnd {
+ /// Optional, a final message indicating to for example indicate the outcome
+ /// of the operation.
+ llvm::Optional<std::string> message;
+};
+llvm::json::Value toJSON(const WorkDoneProgressEnd &);
+
enum class MessageType {
/// An error message.
Error = 1,
diff --git a/clang-tools-extra/clangd/index/Background.cpp b/clang-tools-extra/clangd/index/Background.cpp
index 1fa9825291fe..dd198f0046b1 100644
--- a/clang-tools-extra/clangd/index/Background.cpp
+++ b/clang-tools-extra/clangd/index/Background.cpp
@@ -137,11 +137,13 @@ bool shardIsStale(const LoadedShard &LS, llvm::vfs::FileSystem *FS) {
BackgroundIndex::BackgroundIndex(
Context BackgroundContext, const FileSystemProvider &FSProvider,
const GlobalCompilationDatabase &CDB,
- BackgroundIndexStorage::Factory IndexStorageFactory, size_t ThreadPoolSize)
- : SwapIndex(std::make_unique<MemIndex>()), FSProvider(FSProvider),
- CDB(CDB), BackgroundContext(std::move(BackgroundContext)),
+ BackgroundIndexStorage::Factory IndexStorageFactory, size_t ThreadPoolSize,
+ std::function<void(BackgroundQueue::Stats)> OnProgress)
+ : SwapIndex(std::make_unique<MemIndex>()), FSProvider(FSProvider), CDB(CDB),
+ BackgroundContext(std::move(BackgroundContext)),
Rebuilder(this, &IndexedSymbols, ThreadPoolSize),
IndexStorageFactory(std::move(IndexStorageFactory)),
+ Queue(std::move(OnProgress)),
CommandsChanged(
CDB.watch([&](const std::vector<std::string> &ChangedFiles) {
enqueue(ChangedFiles);
diff --git a/clang-tools-extra/clangd/index/Background.h b/clang-tools-extra/clangd/index/Background.h
index 7ce9e7bf17be..b11008de15d0 100644
--- a/clang-tools-extra/clangd/index/Background.h
+++ b/clang-tools-extra/clangd/index/Background.h
@@ -78,6 +78,17 @@ class BackgroundQueue {
bool operator<(const Task &O) const { return QueuePri < O.QueuePri; }
};
+ // Describes the number of tasks processed by the queue.
+ struct Stats {
+ unsigned Enqueued = 0; // Total number of tasks ever enqueued.
+ unsigned Active = 0; // Tasks being currently processed by a worker.
+ unsigned Completed = 0; // Tasks that have been finished.
+ unsigned LastIdle = 0; // Number of completed tasks when last empty.
+ };
+
+ BackgroundQueue(std::function<void(Stats)> OnProgress = nullptr)
+ : OnProgress(OnProgress) {}
+
// Add tasks to the queue.
void push(Task);
void append(std::vector<Task>);
@@ -100,12 +111,15 @@ class BackgroundQueue {
blockUntilIdleForTest(llvm::Optional<double> TimeoutSeconds);
private:
+ void notifyProgress() const; // Requires lock Mu
+
std::mutex Mu;
- unsigned NumActiveTasks = 0; // Only idle when queue is empty *and* no tasks.
+ Stats Stat;
std::condition_variable CV;
bool ShouldStop = false;
std::vector<Task> Queue; // max-heap
llvm::StringMap<unsigned> Boosts;
+ std::function<void(Stats)> OnProgress;
};
// Builds an in-memory index by by running the static indexer action over
@@ -121,7 +135,8 @@ class BackgroundIndex : public SwapIndex {
Context BackgroundContext, const FileSystemProvider &,
const GlobalCompilationDatabase &CDB,
BackgroundIndexStorage::Factory IndexStorageFactory,
- size_t ThreadPoolSize = llvm::heavyweight_hardware_concurrency());
+ size_t ThreadPoolSize = llvm::heavyweight_hardware_concurrency(),
+ std::function<void(BackgroundQueue::Stats)> OnProgress = nullptr);
~BackgroundIndex(); // Blocks while the current task finishes.
// Enqueue translation units for indexing.
diff --git a/clang-tools-extra/clangd/index/BackgroundQueue.cpp b/clang-tools-extra/clangd/index/BackgroundQueue.cpp
index 914097225b93..00d483f38005 100644
--- a/clang-tools-extra/clangd/index/BackgroundQueue.cpp
+++ b/clang-tools-extra/clangd/index/BackgroundQueue.cpp
@@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
+#include "Logger.h"
#include "index/Background.h"
namespace clang {
@@ -28,10 +29,11 @@ void BackgroundQueue::work(std::function<void()> OnIdle) {
CV.notify_all();
return;
}
- ++NumActiveTasks;
+ ++Stat.Active;
std::pop_heap(Queue.begin(), Queue.end());
Task = std::move(Queue.back());
Queue.pop_back();
+ notifyProgress();
}
if (Task->ThreadPri != llvm::ThreadPriority::Default &&
@@ -43,14 +45,20 @@ void BackgroundQueue::work(std::function<void()> OnIdle) {
{
std::unique_lock<std::mutex> Lock(Mu);
- if (NumActiveTasks == 1 && Queue.empty() && OnIdle) {
+ ++Stat.Completed;
+ if (Stat.Active == 1 && Queue.empty()) {
// We just finished the last item, the queue is going idle.
- Lock.unlock();
- OnIdle();
- Lock.lock();
+ assert(ShouldStop || Stat.Completed == Stat.Enqueued);
+ Stat.LastIdle = Stat.Completed;
+ if (OnIdle) {
+ Lock.unlock();
+ OnIdle();
+ Lock.lock();
+ }
}
- assert(NumActiveTasks > 0 && "before decrementing");
- --NumActiveTasks;
+ assert(Stat.Active > 0 && "before decrementing");
+ --Stat.Active;
+ notifyProgress();
}
CV.notify_all();
}
@@ -70,6 +78,8 @@ void BackgroundQueue::push(Task T) {
T.QueuePri = std::max(T.QueuePri, Boosts.lookup(T.Tag));
Queue.push_back(std::move(T));
std::push_heap(Queue.begin(), Queue.end());
+ ++Stat.Enqueued;
+ notifyProgress();
}
CV.notify_all();
}
@@ -81,6 +91,8 @@ void BackgroundQueue::append(std::vector<Task> Tasks) {
T.QueuePri = std::max(T.QueuePri, Boosts.lookup(T.Tag));
std::move(Tasks.begin(), Tasks.end(), std::back_inserter(Queue));
std::make_heap(Queue.begin(), Queue.end());
+ Stat.Enqueued += Tasks.size();
+ notifyProgress();
}
CV.notify_all();
}
@@ -108,7 +120,14 @@ bool BackgroundQueue::blockUntilIdleForTest(
llvm::Optional<double> TimeoutSeconds) {
std::unique_lock<std::mutex> Lock(Mu);
return wait(Lock, CV, timeoutSeconds(TimeoutSeconds),
- [&] { return Queue.empty() && NumActiveTasks == 0; });
+ [&] { return Queue.empty() && Stat.Active == 0; });
+}
+
+void BackgroundQueue::notifyProgress() const {
+ dlog("Queue: {0}/{1} ({2} active). Last idle at {3}", Stat.Completed,
+ Stat.Enqueued, Stat.Active, Stat.LastIdle);
+ if (OnProgress)
+ OnProgress(Stat);
}
} // namespace clangd
diff --git a/clang-tools-extra/clangd/test/Inputs/background-index/definition.jsonrpc b/clang-tools-extra/clangd/test/Inputs/background-index/definition.jsonrpc
index de99072b6a4c..9ae0d928ac04 100644
--- a/clang-tools-extra/clangd/test/Inputs/background-index/definition.jsonrpc
+++ b/clang-tools-extra/clangd/test/Inputs/background-index/definition.jsonrpc
@@ -5,7 +5,7 @@
"params": {
"processId": 123,
"rootPath": "clangd",
- "capabilities": {},
+ "capabilities": { "window": { "workDoneProgress": true, "implicitWorkDoneProgressCreate": true} },
"trace": "off"
}
}
@@ -22,6 +22,31 @@
}
}
}
+# BUILD: "method": "$/progress"
+# BUILD-NEXT: "params": {
+# BUILD-NEXT: "token": "backgroundIndexProgress",
+# BUILD-NEXT: "value": {
+# BUILD-NEXT: "kind": "begin",
+# BUILD-NEXT: "percentage": 0,
+# BUILD-NEXT: "title": "indexing"
+# BUILD-NEXT: }
+# BUILD-NEXT: }
+# BUILD: "method": "$/progress"
+# BUILD-NEXT: "params": {
+# BUILD-NEXT: "token": "backgroundIndexProgress",
+# BUILD-NEXT: "value": {
+# BUILD-NEXT: "kind": "report",
+# BUILD-NEXT: "message": "0/1",
+# BUILD-NEXT: "percentage": 0
+# BUILD-NEXT: }
+# BUILD-NEXT: }
+# later...
+# BUILD: "message": "1/2",
+# BUILD-NEXT: "percentage": 50
+# finally...
+# BUILD: "kind": "end"
+# when using the index, we spawn one task to read it, but not more.
+# USE-NOT: "message": "1/2",
---
{
"jsonrpc": "2.0",
diff --git a/clang-tools-extra/clangd/test/background-index.test b/clang-tools-extra/clangd/test/background-index.test
index 06efcf9a9f21..821ac7174f59 100644
--- a/clang-tools-extra/clangd/test/background-index.test
+++ b/clang-tools-extra/clangd/test/background-index.test
@@ -11,7 +11,8 @@
# We're editing bar.cpp, which includes foo.h.
# foo() is declared in foo.h and defined in foo.cpp.
# The background index should allow us to go-to-definition on foo().
-# RUN: clangd -background-index -lit-test < %t/definition.jsonrpc | FileCheck %t/definition.jsonrpc
+# We should also see indexing progress notifications.
+# RUN: clangd -background-index -lit-test < %t/definition.jsonrpc | FileCheck %t/definition.jsonrpc --check-prefixes=CHECK,BUILD
# Test that the index is writing files in the expected location.
# RUN: ls %t/.clangd/index/foo.cpp.*.idx
@@ -19,4 +20,4 @@
# Test the index is read from disk: delete code and restart clangd.
# RUN: rm %t/foo.cpp
-# RUN: clangd -background-index -lit-test < %t/definition.jsonrpc | FileCheck %t/definition.jsonrpc
+# RUN: clangd -background-index -lit-test < %t/definition.jsonrpc | FileCheck %t/definition.jsonrpc --check-prefixes=CHECK,USE
diff --git a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp
index 345be6a267e7..00fe975dbc12 100644
--- a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp
+++ b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp
@@ -711,5 +711,53 @@ TEST(BackgroundQueueTest, Boost) {
}
}
+TEST(BackgroundQueueTest, Progress) {
+ using testing::AnyOf;
+ BackgroundQueue::Stats S;
+ BackgroundQueue Q([&](BackgroundQueue::Stats New) {
+ // Verify values are sane.
+ // Items are enqueued one at a time (at least in this test).
+ EXPECT_THAT(New.Enqueued, AnyOf(S.Enqueued, S.Enqueued + 1));
+ // Items are completed one at a time.
+ EXPECT_THAT(New.Completed, AnyOf(S.Completed, S.Completed + 1));
+ // Items are started or completed one at a time.
+ EXPECT_THAT(New.Active, AnyOf(S.Active - 1, S.Active, S.Active + 1));
+ // Idle point only advances in time.
+ EXPECT_GE(New.LastIdle, S.LastIdle);
+ // Idle point is a task that has been completed in the past.
+ EXPECT_LE(New.LastIdle, New.Completed);
+ // LastIdle is now only if we're really idle.
+ EXPECT_EQ(New.LastIdle == New.Enqueued,
+ New.Completed == New.Enqueued && New.Active == 0u);
+ S = New;
+ });
+
+ // Two types of tasks: a ping task enqueues a pong task.
+ // This avoids all enqueues followed by all completions (boring!)
+ std::atomic<int> PingCount(0), PongCount(0);
+ BackgroundQueue::Task Pong([&] { ++PongCount; });
+ BackgroundQueue::Task Ping([&] {
+ ++PingCount;
+ Q.push(Pong);
+ });
+
+ for (int I = 0; I < 1000; ++I)
+ Q.push(Ping);
+ // Spin up some workers and stop while idle.
+ AsyncTaskRunner ThreadPool;
+ for (unsigned I = 0; I < 5; ++I)
+ ThreadPool.runAsync("worker", [&] { Q.work([&] { Q.stop(); }); });
+ ThreadPool.wait();
+
+ // Everything's done, check final stats.
+ // Assertions above ensure we got from 0 to 2000 in a reasonable way.
+ EXPECT_EQ(PingCount.load(), 1000);
+ EXPECT_EQ(PongCount.load(), 1000);
+ EXPECT_EQ(S.Active, 0u);
+ EXPECT_EQ(S.Enqueued, 2000u);
+ EXPECT_EQ(S.Completed, 2000u);
+ EXPECT_EQ(S.LastIdle, 2000u);
+}
+
} // namespace clangd
} // namespace clang
diff --git a/llvm/include/llvm/Support/JSON.h b/llvm/include/llvm/Support/JSON.h
index 2c63468c401a..56d26d2c5a24 100644
--- a/llvm/include/llvm/Support/JSON.h
+++ b/llvm/include/llvm/Support/JSON.h
@@ -598,6 +598,13 @@ inline bool fromJSON(const Value &E, bool &Out) {
}
return false;
}
+inline bool fromJSON(const Value &E, std::nullptr_t &Out) {
+ if (auto S = E.getAsNull()) {
+ Out = *S;
+ return true;
+ }
+ return false;
+}
template <typename T> bool fromJSON(const Value &E, llvm::Optional<T> &Out) {
if (E.getAsNull()) {
Out = llvm::None;
More information about the cfe-commits
mailing list