|
@@ -6,8 +6,10 @@
|
|
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
|
|
|
|
|
|
|
#include <csignal>
|
|
#include <csignal>
|
|
|
|
|
+#include <cstdio>
|
|
|
#include <cstdlib>
|
|
#include <cstdlib>
|
|
|
#include <filesystem>
|
|
#include <filesystem>
|
|
|
|
|
+#include <fstream>
|
|
|
#include <iostream>
|
|
#include <iostream>
|
|
|
#include <optional>
|
|
#include <optional>
|
|
|
#include <string>
|
|
#include <string>
|
|
@@ -32,6 +34,55 @@ void signalHandler(int signal) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// v1.8.3: diagnostic handlers for hunting the untracked RSS leak observed
|
|
|
|
|
+// on Zoe (BUG-memory-leak-zoe.md, "Update 2026-05-13 10:05"). Both are
|
|
|
|
|
+// async-signal-unsafe under the spec (they call into glibc allocator
|
|
|
|
|
+// state), but `malloc_info` / `malloc_trim` are well-known to work from
|
|
|
|
|
+// signal handlers in practice on glibc — and we'd rather have the
|
|
|
|
|
+// diagnostic than be pedantic about it.
|
|
|
|
|
+//
|
|
|
|
|
+// kill -USR1 $PID → write malloc_info(0) XML to stderr (and journal)
|
|
|
|
|
+// — captures arena stats: blocks in use, fastbin
|
|
|
|
|
+// caches, mmap'd regions. Compare two samples
|
|
|
|
|
+// over time to find which arena is growing.
|
|
|
|
|
+// kill -USR2 $PID → call malloc_trim(0), then log before/after RSS
|
|
|
|
|
+// — if RSS drops sharply, the gap is allocator
|
|
|
|
|
+// retention (madvise(DONTNEED) not happening
|
|
|
|
|
+// autonomously). If it doesn't drop, the leak
|
|
|
|
|
+// is genuinely held by the application.
|
|
|
|
|
+void mallocInfoHandler(int) {
|
|
|
|
|
+#if defined(__GLIBC__) && !defined(__APPLE__)
|
|
|
|
|
+ std::fprintf(stderr, "=== malloc_info dump (SIGUSR1) ===\n");
|
|
|
|
|
+ ::malloc_info(0, stderr);
|
|
|
|
|
+ std::fprintf(stderr, "=== end malloc_info ===\n");
|
|
|
|
|
+ std::fflush(stderr);
|
|
|
|
|
+#endif
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void mallocTrimHandler(int) {
|
|
|
|
|
+#if defined(__GLIBC__) && !defined(__APPLE__)
|
|
|
|
|
+ auto readRssKb = []() -> long {
|
|
|
|
|
+ std::ifstream s("/proc/self/status");
|
|
|
|
|
+ std::string line;
|
|
|
|
|
+ while (std::getline(s, line)) {
|
|
|
|
|
+ if (line.rfind("VmRSS:", 0) == 0) {
|
|
|
|
|
+ long kb = 0;
|
|
|
|
|
+ std::sscanf(line.c_str(), "VmRSS: %ld kB", &kb);
|
|
|
|
|
+ return kb;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ };
|
|
|
|
|
+ long before = readRssKb();
|
|
|
|
|
+ int released = ::malloc_trim(0);
|
|
|
|
|
+ long after = readRssKb();
|
|
|
|
|
+ std::fprintf(stderr,
|
|
|
|
|
+ "=== malloc_trim(0) (SIGUSR2): returned=%d, VmRSS %ld kB -> %ld kB (delta %+ld kB) ===\n",
|
|
|
|
|
+ released, before, after, after - before);
|
|
|
|
|
+ std::fflush(stderr);
|
|
|
|
|
+#endif
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
void printUsage(const char* programName) {
|
|
void printUsage(const char* programName) {
|
|
|
std::cout << "Usage: " << programName << " [OPTIONS]\n"
|
|
std::cout << "Usage: " << programName << " [OPTIONS]\n"
|
|
|
<< "\n"
|
|
<< "\n"
|
|
@@ -249,6 +300,11 @@ int main(int argc, char* argv[]) {
|
|
|
// Set up signal handlers
|
|
// Set up signal handlers
|
|
|
std::signal(SIGINT, signalHandler);
|
|
std::signal(SIGINT, signalHandler);
|
|
|
std::signal(SIGTERM, signalHandler);
|
|
std::signal(SIGTERM, signalHandler);
|
|
|
|
|
+ // v1.8.3: SIGUSR1 / SIGUSR2 for live heap-state probing on Zoe. Both
|
|
|
|
|
+ // log to stderr (caught by the systemd journal), do not interrupt
|
|
|
|
|
+ // service operation. Documented in CLAUDE.md.
|
|
|
|
|
+ std::signal(SIGUSR1, mallocInfoHandler);
|
|
|
|
|
+ std::signal(SIGUSR2, mallocTrimHandler);
|
|
|
|
|
|
|
|
#ifdef HAVE_SYSTEMD
|
|
#ifdef HAVE_SYSTEMD
|
|
|
// v1.7.5: extend the systemd start timeout while recovery is
|
|
// v1.7.5: extend the systemd start timeout while recovery is
|