<div dir="ltr">The TUSchedulerTest::Debounce breaks on Windows buildbots. Probably because the timeouts of 50ms/10ms/40ms are too low to be scheduled properly.<div>Increased the timeouts in r326598 to 1s/200ms/2s, hopefully that would unbreak the buildbots.</div><div><br></div><div>We could tweak them back to lower values that work on Monday :-)<br></div></div><br><br><div class="gmail_quote"><div dir="ltr">On Fri, Mar 2, 2018 at 9:58 AM Sam McCall via cfe-commits <<a href="mailto:cfe-commits@lists.llvm.org">cfe-commits@lists.llvm.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Author: sammccall<br>
Date: Fri Mar  2 00:56:37 2018<br>
New Revision: 326546<br>
<br>
URL: <a href="http://llvm.org/viewvc/llvm-project?rev=326546&view=rev" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project?rev=326546&view=rev</a><br>
Log:<br>
[clangd] Debounce streams of updates.<br>
<br>
Summary:<br>
Don't actually start building ASTs for new revisions until either:<br>
- 500ms have passed since the last revision, or<br>
- we actually need the revision for something (or to unblock the queue)<br>
<br>
In practice, this avoids the "first keystroke results in diagnostics" problem.<br>
This is kind of awkward to test, and the test is pretty bad.<br>
It can be observed nicely by capturing a trace, though.<br>
<br>
Reviewers: hokein, ilya-biryukov<br>
<br>
Subscribers: klimek, jkorous-apple, ioeric, cfe-commits<br>
<br>
Differential Revision: <a href="https://reviews.llvm.org/D43648" rel="noreferrer" target="_blank">https://reviews.llvm.org/D43648</a><br>
<br>
Modified:<br>
    clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp<br>
    clang-tools-extra/trunk/clangd/ClangdServer.cpp<br>
    clang-tools-extra/trunk/clangd/ClangdServer.h<br>
    clang-tools-extra/trunk/clangd/TUScheduler.cpp<br>
    clang-tools-extra/trunk/clangd/TUScheduler.h<br>
    clang-tools-extra/trunk/clangd/Threading.cpp<br>
    clang-tools-extra/trunk/clangd/Threading.h<br>
    clang-tools-extra/trunk/unittests/clangd/TUSchedulerTests.cpp<br>
<br>
Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp?rev=326546&r1=326545&r2=326546&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp?rev=326546&r1=326545&r2=326546&view=diff</a><br>
==============================================================================<br>
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp (original)<br>
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp Fri Mar  2 00:56:37 2018<br>
@@ -405,7 +405,7 @@ ClangdLSPServer::ClangdLSPServer(JSONOut<br>
     : Out(Out), CDB(std::move(CompileCommandsDir)), CCOpts(CCOpts),<br>
       Server(CDB, /*DiagConsumer=*/*this, FSProvider, AsyncThreadsCount,<br>
              StorePreamblesInMemory, BuildDynamicSymbolIndex, StaticIdx,<br>
-             ResourceDir) {}<br>
+             ResourceDir, /*UpdateDebounce=*/std::chrono::milliseconds(500)) {}<br>
<br>
 bool ClangdLSPServer::run(std::istream &In, JSONStreamStyle InputStyle) {<br>
   assert(!IsDone && "Run was called before");<br>
<br>
Modified: clang-tools-extra/trunk/clangd/ClangdServer.cpp<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.cpp?rev=326546&r1=326545&r2=326546&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.cpp?rev=326546&r1=326545&r2=326546&view=diff</a><br>
==============================================================================<br>
--- clang-tools-extra/trunk/clangd/ClangdServer.cpp (original)<br>
+++ clang-tools-extra/trunk/clangd/ClangdServer.cpp Fri Mar  2 00:56:37 2018<br>
@@ -76,7 +76,8 @@ ClangdServer::ClangdServer(GlobalCompila<br>
                            unsigned AsyncThreadsCount,<br>
                            bool StorePreamblesInMemory,<br>
                            bool BuildDynamicSymbolIndex, SymbolIndex *StaticIdx,<br>
-                           llvm::Optional<StringRef> ResourceDir)<br>
+                           llvm::Optional<StringRef> ResourceDir,<br>
+                           std::chrono::steady_clock::duration UpdateDebounce)<br>
     : CompileArgs(CDB,<br>
                   ResourceDir ? ResourceDir->str() : getStandardResourceDir()),<br>
       DiagConsumer(DiagConsumer), FSProvider(FSProvider),<br>
@@ -91,7 +92,8 @@ ClangdServer::ClangdServer(GlobalCompila<br>
                     FileIdx<br>
                         ? [this](PathRef Path,<br>
                                  ParsedAST *AST) { FileIdx->update(Path, AST); }<br>
-                        : ASTParsedCallback()) {<br>
+                        : ASTParsedCallback(),<br>
+                    UpdateDebounce) {<br>
   if (FileIdx && StaticIdx) {<br>
     MergedIndex = mergeIndex(FileIdx.get(), StaticIdx);<br>
     Index = MergedIndex.get();<br>
<br>
Modified: clang-tools-extra/trunk/clangd/ClangdServer.h<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.h?rev=326546&r1=326545&r2=326546&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.h?rev=326546&r1=326545&r2=326546&view=diff</a><br>
==============================================================================<br>
--- clang-tools-extra/trunk/clangd/ClangdServer.h (original)<br>
+++ clang-tools-extra/trunk/clangd/ClangdServer.h Fri Mar  2 00:56:37 2018<br>
@@ -125,6 +125,8 @@ public:<br>
   /// \p DiagConsumer. Note that a callback to \p DiagConsumer happens on a<br>
   /// worker thread. Therefore, instances of \p DiagConsumer must properly<br>
   /// synchronize access to shared state.<br>
+  /// UpdateDebounce determines how long to wait after a new version of the file<br>
+  /// before starting to compute diagnostics.<br>
   ///<br>
   /// \p StorePreamblesInMemory defines whether the Preambles generated by<br>
   /// clangd are stored in-memory or on disk.<br>
@@ -135,13 +137,17 @@ public:<br>
   ///<br>
   /// If \p StaticIdx is set, ClangdServer uses the index for global code<br>
   /// completion.<br>
+  /// FIXME(sammccall): pull out an options struct.<br>
   ClangdServer(GlobalCompilationDatabase &CDB,<br>
                DiagnosticsConsumer &DiagConsumer,<br>
                FileSystemProvider &FSProvider, unsigned AsyncThreadsCount,<br>
                bool StorePreamblesInMemory,<br>
                bool BuildDynamicSymbolIndex = false,<br>
                SymbolIndex *StaticIdx = nullptr,<br>
-               llvm::Optional<StringRef> ResourceDir = llvm::None);<br>
+               llvm::Optional<StringRef> ResourceDir = llvm::None,<br>
+               /* Tiny default debounce, so tests hit the debounce logic */<br>
+               std::chrono::steady_clock::duration UpdateDebounce =<br>
+                   std::chrono::milliseconds(20));<br>
<br>
   /// Set the root path of the workspace.<br>
   void setRootPath(PathRef RootPath);<br>
<br>
Modified: clang-tools-extra/trunk/clangd/TUScheduler.cpp<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/TUScheduler.cpp?rev=326546&r1=326545&r2=326546&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/TUScheduler.cpp?rev=326546&r1=326545&r2=326546&view=diff</a><br>
==============================================================================<br>
--- clang-tools-extra/trunk/clangd/TUScheduler.cpp (original)<br>
+++ clang-tools-extra/trunk/clangd/TUScheduler.cpp Fri Mar  2 00:56:37 2018<br>
@@ -54,6 +54,7 @@<br>
<br>
 namespace clang {<br>
 namespace clangd {<br>
+using std::chrono::steady_clock;<br>
 namespace {<br>
 class ASTWorkerHandle;<br>
<br>
@@ -69,8 +70,8 @@ class ASTWorkerHandle;<br>
 /// worker.<br>
 class ASTWorker {<br>
   friend class ASTWorkerHandle;<br>
-  ASTWorker(llvm::StringRef File, Semaphore &Barrier, CppFile AST,<br>
-            bool RunSync);<br>
+  ASTWorker(llvm::StringRef File, Semaphore &Barrier, CppFile AST, bool RunSync,<br>
+            steady_clock::duration UpdateDebounce);<br>
<br>
 public:<br>
   /// Create a new ASTWorker and return a handle to it.<br>
@@ -79,7 +80,8 @@ public:<br>
   /// synchronously instead. \p Barrier is acquired when processing each<br>
   /// request, it is be used to limit the number of actively running threads.<br>
   static ASTWorkerHandle Create(llvm::StringRef File, AsyncTaskRunner *Tasks,<br>
-                                Semaphore &Barrier, CppFile AST);<br>
+                                Semaphore &Barrier, CppFile AST,<br>
+                                steady_clock::duration UpdateDebounce);<br>
   ~ASTWorker();<br>
<br>
   void update(ParseInputs Inputs, WantDiagnostics,<br>
@@ -101,18 +103,27 @@ private:<br>
   /// Adds a new task to the end of the request queue.<br>
   void startTask(llvm::StringRef Name, UniqueFunction<void()> Task,<br>
                  llvm::Optional<WantDiagnostics> UpdateType);<br>
+  /// Determines the next action to perform.<br>
+  /// All actions that should never run are disarded.<br>
+  /// Returns a deadline for the next action. If it's expired, run now.<br>
+  /// scheduleLocked() is called again at the deadline, or if requests arrive.<br>
+  Deadline scheduleLocked();<br>
   /// Should the first task in the queue be skipped instead of run?<br>
   bool shouldSkipHeadLocked() const;<br>
<br>
   struct Request {<br>
     UniqueFunction<void()> Action;<br>
     std::string Name;<br>
+    steady_clock::time_point AddTime;<br>
     Context Ctx;<br>
     llvm::Optional<WantDiagnostics> UpdateType;<br>
   };<br>
<br>
-  std::string File;<br>
+  const std::string File;<br>
   const bool RunSync;<br>
+  // Time to wait after an update to see whether another update obsoletes it.<br>
+  const steady_clock::duration UpdateDebounce;<br>
+<br>
   Semaphore &Barrier;<br>
   // AST and FileInputs are only accessed on the processing thread from run().<br>
   CppFile AST;<br>
@@ -172,9 +183,10 @@ private:<br>
 };<br>
<br>
 ASTWorkerHandle ASTWorker::Create(llvm::StringRef File, AsyncTaskRunner *Tasks,<br>
-                                  Semaphore &Barrier, CppFile AST) {<br>
-  std::shared_ptr<ASTWorker> Worker(<br>
-      new ASTWorker(File, Barrier, std::move(AST), /*RunSync=*/!Tasks));<br>
+                                  Semaphore &Barrier, CppFile AST,<br>
+                                  steady_clock::duration UpdateDebounce) {<br>
+  std::shared_ptr<ASTWorker> Worker(new ASTWorker(<br>
+      File, Barrier, std::move(AST), /*RunSync=*/!Tasks, UpdateDebounce));<br>
   if (Tasks)<br>
     Tasks->runAsync("worker:" + llvm::sys::path::filename(File),<br>
                     [Worker]() { Worker->run(); });<br>
@@ -183,9 +195,9 @@ ASTWorkerHandle ASTWorker::Create(llvm::<br>
 }<br>
<br>
 ASTWorker::ASTWorker(llvm::StringRef File, Semaphore &Barrier, CppFile AST,<br>
-                     bool RunSync)<br>
-    : File(File), RunSync(RunSync), Barrier(Barrier), AST(std::move(AST)),<br>
-      Done(false) {<br>
+                     bool RunSync, steady_clock::duration UpdateDebounce)<br>
+    : File(File), RunSync(RunSync), UpdateDebounce(UpdateDebounce),<br>
+      Barrier(Barrier), AST(std::move(AST)), Done(false) {<br>
   if (RunSync)<br>
     return;<br>
 }<br>
@@ -275,8 +287,8 @@ void ASTWorker::startTask(llvm::StringRe<br>
   {<br>
     std::lock_guard<std::mutex> Lock(Mutex);<br>
     assert(!Done && "running a task after stop()");<br>
-    Requests.push_back(<br>
-        {std::move(Task), Name, Context::current().clone(), UpdateType});<br>
+    Requests.push_back({std::move(Task), Name, steady_clock::now(),<br>
+                        Context::current().clone(), UpdateType});<br>
   }<br>
   RequestsCV.notify_all();<br>
 }<br>
@@ -286,17 +298,31 @@ void ASTWorker::run() {<br>
     Request Req;<br>
     {<br>
       std::unique_lock<std::mutex> Lock(Mutex);<br>
-      RequestsCV.wait(Lock, [&]() { return Done || !Requests.empty(); });<br>
-      if (Requests.empty()) {<br>
-        assert(Done);<br>
-        return;<br>
-      }<br>
-      // Even when Done is true, we finish processing all pending requests<br>
-      // before exiting the processing loop.<br>
+      for (auto Wait = scheduleLocked(); !Wait.expired();<br>
+           Wait = scheduleLocked()) {<br>
+        if (Done) {<br>
+          if (Requests.empty())<br>
+            return;<br>
+          else     // Even though Done is set, finish pending requests.<br>
+            break; // However, skip delays to shutdown fast.<br>
+        }<br>
+<br>
+        // Tracing: we have a next request, attribute this sleep to it.<br>
+        Optional<WithContext> Ctx;<br>
+        Optional<trace::Span> Tracer;<br>
+        if (!Requests.empty()) {<br>
+          Ctx.emplace(Requests.front().Ctx.clone());<br>
+          Tracer.emplace("Debounce");<br>
+          SPAN_ATTACH(*Tracer, "next_request", Requests.front().Name);<br>
+          if (!(Wait == Deadline::infinity()))<br>
+            SPAN_ATTACH(*Tracer, "sleep_ms",<br>
+                        std::chrono::duration_cast<std::chrono::milliseconds>(<br>
+                            Wait.time() - steady_clock::now())<br>
+                            .count());<br>
+        }<br>
<br>
-      while (shouldSkipHeadLocked())<br>
-        Requests.pop_front();<br>
-      assert(!Requests.empty() && "skipped the whole queue");<br>
+        wait(Lock, RequestsCV, Wait);<br>
+      }<br>
       Req = std::move(Requests.front());<br>
       // Leave it on the queue for now, so waiters don't see an empty queue.<br>
     } // unlock Mutex<br>
@@ -316,6 +342,24 @@ void ASTWorker::run() {<br>
   }<br>
 }<br>
<br>
+Deadline ASTWorker::scheduleLocked() {<br>
+  if (Requests.empty())<br>
+    return Deadline::infinity(); // Wait for new requests.<br>
+  while (shouldSkipHeadLocked())<br>
+    Requests.pop_front();<br>
+  assert(!Requests.empty() && "skipped the whole queue");<br>
+  // Some updates aren't dead yet, but never end up being used.<br>
+  // e.g. the first keystroke is live until obsoleted by the second.<br>
+  // We debounce "maybe-unused" writes, sleeping 500ms in case they become dead.<br>
+  // But don't delay reads (including updates where diagnostics are needed).<br>
+  for (const auto &R : Requests)<br>
+    if (R.UpdateType == None || R.UpdateType == WantDiagnostics::Yes)<br>
+      return Deadline::zero();<br>
+  // Front request needs to be debounced, so determine when we're ready.<br>
+  Deadline D(Requests.front().AddTime + UpdateDebounce);<br>
+  return D;<br>
+}<br>
+<br>
 // Returns true if Requests.front() is a dead update that can be skipped.<br>
 bool ASTWorker::shouldSkipHeadLocked() const {<br>
   assert(!Requests.empty());<br>
@@ -370,10 +414,12 @@ struct TUScheduler::FileData {<br>
<br>
 TUScheduler::TUScheduler(unsigned AsyncThreadsCount,<br>
                          bool StorePreamblesInMemory,<br>
-                         ASTParsedCallback ASTCallback)<br>
+                         ASTParsedCallback ASTCallback,<br>
+                         steady_clock::duration UpdateDebounce)<br>
     : StorePreamblesInMemory(StorePreamblesInMemory),<br>
       PCHOps(std::make_shared<PCHContainerOperations>()),<br>
-      ASTCallback(std::move(ASTCallback)), Barrier(AsyncThreadsCount) {<br>
+      ASTCallback(std::move(ASTCallback)), Barrier(AsyncThreadsCount),<br>
+      UpdateDebounce(UpdateDebounce) {<br>
   if (0 < AsyncThreadsCount) {<br>
     PreambleTasks.emplace();<br>
     WorkerThreads.emplace();<br>
@@ -409,7 +455,8 @@ void TUScheduler::update(<br>
     // Create a new worker to process the AST-related tasks.<br>
     ASTWorkerHandle Worker = ASTWorker::Create(<br>
         File, WorkerThreads ? WorkerThreads.getPointer() : nullptr, Barrier,<br>
-        CppFile(File, StorePreamblesInMemory, PCHOps, ASTCallback));<br>
+        CppFile(File, StorePreamblesInMemory, PCHOps, ASTCallback),<br>
+        UpdateDebounce);<br>
     FD = std::unique_ptr<FileData>(new FileData{Inputs, std::move(Worker)});<br>
   } else {<br>
     FD->Inputs = Inputs;<br>
<br>
Modified: clang-tools-extra/trunk/clangd/TUScheduler.h<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/TUScheduler.h?rev=326546&r1=326545&r2=326546&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/TUScheduler.h?rev=326546&r1=326545&r2=326546&view=diff</a><br>
==============================================================================<br>
--- clang-tools-extra/trunk/clangd/TUScheduler.h (original)<br>
+++ clang-tools-extra/trunk/clangd/TUScheduler.h Fri Mar  2 00:56:37 2018<br>
@@ -17,6 +17,7 @@<br>
<br>
 namespace clang {<br>
 namespace clangd {<br>
+<br>
 /// Returns a number of a default async threads to use for TUScheduler.<br>
 /// Returned value is always >= 1 (i.e. will not cause requests to be processed<br>
 /// synchronously).<br>
@@ -46,10 +47,12 @@ enum class WantDiagnostics {<br>
 /// and scheduling tasks.<br>
 /// Callbacks are run on a threadpool and it's appropriate to do slow work in<br>
 /// them. Each task has a name, used for tracing (should be UpperCamelCase).<br>
+/// FIXME(sammccall): pull out a scheduler options struct.<br>
 class TUScheduler {<br>
 public:<br>
   TUScheduler(unsigned AsyncThreadsCount, bool StorePreamblesInMemory,<br>
-              ASTParsedCallback ASTCallback);<br>
+              ASTParsedCallback ASTCallback,<br>
+              std::chrono::steady_clock::duration UpdateDebounce);<br>
   ~TUScheduler();<br>
<br>
   /// Returns estimated memory usage for each of the currently open files.<br>
@@ -101,6 +104,7 @@ private:<br>
   // asynchronously.<br>
   llvm::Optional<AsyncTaskRunner> PreambleTasks;<br>
   llvm::Optional<AsyncTaskRunner> WorkerThreads;<br>
+  std::chrono::steady_clock::duration UpdateDebounce;<br>
 };<br>
 } // namespace clangd<br>
 } // namespace clang<br>
<br>
Modified: clang-tools-extra/trunk/clangd/Threading.cpp<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Threading.cpp?rev=326546&r1=326545&r2=326546&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Threading.cpp?rev=326546&r1=326545&r2=326546&view=diff</a><br>
==============================================================================<br>
--- clang-tools-extra/trunk/clangd/Threading.cpp (original)<br>
+++ clang-tools-extra/trunk/clangd/Threading.cpp Fri Mar  2 00:56:37 2018<br>
@@ -76,10 +76,19 @@ void AsyncTaskRunner::runAsync(llvm::Twi<br>
 Deadline timeoutSeconds(llvm::Optional<double> Seconds) {<br>
   using namespace std::chrono;<br>
   if (!Seconds)<br>
-    return llvm::None;<br>
+    return Deadline::infinity();<br>
   return steady_clock::now() +<br>
          duration_cast<steady_clock::duration>(duration<double>(*Seconds));<br>
 }<br>
<br>
+void wait(std::unique_lock<std::mutex> &Lock, std::condition_variable &CV,<br>
+          Deadline D) {<br>
+  if (D == Deadline::zero())<br>
+    return;<br>
+  if (D == Deadline::infinity())<br>
+    return CV.wait(Lock);<br>
+  CV.wait_until(Lock, D.time());<br>
+}<br>
+<br>
 } // namespace clangd<br>
 } // namespace clang<br>
<br>
Modified: clang-tools-extra/trunk/clangd/Threading.h<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Threading.h?rev=326546&r1=326545&r2=326546&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Threading.h?rev=326546&r1=326545&r2=326546&view=diff</a><br>
==============================================================================<br>
--- clang-tools-extra/trunk/clangd/Threading.h (original)<br>
+++ clang-tools-extra/trunk/clangd/Threading.h Fri Mar  2 00:56:37 2018<br>
@@ -50,18 +50,50 @@ private:<br>
   std::size_t FreeSlots;<br>
 };<br>
<br>
-/// A point in time we may wait for, or None to wait forever.<br>
+/// A point in time we can wait for.<br>
+/// Can be zero (don't wait) or infinity (wait forever).<br>
 /// (Not time_point::max(), because many std::chrono implementations overflow).<br>
-using Deadline = llvm::Optional<std::chrono::steady_clock::time_point>;<br>
-/// Makes a deadline from a timeout in seconds.<br>
+class Deadline {<br>
+public:<br>
+  Deadline(std::chrono::steady_clock::time_point Time)<br>
+      : Type(Finite), Time(Time) {}<br>
+  static Deadline zero() { return Deadline(Zero); }<br>
+  static Deadline infinity() { return Deadline(Infinite); }<br>
+<br>
+  std::chrono::steady_clock::time_point time() const {<br>
+    assert(Type == Finite);<br>
+    return Time;<br>
+  }<br>
+  bool expired() const {<br>
+    return (Type == Zero) ||<br>
+           (Type == Finite && Time < std::chrono::steady_clock::now());<br>
+  }<br>
+  bool operator==(const Deadline &Other) const {<br>
+    return (Type == Other.Type) && (Type != Finite || Time == Other.Time);<br>
+  }<br>
+<br>
+private:<br>
+  enum Type { Zero, Infinite, Finite };<br>
+<br>
+  Deadline(enum Type Type) : Type(Type) {}<br>
+  enum Type Type;<br>
+  std::chrono::steady_clock::time_point Time;<br>
+};<br>
+<br>
+/// Makes a deadline from a timeout in seconds. None means wait forever.<br>
 Deadline timeoutSeconds(llvm::Optional<double> Seconds);<br>
+/// Wait once on CV for the specified duration.<br>
+void wait(std::unique_lock<std::mutex> &Lock, std::condition_variable &CV,<br>
+          Deadline D);<br>
 /// Waits on a condition variable until F() is true or D expires.<br>
 template <typename Func><br>
 LLVM_NODISCARD bool wait(std::unique_lock<std::mutex> &Lock,<br>
                          std::condition_variable &CV, Deadline D, Func F) {<br>
-  if (D)<br>
-    return CV.wait_until(Lock, *D, F);<br>
-  CV.wait(Lock, F);<br>
+  while (!F()) {<br>
+    if (D.expired())<br>
+      return false;<br>
+    wait(Lock, CV, D);<br>
+  }<br>
   return true;<br>
 }<br>
<br>
@@ -73,7 +105,7 @@ public:<br>
   /// Destructor waits for all pending tasks to finish.<br>
   ~AsyncTaskRunner();<br>
<br>
-  void wait() const { (void) wait(llvm::None); }<br>
+  void wait() const { (void)wait(Deadline::infinity()); }<br>
   LLVM_NODISCARD bool wait(Deadline D) const;<br>
   // The name is used for tracing and debugging (e.g. to name a spawned thread).<br>
   void runAsync(llvm::Twine Name, UniqueFunction<void()> Action);<br>
<br>
Modified: clang-tools-extra/trunk/unittests/clangd/TUSchedulerTests.cpp<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/TUSchedulerTests.cpp?rev=326546&r1=326545&r2=326546&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/TUSchedulerTests.cpp?rev=326546&r1=326545&r2=326546&view=diff</a><br>
==============================================================================<br>
--- clang-tools-extra/trunk/unittests/clangd/TUSchedulerTests.cpp (original)<br>
+++ clang-tools-extra/trunk/unittests/clangd/TUSchedulerTests.cpp Fri Mar  2 00:56:37 2018<br>
@@ -42,7 +42,8 @@ private:<br>
 TEST_F(TUSchedulerTests, MissingFiles) {<br>
   TUScheduler S(getDefaultAsyncThreadsCount(),<br>
                 /*StorePreamblesInMemory=*/true,<br>
-                /*ASTParsedCallback=*/nullptr);<br>
+                /*ASTParsedCallback=*/nullptr,<br>
+                /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero());<br>
<br>
   auto Added = testPath("added.cpp");<br>
   Files[Added] = "";<br>
@@ -94,9 +95,11 @@ TEST_F(TUSchedulerTests, WantDiagnostics<br>
     // To avoid a racy test, don't allow tasks to actualy run on the worker<br>
     // thread until we've scheduled them all.<br>
     Notification Ready;<br>
-    TUScheduler S(getDefaultAsyncThreadsCount(),<br>
-                  /*StorePreamblesInMemory=*/true,<br>
-                  /*ASTParsedCallback=*/nullptr);<br>
+    TUScheduler S(<br>
+        getDefaultAsyncThreadsCount(),<br>
+        /*StorePreamblesInMemory=*/true,<br>
+        /*ASTParsedCallback=*/nullptr,<br>
+        /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero());<br>
     auto Path = testPath("foo.cpp");<br>
     S.update(Path, getInputs(Path, ""), WantDiagnostics::Yes,<br>
              [&](std::vector<DiagWithFixIts>) { Ready.wait(); });<br>
@@ -118,6 +121,28 @@ TEST_F(TUSchedulerTests, WantDiagnostics<br>
   EXPECT_EQ(2, CallbackCount);<br>
 }<br>
<br>
+TEST_F(TUSchedulerTests, Debounce) {<br>
+  std::atomic<int> CallbackCount(0);<br>
+  {<br>
+    TUScheduler S(getDefaultAsyncThreadsCount(),<br>
+                  /*StorePreamblesInMemory=*/true,<br>
+                  /*ASTParsedCallback=*/nullptr,<br>
+                  /*UpdateDebounce=*/std::chrono::milliseconds(50));<br>
+    auto Path = testPath("foo.cpp");<br>
+    S.update(Path, getInputs(Path, "auto (debounced)"), WantDiagnostics::Auto,<br>
+             [&](std::vector<DiagWithFixIts> Diags) {<br>
+               ADD_FAILURE() << "auto should have been debounced and canceled";<br>
+             });<br>
+    std::this_thread::sleep_for(std::chrono::milliseconds(10));<br>
+    S.update(Path, getInputs(Path, "auto (timed out)"), WantDiagnostics::Auto,<br>
+             [&](std::vector<DiagWithFixIts> Diags) { ++CallbackCount; });<br>
+    std::this_thread::sleep_for(std::chrono::milliseconds(60));<br>
+    S.update(Path, getInputs(Path, "auto (shut down)"), WantDiagnostics::Auto,<br>
+             [&](std::vector<DiagWithFixIts> Diags) { ++CallbackCount; });<br>
+  }<br>
+  EXPECT_EQ(2, CallbackCount);<br>
+}<br>
+<br>
 TEST_F(TUSchedulerTests, ManyUpdates) {<br>
   const int FilesCount = 3;<br>
   const int UpdatesPerFile = 10;<br>
@@ -131,7 +156,8 @@ TEST_F(TUSchedulerTests, ManyUpdates) {<br>
   {<br>
     TUScheduler S(getDefaultAsyncThreadsCount(),<br>
                   /*StorePreamblesInMemory=*/true,<br>
-                  /*ASTParsedCallback=*/nullptr);<br>
+                  /*ASTParsedCallback=*/nullptr,<br>
+                  /*UpdateDebounce=*/std::chrono::milliseconds(50));<br>
<br>
     std::vector<std::string> Files;<br>
     for (int I = 0; I < FilesCount; ++I) {<br>
<br>
<br>
_______________________________________________<br>
cfe-commits mailing list<br>
<a href="mailto:cfe-commits@lists.llvm.org" target="_blank">cfe-commits@lists.llvm.org</a><br>
<a href="http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits" rel="noreferrer" target="_blank">http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits</a><br>
</blockquote></div><br clear="all"><div><br></div>-- <br><div dir="ltr" class="gmail_signature" data-smartmail="gmail_signature"><div dir="ltr"><div><div dir="ltr"><div>Regards,</div><div>Ilya Biryukov</div></div></div></div></div>