Skip to content

[lldb-dap] Implement a MemoryMonitor #129332

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 5, 2025

Conversation

JDevlieghere
Copy link
Member

@JDevlieghere JDevlieghere commented Feb 28, 2025

This implements a memory monitor for macOS & Linux, and registers a callback that invokes SBDebugger::MemoryPressureDetected when a low memory event is detected. This is motivated by the new server mode, where the lldb-dap process will live across multiple debug sessions and will use more memory due to caching.

This implements a memory monitor for macOS & Linux, and registers a
callback that invokes SBDebugger::MemoryPressureDetected() when a low
memory event is detected.
@llvmbot
Copy link
Member

llvmbot commented Feb 28, 2025

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

Changes

This implements a memory monitor for macOS & Linux, and registers a callback that invokes SBDebugger::MemoryPressureDetected() when a low memory event is detected.


Full diff: https://github.com/llvm/llvm-project/pull/129332.diff

4 Files Affected:

  • (modified) lldb/tools/lldb-dap/CMakeLists.txt (+1)
  • (added) lldb/tools/lldb-dap/MemoryMonitor.cpp (+114)
  • (added) lldb/tools/lldb-dap/MemoryMonitor.h (+41)
  • (modified) lldb/tools/lldb-dap/lldb-dap.cpp (+15-2)
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index 8b3c520ec4360..8db377e31c3c6 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -36,6 +36,7 @@ add_lldb_tool(lldb-dap
   RunInTerminal.cpp
   SourceBreakpoint.cpp
   Watchpoint.cpp
+  MemoryMonitor.cpp
 
   Handler/ResponseHandler.cpp
   Handler/AttachRequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/MemoryMonitor.cpp b/lldb/tools/lldb-dap/MemoryMonitor.cpp
new file mode 100644
index 0000000000000..da3da42fe9b0f
--- /dev/null
+++ b/lldb/tools/lldb-dap/MemoryMonitor.cpp
@@ -0,0 +1,114 @@
+//===-- MemoryMonitor.cpp -------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "MemoryMonitor.h"
+#include "llvm/ADT/ScopeExit.h"
+#include <atomic>
+#include <cstdio>
+#include <cstring>
+#include <thread>
+
+#if defined(__APPLE__)
+#include <dispatch/dispatch.h>
+#endif
+
+#if defined(__linux__)
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+#endif
+
+using namespace lldb_dap;
+
+#if defined(__APPLE__)
+class MemoryMonitorDarwin : public MemoryMonitor {
+  using MemoryMonitor::MemoryMonitor;
+  void Start() override {
+    m_memory_pressure_source = dispatch_source_create(
+        DISPATCH_SOURCE_TYPE_MEMORYPRESSURE,
+        0, // system-wide monitoring
+        DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL,
+        dispatch_get_main_queue());
+
+    dispatch_source_set_event_handler(m_memory_pressure_source, ^{
+      dispatch_source_memorypressure_flags_t pressureLevel =
+          dispatch_source_get_data(m_memory_pressure_source);
+      if (pressureLevel &
+          (DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL)) {
+        m_callback();
+      }
+    });
+  }
+
+  void Stop() override { dispatch_source_cancel(m_memory_pressure_source); }
+
+private:
+  dispatch_source_t m_memory_pressure_source;
+};
+#endif
+
+#if defined(__linux__)
+static void MonitorThread(std::atomic<bool> &done,
+                          MemoryMonitor::Callback callback) {
+  struct pollfd fds;
+  fds.fd = open("/proc/pressure/memory", O_RDWR | O_NONBLOCK);
+  if (fds.fd < 0)
+    return;
+  fds.events = POLLPRI;
+
+  auto cleanup = llvm::make_scope_exit([&]() { close(fds.fd); });
+
+  // Detect a 50ms stall in a 2 second time window.
+  const char trig[] = "some 50000 2000000";
+  if (write(fds.fd, trig, strlen(trig) + 1) < 0)
+    return;
+
+  while (!done) {
+    int n = poll(&fds, 1, 1000);
+    if (n > 0) {
+      if (fds.revents & POLLERR)
+        return;
+      if (fds.revents & POLLPRI)
+        callback();
+    }
+  }
+}
+
+class MemoryMonitorLinux : public MemoryMonitor {
+public:
+  using MemoryMonitor::MemoryMonitor;
+
+  void Start() override {
+    m_memory_pressure_thread =
+        std::thread(MonitorThread, std::ref(m_done), m_callback);
+  }
+
+  void Stop() override {
+    if (m_memory_pressure_thread.joinable()) {
+      m_done = true;
+      m_memory_pressure_thread.join();
+    }
+  }
+
+private:
+  std::atomic<bool> m_done = false;
+  std::thread m_memory_pressure_thread;
+};
+#endif
+
+std::unique_ptr<MemoryMonitor> MemoryMonitor::Create(Callback callback) {
+#if defined(__APPLE__)
+  return std::make_unique<MemoryMonitorDarwin>(callback);
+#endif
+
+#if defined(__linux__)
+  return std::make_unique<MemoryMonitorLinux>(callback);
+#endif
+
+  return nullptr;
+}
diff --git a/lldb/tools/lldb-dap/MemoryMonitor.h b/lldb/tools/lldb-dap/MemoryMonitor.h
new file mode 100644
index 0000000000000..e07c3bde9e85c
--- /dev/null
+++ b/lldb/tools/lldb-dap/MemoryMonitor.h
@@ -0,0 +1,41 @@
+//===-- MemoryMonitor.h ---------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TOOLS_LLDB_DAP_WATCHPOINT_H
+#define LLDB_TOOLS_LLDB_DAP_WATCHPOINT_H
+
+#include <functional>
+#include <memory>
+
+namespace lldb_dap {
+
+class MemoryMonitor {
+public:
+  using Callback = std::function<void()>;
+
+  MemoryMonitor(Callback callback) : m_callback(callback) {}
+  virtual ~MemoryMonitor() = default;
+
+  /// MemoryMonitor is not copyable.
+  /// @{
+  MemoryMonitor(const MemoryMonitor &) = delete;
+  MemoryMonitor &operator=(const MemoryMonitor &) = delete;
+  /// @}
+
+  static std::unique_ptr<MemoryMonitor> Create(Callback callback);
+
+  virtual void Start() = 0;
+  virtual void Stop() = 0;
+
+protected:
+  Callback m_callback;
+};
+
+} // namespace lldb_dap
+
+#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index d005eccfae903..41405df548da2 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -9,7 +9,9 @@
 #include "DAP.h"
 #include "EventHelper.h"
 #include "Handler/RequestHandler.h"
+#include "MemoryMonitor.h"
 #include "RunInTerminal.h"
+#include "lldb/API/SBDebugger.h"
 #include "lldb/API/SBStream.h"
 #include "lldb/Host/Config.h"
 #include "lldb/Host/File.h"
@@ -504,9 +506,20 @@ int main(int argc, char *argv[]) {
     return EXIT_FAILURE;
   }
 
+  // Create a memory monitor. This can return nullptr if the host platform is
+  // not supported.
+  std::unique_ptr<MemoryMonitor> memory_monitor = MemoryMonitor::Create(
+      []() { lldb::SBDebugger::MemoryPressureDetected(); });
+
+  if (memory_monitor)
+    memory_monitor->Start();
+
   // Terminate the debugger before the C++ destructor chain kicks in.
-  auto terminate_debugger =
-      llvm::make_scope_exit([] { lldb::SBDebugger::Terminate(); });
+  auto terminate_debugger = llvm::make_scope_exit([&] {
+    if (memory_monitor)
+      memory_monitor->Stop();
+    lldb::SBDebugger::Terminate();
+  });
 
   std::vector<std::string> pre_init_commands;
   for (const std::string &arg :

Copy link
Contributor

@ashgti ashgti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

Copy link
Member

@vogelsgesang vogelsgesang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good :)

Out of curiosity: What motivated you to make this change?
Maybe you might also want to add that reasoning to the commit message, might be useful to future contributors who want to understand your original motivations in the future

@JDevlieghere
Copy link
Member Author

I moved the MemoryMonitor into Host. As I was testing this on macOS with the memory_pressure utility, I noticed that the libdispatch call would fail consistently. I was afraid this was going to happen as I'm pretty sure you're not supposed to call this from anything but Objective-C(++). Anyway, I didn't want to mimic the complexity of the Host library for the lldb-dap tool so I decided to just move it in there, as we're already relying on a bunch of other host classes.

macOS now has it's own implementation based on libdispatch and Linux uses a polling approach. I made an attempt to make this work on Windows too, but I don't have a Windows setup handy to test this on, so I'm not even sure it compiles. If someone does, I would appreciate it if you could confirm this builds and maybe even try it out with testlimit.

Copy link
Contributor

@ashgti ashgti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@walter-erquinigo
Copy link
Member

This looks very nice. Just out of curiosity, is something printed on the terminal/console whenever one of these low memory events happen?

@JDevlieghere
Copy link
Member Author

This looks very nice. Just out of curiosity, is something printed on the terminal/console whenever one of these low memory events happen?

No, this should be transparent to the user. Removing the orphaned modules might not be enough to get out of this situation, so the callback might be called repeatedly. I've added a log line so we can see when this happens that way.

@JDevlieghere JDevlieghere changed the title [lldb-dap] Implement a MemoryMonitor for macOS & Linux [lldb-dap] Implement a MemoryMonitor Mar 5, 2025
@JDevlieghere JDevlieghere merged commit 6c4febe into llvm:main Mar 5, 2025
10 checks passed
@JDevlieghere JDevlieghere deleted the memory-monitor branch March 5, 2025 16:49

lldb::thread_result_t MonitorThread() {
#if defined(__linux__)
struct pollfd fds;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you need to zero out this struct?

while (!m_done) {
int n = poll(&fds, 1, g_timeout);
if (n > 0) {
if (fds.revents & POLLERR)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there's an error, is there a way to restart the loop?

nico added a commit that referenced this pull request Mar 7, 2025
Motivated by #129332, which added a new .mm file that didn't get
autosynced.
jph-13 pushed a commit to jph-13/llvm-project that referenced this pull request Mar 21, 2025
This implements a memory monitor for macOS, Linux and Windows. It
registers a callback that invokes `SBDebugger::MemoryPressureDetected`
when a low memory event is detected. This is motivated by the new
server mode, where the lldb-dap process will live across multiple
debug sessions and will use more memory due to caching.
jph-13 pushed a commit to jph-13/llvm-project that referenced this pull request Mar 21, 2025
Motivated by llvm#129332, which added a new .mm file that didn't get
autosynced.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants