diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -44,6 +44,7 @@ llvm::Optional CompileCommandsDir, bool UseDirBasedCDB, llvm::Optional ForcedOffsetEncoding, const ClangdServer::Options &Opts); + /// The destructor blocks on any outstanding background tasks. ~ClangdLSPServer(); /// Run LSP server loop, communicating with the Transport provided in the @@ -211,11 +212,10 @@ std::unique_ptr BaseCDB; // CDB is BaseCDB plus any comands overridden via LSP extensions. llvm::Optional CDB; - // The ClangdServer is created by the "initialize" LSP method. - // It is destroyed before run() returns, to ensure worker threads exit. ClangdServer::Options ClangdServerOpts; - llvm::Optional Server; llvm::Optional NegotiatedOffsetEncoding; + // The ClangdServer is created by the "initialize" LSP method. + llvm::Optional Server; }; } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -1231,7 +1231,11 @@ // clang-format on } -ClangdLSPServer::~ClangdLSPServer() { IsBeingDestroyed = true; } +ClangdLSPServer::~ClangdLSPServer() { IsBeingDestroyed = true; + // Explicitly destroy ClangdServer first, blocking on threads it owns. + // This ensures they don't access any other members. + Server.reset(); +} bool ClangdLSPServer::run() { // Run the Language Server loop. @@ -1241,8 +1245,6 @@ CleanExit = false; } - // Destroy ClangdServer to ensure all worker threads finish. - Server.reset(); return CleanExit && ShutdownRequestReceived; } diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -27,6 +27,7 @@ #include "llvm/Support/Signals.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/raw_ostream.h" +#include #include #include #include @@ -670,6 +671,24 @@ /*UseDirBasedCDB=*/CompileArgsFrom == FilesystemCompileArgs, OffsetEncodingFromFlag, Opts); llvm::set_thread_name("clangd.main"); - return LSPServer.run() ? 0 - : static_cast(ErrorResultCode::NoShutdownRequest); + int ExitCode = LSPServer.run() + ? 0 + : static_cast(ErrorResultCode::NoShutdownRequest); + log("LSP finished, exiting with status {0}", ExitCode); + + // There may still be lingering background threads (e.g. slow requests + // whose results will be dropped, background index shutting down). + // + // These should terminate quickly, and ~ClangdLSPServer blocks on them. + // However if a bug causes them to run forever, we want to ensure the process + // eventually exits. As clangd isn't directly user-facing, an editor can + // "leak" clangd processes. Crashing in this case contains the damage. + // + // This is more portable than sys::WatchDog, and yields a stack trace. + std::thread([] { + std::this_thread::sleep_for(std::chrono::minutes(5)); + std::abort(); + }).detach(); + + return ExitCode; }