[Lldb-commits] [lldb] [lldb] address memory leakage in lldb-server (PR #177572)

via lldb-commits lldb-commits at lists.llvm.org
Sun Feb 22 23:46:40 PST 2026


https://github.com/daniilavdeev updated https://github.com/llvm/llvm-project/pull/177572

>From fdda56b709874dda0cb7ccd4035e700f36329301 Mon Sep 17 00:00:00 2001
From: Daniil Avdeev <daniilavdeev237 at gmail.com>
Date: Mon, 26 Jan 2026 16:10:51 +0000
Subject: [PATCH 1/3] [lldb] address memory leakage in lldb-server

lldb-server has exhibited fairly unexpected behaviour. The time each
iteration of the main loop takes (attach + spawn a child process) has
been progressively increasing over the course of the lldb-server
execution. For instance, at the beginning of the remote tests run (when
a single instance of lldb-server on the remote side processes all the
incoming connections), each iteration took approximately 0.1 seconds,
increasing to 1.5 seconds by the end.

The analysis of the lldb-server application indicates that the
__libc_fork function takes more and more time on each iteration. The
most plausible interpretation of this fact would appear to be that the
application accumulates a certain resource that the fork function
subsequently had to process.

The following investigation has shown that the memory leakage did seem
to take place during the lldb-server execution. After the spawn of a
child process lldb-server additionally creates a monitoring thread, the
only purpose of which is to call a waitpid on the corresponding child
process. However, it appears to me that lldb-server application tracks
neither its children nor these internal threads, which ultimately
results in the threads not being joined or detached and the resources
associated with these threads won't be released.

Meanwhile, the creation of the thread is followed by the allocation of
the stack memory for this thread (usually 8MB), which is never released.
Given this behaviour, the amount of unfreed memory accumulated by the
lldb-server reaches approximately 30-35GB by the end of the test run. At
the same time, the fork function maps the parent's memory to the child
process, therefore the uncontrolled growth of unnecessary memory slows
the execution of the fork function significantly.

To summarise, the memory leakage that was caused by the inappropriate
managing of the internal threads appears to be the underlying cause of
the progressively increasing duration of the main loop, leading to
performance degradation of lldb-server application, which is especially
noticeable during long tests runs.

The proposed solution at this stage is to simply detach internal
threads, so that the OS can release memory associated with these
threads. That is to say, it seems to me that lldb-server doesn't have
appropriate mechanisms to track spawned threads, therefore a more
thoughtful approach would likely require changes to a substantial part
of the existing logic.
---
 lldb/source/Host/posix/HostProcessPosix.cpp     | 13 ++++++++++---
 lldb/source/Host/windows/HostProcessWindows.cpp |  8 +++++++-
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/lldb/source/Host/posix/HostProcessPosix.cpp b/lldb/source/Host/posix/HostProcessPosix.cpp
index 2a118f0de9511..977ff70991bb6 100644
--- a/lldb/source/Host/posix/HostProcessPosix.cpp
+++ b/lldb/source/Host/posix/HostProcessPosix.cpp
@@ -6,9 +6,10 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "lldb/Host/Host.h"
-#include "lldb/Host/FileSystem.h"
 #include "lldb/Host/posix/HostProcessPosix.h"
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Host/Host.h"
+#include "lldb/Host/posix/HostThreadPosix.h"
 
 #include "llvm/ADT/STLExtras.h"
 
@@ -61,5 +62,11 @@ bool HostProcessPosix::IsRunning() const {
 
 llvm::Expected<HostThread> HostProcessPosix::StartMonitoring(
     const Host::MonitorChildProcessCallback &callback) {
-  return Host::StartMonitoringChildProcess(callback, m_process);
+  auto host_thread = Host::StartMonitoringChildProcess(callback, m_process);
+  if (!host_thread)
+    return host_thread;
+
+  auto &native_thread = host_thread->GetNativeThread();
+  native_thread.Detach();
+  return host_thread;
 }
diff --git a/lldb/source/Host/windows/HostProcessWindows.cpp b/lldb/source/Host/windows/HostProcessWindows.cpp
index 44cb333808773..8c7c57dd88462 100644
--- a/lldb/source/Host/windows/HostProcessWindows.cpp
+++ b/lldb/source/Host/windows/HostProcessWindows.cpp
@@ -84,10 +84,16 @@ llvm::Expected<HostThread> HostProcessWindows::StartMonitoring(
   // can have ownership over its own copy of the handle.
   if (::DuplicateHandle(GetCurrentProcess(), m_process, GetCurrentProcess(),
                         &process_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
-    return ThreadLauncher::LaunchThread(
+    auto host_thread = ThreadLauncher::LaunchThread(
         "ChildProcessMonitor", [callback, process_handle] {
           return MonitorThread(callback, process_handle);
         });
+    if (!host_thread)
+      return host_thread;
+
+    auto &native_thread = host_thread->GetNativeThread();
+    native_thread.Reset();
+    return host_thread;
   } else {
     return llvm::errorCodeToError(llvm::mapWindowsError(GetLastError()));
   }

>From a03d104d3729c97bde82cff7a200cde09c3beb6d Mon Sep 17 00:00:00 2001
From: Daniil Avdeev <daniilavdeev237 at gmail.com>
Date: Wed, 18 Feb 2026 18:09:29 +0000
Subject: [PATCH 2/3] fix

---
 .../Host/common/MonitoringProcessLauncher.cpp       |  9 ++++++---
 lldb/source/Host/posix/HostProcessPosix.cpp         | 13 +++----------
 lldb/source/Host/windows/HostProcessWindows.cpp     |  8 +-------
 3 files changed, 10 insertions(+), 20 deletions(-)

diff --git a/lldb/source/Host/common/MonitoringProcessLauncher.cpp b/lldb/source/Host/common/MonitoringProcessLauncher.cpp
index cf1d6b900cdfd..1bbd4f28b3c5b 100644
--- a/lldb/source/Host/common/MonitoringProcessLauncher.cpp
+++ b/lldb/source/Host/common/MonitoringProcessLauncher.cpp
@@ -56,12 +56,15 @@ MonitoringProcessLauncher::LaunchProcess(const ProcessLaunchInfo &launch_info,
     assert(launch_info.GetMonitorProcessCallback());
     llvm::Expected<HostThread> maybe_thread =
         process.StartMonitoring(launch_info.GetMonitorProcessCallback());
-    if (!maybe_thread)
+    if (!maybe_thread) {
       error = Status::FromErrorStringWithFormatv(
           "failed to launch host thread: {}",
           llvm::toString(maybe_thread.takeError()));
-    if (log)
-      log->PutCString("started monitoring child process.");
+    } else {
+      if (log)
+        log->PutCString("started monitoring child process.");
+      maybe_thread->Reset();
+    }
   } else {
     // Invalid process ID, something didn't go well
     if (error.Success())
diff --git a/lldb/source/Host/posix/HostProcessPosix.cpp b/lldb/source/Host/posix/HostProcessPosix.cpp
index 977ff70991bb6..2a118f0de9511 100644
--- a/lldb/source/Host/posix/HostProcessPosix.cpp
+++ b/lldb/source/Host/posix/HostProcessPosix.cpp
@@ -6,10 +6,9 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "lldb/Host/posix/HostProcessPosix.h"
-#include "lldb/Host/FileSystem.h"
 #include "lldb/Host/Host.h"
-#include "lldb/Host/posix/HostThreadPosix.h"
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Host/posix/HostProcessPosix.h"
 
 #include "llvm/ADT/STLExtras.h"
 
@@ -62,11 +61,5 @@ bool HostProcessPosix::IsRunning() const {
 
 llvm::Expected<HostThread> HostProcessPosix::StartMonitoring(
     const Host::MonitorChildProcessCallback &callback) {
-  auto host_thread = Host::StartMonitoringChildProcess(callback, m_process);
-  if (!host_thread)
-    return host_thread;
-
-  auto &native_thread = host_thread->GetNativeThread();
-  native_thread.Detach();
-  return host_thread;
+  return Host::StartMonitoringChildProcess(callback, m_process);
 }
diff --git a/lldb/source/Host/windows/HostProcessWindows.cpp b/lldb/source/Host/windows/HostProcessWindows.cpp
index 8c7c57dd88462..44cb333808773 100644
--- a/lldb/source/Host/windows/HostProcessWindows.cpp
+++ b/lldb/source/Host/windows/HostProcessWindows.cpp
@@ -84,16 +84,10 @@ llvm::Expected<HostThread> HostProcessWindows::StartMonitoring(
   // can have ownership over its own copy of the handle.
   if (::DuplicateHandle(GetCurrentProcess(), m_process, GetCurrentProcess(),
                         &process_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
-    auto host_thread = ThreadLauncher::LaunchThread(
+    return ThreadLauncher::LaunchThread(
         "ChildProcessMonitor", [callback, process_handle] {
           return MonitorThread(callback, process_handle);
         });
-    if (!host_thread)
-      return host_thread;
-
-    auto &native_thread = host_thread->GetNativeThread();
-    native_thread.Reset();
-    return host_thread;
   } else {
     return llvm::errorCodeToError(llvm::mapWindowsError(GetLastError()));
   }

>From 6598622e4a073a3533f73e329befe09fa09836d9 Mon Sep 17 00:00:00 2001
From: Daniil Avdeev <daniilavdeev237 at gmail.com>
Date: Mon, 23 Feb 2026 07:46:07 +0000
Subject: [PATCH 3/3] add comment

---
 lldb/source/Host/common/MonitoringProcessLauncher.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lldb/source/Host/common/MonitoringProcessLauncher.cpp b/lldb/source/Host/common/MonitoringProcessLauncher.cpp
index 1bbd4f28b3c5b..2d9379f697742 100644
--- a/lldb/source/Host/common/MonitoringProcessLauncher.cpp
+++ b/lldb/source/Host/common/MonitoringProcessLauncher.cpp
@@ -63,6 +63,8 @@ MonitoringProcessLauncher::LaunchProcess(const ProcessLaunchInfo &launch_info,
     } else {
       if (log)
         log->PutCString("started monitoring child process.");
+
+      // Allow the thread to exit on its own once its work is done.
       maybe_thread->Reset();
     }
   } else {



More information about the lldb-commits mailing list