-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[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
Conversation
This implements a memory monitor for macOS & Linux, and registers a callback that invokes SBDebugger::MemoryPressureDetected() when a low memory event is detected.
@llvm/pr-subscribers-lldb Author: Jonas Devlieghere (JDevlieghere) ChangesThis 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:
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 :
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
There was a problem hiding this 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
I moved the MemoryMonitor into Host. As I was testing this on macOS with the 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
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. |
|
||
lldb::thread_result_t MonitorThread() { | ||
#if defined(__linux__) | ||
struct pollfd fds; |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
Motivated by #129332, which added a new .mm file that didn't get autosynced.
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.
Motivated by llvm#129332, which added a new .mm file that didn't get autosynced.
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.