forked from OSchip/llvm-project
[clangd] Trim memory periodically when using glibc malloc
This diff addresses the issue of the ever increasing memory usage of clangd. The key to understand what happens is to use `malloc_stats()`: malloc arenas keep getting bigger, although the actual memory used does not. It seems some operations while bulding the indices (both dynamic and background) create this problem. Specifically, 'FileSymbols::update' and 'FileSymbols::buildIndex' seem especially affected. This diff adds a call to `malloc_trim()` periodically in ClangdLSPServer. Fixes: https://github.com/clangd/clangd/issues/251 Fixes: https://github.com/clangd/clangd/issues/115 Reviewed By: sammccall Differential Revision: https://reviews.llvm.org/D93452
This commit is contained in:
parent
f72c384b5b
commit
b8c37153d5
|
@ -14,9 +14,12 @@ if (NOT DEFINED CLANGD_BUILD_XPC)
|
||||||
unset(CLANGD_BUILD_XPC_DEFAULT)
|
unset(CLANGD_BUILD_XPC_DEFAULT)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
option(CLANGD_MALLOC_TRIM "Call malloc_trim(3) periodically in Clangd. (only takes effect when using glibc)" ON)
|
||||||
|
|
||||||
llvm_canonicalize_cmake_booleans(
|
llvm_canonicalize_cmake_booleans(
|
||||||
CLANGD_BUILD_XPC
|
CLANGD_BUILD_XPC
|
||||||
CLANGD_ENABLE_REMOTE
|
CLANGD_ENABLE_REMOTE
|
||||||
|
CLANGD_MALLOC_TRIM
|
||||||
LLVM_ENABLE_ZLIB
|
LLVM_ENABLE_ZLIB
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -178,6 +178,7 @@ public:
|
||||||
} else if (auto Handler = Notifications.lookup(Method)) {
|
} else if (auto Handler = Notifications.lookup(Method)) {
|
||||||
Handler(std::move(Params));
|
Handler(std::move(Params));
|
||||||
Server.maybeExportMemoryProfile();
|
Server.maybeExportMemoryProfile();
|
||||||
|
Server.maybeCleanupMemory();
|
||||||
} else {
|
} else {
|
||||||
log("unhandled notification {0}", Method);
|
log("unhandled notification {0}", Method);
|
||||||
}
|
}
|
||||||
|
@ -453,6 +454,7 @@ void ClangdLSPServer::callRaw(StringRef Method, llvm::json::Value Params,
|
||||||
|
|
||||||
void ClangdLSPServer::notify(llvm::StringRef Method, llvm::json::Value Params) {
|
void ClangdLSPServer::notify(llvm::StringRef Method, llvm::json::Value Params) {
|
||||||
log("--> {0}", Method);
|
log("--> {0}", Method);
|
||||||
|
maybeCleanupMemory();
|
||||||
std::lock_guard<std::mutex> Lock(TranspWriter);
|
std::lock_guard<std::mutex> Lock(TranspWriter);
|
||||||
Transp.notify(Method, std::move(Params));
|
Transp.notify(Method, std::move(Params));
|
||||||
}
|
}
|
||||||
|
@ -1301,6 +1303,27 @@ void ClangdLSPServer::maybeExportMemoryProfile() {
|
||||||
NextProfileTime = Now + ProfileInterval;
|
NextProfileTime = Now + ProfileInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClangdLSPServer::maybeCleanupMemory() {
|
||||||
|
// Memory cleanup is probably expensive, throttle it
|
||||||
|
static constexpr auto MemoryCleanupInterval = std::chrono::minutes(1);
|
||||||
|
|
||||||
|
if (!Opts.MemoryCleanup)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// FIXME: this can probably be done without a mutex
|
||||||
|
// and the logic could be shared with maybeExportMemoryProfile
|
||||||
|
{
|
||||||
|
auto Now = std::chrono::steady_clock::now();
|
||||||
|
std::lock_guard<std::mutex> Lock(NextMemoryCleanupTimeMutex);
|
||||||
|
if (Now < NextMemoryCleanupTime)
|
||||||
|
return;
|
||||||
|
NextMemoryCleanupTime = Now + MemoryCleanupInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
vlog("Calling memory cleanup callback");
|
||||||
|
Opts.MemoryCleanup();
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: This function needs to be properly tested.
|
// FIXME: This function needs to be properly tested.
|
||||||
void ClangdLSPServer::onChangeConfiguration(
|
void ClangdLSPServer::onChangeConfiguration(
|
||||||
const DidChangeConfigurationParams &Params) {
|
const DidChangeConfigurationParams &Params) {
|
||||||
|
@ -1507,8 +1530,9 @@ ClangdLSPServer::ClangdLSPServer(class Transport &Transp,
|
||||||
MsgHandler->bind("textDocument/foldingRange", &ClangdLSPServer::onFoldingRange);
|
MsgHandler->bind("textDocument/foldingRange", &ClangdLSPServer::onFoldingRange);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
// Delay first profile until we've finished warming up.
|
// Delay first profile and memory cleanup until we've finished warming up.
|
||||||
NextProfileTime = std::chrono::steady_clock::now() + std::chrono::minutes(1);
|
NextMemoryCleanupTime = NextProfileTime =
|
||||||
|
std::chrono::steady_clock::now() + std::chrono::minutes(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClangdLSPServer::~ClangdLSPServer() {
|
ClangdLSPServer::~ClangdLSPServer() {
|
||||||
|
@ -1621,6 +1645,10 @@ void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version,
|
||||||
void ClangdLSPServer::onBackgroundIndexProgress(
|
void ClangdLSPServer::onBackgroundIndexProgress(
|
||||||
const BackgroundQueue::Stats &Stats) {
|
const BackgroundQueue::Stats &Stats) {
|
||||||
static const char ProgressToken[] = "backgroundIndexProgress";
|
static const char ProgressToken[] = "backgroundIndexProgress";
|
||||||
|
|
||||||
|
// The background index did some work, maybe we need to cleanup
|
||||||
|
maybeCleanupMemory();
|
||||||
|
|
||||||
std::lock_guard<std::mutex> Lock(BackgroundIndexProgressMutex);
|
std::lock_guard<std::mutex> Lock(BackgroundIndexProgressMutex);
|
||||||
|
|
||||||
auto NotifyProgress = [this](const BackgroundQueue::Stats &Stats) {
|
auto NotifyProgress = [this](const BackgroundQueue::Stats &Stats) {
|
||||||
|
|
|
@ -48,6 +48,9 @@ public:
|
||||||
llvm::Optional<Path> CompileCommandsDir;
|
llvm::Optional<Path> CompileCommandsDir;
|
||||||
/// The offset-encoding to use, or None to negotiate it over LSP.
|
/// The offset-encoding to use, or None to negotiate it over LSP.
|
||||||
llvm::Optional<OffsetEncoding> Encoding;
|
llvm::Optional<OffsetEncoding> Encoding;
|
||||||
|
/// If set, periodically called to release memory.
|
||||||
|
/// Consider malloc_trim(3)
|
||||||
|
std::function<void()> MemoryCleanup = nullptr;
|
||||||
|
|
||||||
/// Per-feature options. Generally ClangdServer lets these vary
|
/// Per-feature options. Generally ClangdServer lets these vary
|
||||||
/// per-request, but LSP allows limited/no customizations.
|
/// per-request, but LSP allows limited/no customizations.
|
||||||
|
@ -184,10 +187,18 @@ private:
|
||||||
/// profiling hasn't happened recently.
|
/// profiling hasn't happened recently.
|
||||||
void maybeExportMemoryProfile();
|
void maybeExportMemoryProfile();
|
||||||
|
|
||||||
|
/// Run the MemoryCleanup callback if it's time.
|
||||||
|
/// This method is thread safe.
|
||||||
|
void maybeCleanupMemory();
|
||||||
|
|
||||||
/// Timepoint until which profiling is off. It is used to throttle profiling
|
/// Timepoint until which profiling is off. It is used to throttle profiling
|
||||||
/// requests.
|
/// requests.
|
||||||
std::chrono::steady_clock::time_point NextProfileTime;
|
std::chrono::steady_clock::time_point NextProfileTime;
|
||||||
|
|
||||||
|
/// Next time we want to call the MemoryCleanup callback.
|
||||||
|
std::mutex NextMemoryCleanupTimeMutex;
|
||||||
|
std::chrono::steady_clock::time_point NextMemoryCleanupTime;
|
||||||
|
|
||||||
/// Since initialization of CDBs and ClangdServer is done lazily, the
|
/// Since initialization of CDBs and ClangdServer is done lazily, the
|
||||||
/// following context captures the one used while creating ClangdLSPServer and
|
/// following context captures the one used while creating ClangdLSPServer and
|
||||||
/// passes it to above mentioned object instances to make sure they share the
|
/// passes it to above mentioned object instances to make sure they share the
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
#define CLANGD_BUILD_XPC @CLANGD_BUILD_XPC@
|
#define CLANGD_BUILD_XPC @CLANGD_BUILD_XPC@
|
||||||
#define CLANGD_ENABLE_REMOTE @CLANGD_ENABLE_REMOTE@
|
#define CLANGD_ENABLE_REMOTE @CLANGD_ENABLE_REMOTE@
|
||||||
|
#define CLANGD_MALLOC_TRIM @CLANGD_MALLOC_TRIM@
|
||||||
|
|
|
@ -50,6 +50,10 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __GLIBC__
|
||||||
|
#include <malloc.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace clang {
|
namespace clang {
|
||||||
namespace clangd {
|
namespace clangd {
|
||||||
|
|
||||||
|
@ -497,6 +501,29 @@ opt<bool> CollectMainFileRefs{
|
||||||
init(ClangdServer::Options().CollectMainFileRefs),
|
init(ClangdServer::Options().CollectMainFileRefs),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if defined(__GLIBC__) && CLANGD_MALLOC_TRIM
|
||||||
|
opt<bool> EnableMallocTrim{
|
||||||
|
"malloc-trim",
|
||||||
|
cat(Misc),
|
||||||
|
desc("Release memory periodically via malloc_trim(3)."),
|
||||||
|
init(true),
|
||||||
|
};
|
||||||
|
|
||||||
|
std::function<void()> getMemoryCleanupFunction() {
|
||||||
|
if (!EnableMallocTrim)
|
||||||
|
return nullptr;
|
||||||
|
// Leave a few MB at the top of the heap: it is insignificant
|
||||||
|
// and will most likely be needed by the main thread
|
||||||
|
constexpr size_t MallocTrimPad = 20'000'000;
|
||||||
|
return []() {
|
||||||
|
if (malloc_trim(MallocTrimPad))
|
||||||
|
vlog("Released memory via malloc_trim");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
std::function<void()> getMemoryCleanupFunction() { return nullptr; }
|
||||||
|
#endif
|
||||||
|
|
||||||
#if CLANGD_ENABLE_REMOTE
|
#if CLANGD_ENABLE_REMOTE
|
||||||
opt<std::string> RemoteIndexAddress{
|
opt<std::string> RemoteIndexAddress{
|
||||||
"remote-index-address",
|
"remote-index-address",
|
||||||
|
@ -797,6 +824,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
|
||||||
Opts.BuildRecoveryAST = RecoveryAST;
|
Opts.BuildRecoveryAST = RecoveryAST;
|
||||||
Opts.PreserveRecoveryASTType = RecoveryASTType;
|
Opts.PreserveRecoveryASTType = RecoveryASTType;
|
||||||
Opts.FoldingRanges = FoldingRanges;
|
Opts.FoldingRanges = FoldingRanges;
|
||||||
|
Opts.MemoryCleanup = getMemoryCleanupFunction();
|
||||||
|
|
||||||
Opts.CodeComplete.IncludeIneligibleResults = IncludeIneligibleResults;
|
Opts.CodeComplete.IncludeIneligibleResults = IncludeIneligibleResults;
|
||||||
Opts.CodeComplete.Limit = LimitResults;
|
Opts.CodeComplete.Limit = LimitResults;
|
||||||
|
|
Loading…
Reference in New Issue