diff options
| -rw-r--r-- | clang-tools-extra/clangd/ClangdServer.cpp | 13 | ||||
| -rw-r--r-- | clang-tools-extra/clangd/ClangdServer.h | 7 | ||||
| -rw-r--r-- | clang-tools-extra/clangd/DraftStore.h | 5 | ||||
| -rw-r--r-- | clang-tools-extra/unittests/clangd/ClangdTests.cpp | 63 | 
4 files changed, 86 insertions, 2 deletions
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index 2ddcd01e6a0..b1d9914ade9 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -315,6 +315,19 @@ std::future<void> ClangdServer::scheduleReparseAndDiags(      auto Diags = DeferredRebuild.get();      if (!Diags)        return; // A new reparse was requested before this one completed. + +    // We need to serialize access to resulting diagnostics to avoid calling +    // `onDiagnosticsReady` in the wrong order. +    std::lock_guard<std::mutex> DiagsLock(DiagnosticsMutex); +    DocVersion &LastReportedDiagsVersion = ReportedDiagnosticVersions[FileStr]; +    // FIXME(ibiryukov): get rid of '<' comparison here. In the current +    // implementation diagnostics will not be reported after version counters' +    // overflow. This should not happen in practice, since DocVersion is a +    // 64-bit unsigned integer. +    if (Version < LastReportedDiagsVersion) +      return; +    LastReportedDiagsVersion = Version; +      DiagConsumer.onDiagnosticsReady(FileStr,                                      make_tagged(std::move(*Diags), Tag));    }; diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index 529f783cb22..015e14616f4 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -284,6 +284,13 @@ private:    // ClangdServer    ClangdScheduler WorkScheduler;    bool SnippetCompletions; + +  /// Used to serialize diagnostic callbacks. +  /// FIXME(ibiryukov): get rid of an extra map and put all version counters +  /// into CppFile. +  std::mutex DiagnosticsMutex; +  /// Maps from a filename to the latest version of reported diagnostics. +  llvm::StringMap<DocVersion> ReportedDiagnosticVersions;  };  } // namespace clangd diff --git a/clang-tools-extra/clangd/DraftStore.h b/clang-tools-extra/clangd/DraftStore.h index c4e31e7c813..2dd7184437a 100644 --- a/clang-tools-extra/clangd/DraftStore.h +++ b/clang-tools-extra/clangd/DraftStore.h @@ -13,6 +13,7 @@  #include "Path.h"  #include "clang/Basic/LLVM.h"  #include "llvm/ADT/StringMap.h" +#include <cstdint>  #include <mutex>  #include <string>  #include <vector> @@ -20,8 +21,8 @@  namespace clang {  namespace clangd { -/// Using 'unsigned' here to avoid undefined behaviour on overflow. -typedef unsigned DocVersion; +/// Using unsigned int type here to avoid undefined behaviour on overflow. +typedef uint64_t DocVersion;  /// Document draft with a version of this draft.  struct VersionedDraft { diff --git a/clang-tools-extra/unittests/clangd/ClangdTests.cpp b/clang-tools-extra/unittests/clangd/ClangdTests.cpp index d286514e864..ed4ab84896c 100644 --- a/clang-tools-extra/unittests/clangd/ClangdTests.cpp +++ b/clang-tools-extra/unittests/clangd/ClangdTests.cpp @@ -22,6 +22,7 @@  #include <iostream>  #include <random>  #include <string> +#include <thread>  #include <vector>  namespace clang { @@ -898,5 +899,67 @@ int d;    }  } +TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) { +  class NoConcurrentAccessDiagConsumer : public DiagnosticsConsumer { +  public: +    NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse) +        : StartSecondReparse(std::move(StartSecondReparse)) {} + +    void onDiagnosticsReady( +        PathRef File, +        Tagged<std::vector<DiagWithFixIts>> Diagnostics) override { + +      std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t()); +      ASSERT_TRUE(Lock.owns_lock()) +          << "Detected concurrent onDiagnosticsReady calls for the same file."; +      if (FirstRequest) { +        FirstRequest = false; +        StartSecondReparse.set_value(); +        // Sleep long enough for the second request to be processed. +        std::this_thread::sleep_for(std::chrono::milliseconds(50)); +      } +    } + +  private: +    std::mutex Mutex; +    bool FirstRequest = true; +    std::promise<void> StartSecondReparse; +  }; + +  const auto SourceContentsWithoutErrors = R"cpp( +int a; +int b; +int c; +int d; +)cpp"; + +  const auto SourceContentsWithErrors = R"cpp( +int a = x; +int b; +int c; +int d; +)cpp"; + +  auto FooCpp = getVirtualTestFilePath("foo.cpp"); +  llvm::StringMap<std::string> FileContents; +  FileContents[FooCpp] = ""; +  ConstantFSProvider FS(buildTestFS(FileContents)); + +  std::promise<void> StartSecondReparsePromise; +  std::future<void> StartSecondReparse = StartSecondReparsePromise.get_future(); + +  NoConcurrentAccessDiagConsumer DiagConsumer( +      std::move(StartSecondReparsePromise)); + +  MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true); +  ClangdServer Server(CDB, DiagConsumer, FS, 4, /*SnippetCompletions=*/false, +                      EmptyLogger::getInstance()); +  Server.addDocument(FooCpp, SourceContentsWithErrors); +  StartSecondReparse.wait(); + +  auto Future = Server.addDocument(FooCpp, SourceContentsWithoutErrors); +  Future.wait(); +} +  } // namespace clangd  } // namespace clang  | 

