Libelektra: KeySet Speicherverlust schließen

Erstellt am 4. Nov. 2019  ·  32Kommentare  ·  Quelle: ElektraInitiative/libelektra

Beim Benchmarking des neuen Elektrad traten Symptome eines Gedächtnisverlusts auf.
Die Speichernutzung würde steigen, nachdem neue Handles erstellt und kdbGet() aufgerufen wurden, aber niemals nach ksClose() und kdbClose() .

Mein erster Verdacht war das mmap-Caching - was wahrscheinlich auch zu Speicherlecks führt, da der mmaped-Speicher nie freigegeben wird -, aber das Deaktivieren des Caching durch Erstellen mit -DPLUGINS="ALL;-cache" hat mein Problem nicht gelöst.

Schritte zum Reproduzieren des Problems

Gehe zu> = 1,13

Ich habe zwei Tests im go-elektra repo erstellt. Beide erstellen ein Testschlüsselset mit 100000 Schlüsseln.

  1. TestKeySetMemory erstellt Handles und kdbGets Keysets in einer Schleife und schließt nach 1 Sekunde Wartezeit das Keyset + Handle sofort wieder, bevor Sie von vorne beginnen.
  2. TestKeySetMemoryWithDelayedClose erstellt auch Handles und füllt Keysets mit kdbGets - verzögert jedoch das Schließen des Handles und des Keysets, bis alle 20 Keysets geladen wurden. Dies ahmt das Verhalten des elektrad Webservers nach.

Beide Tests warten nach Abschluss 20 Sekunden, damit der Tester den Speicherverbrauch des Tests über htop oder ähnliche Tools anzeigen kann.

Der erste Test, bei dem Griff und Keyset sofort geschlossen werden, behält über die gesamte Testdauer den gleichen Speicherbedarf.

Der zweite Test, bei dem die Handles und Keysets erst geschlossen werden, nachdem jedes Keyset "geladen" wurde, gibt niemals Speicher frei. Auch nach dem Erzwingen der Speicherbereinigung und dem Warten auf 20 Sekunden.

Im Moment bin ich ahnungslos, warum sich das Verhalten dieser Tests unterscheidet.

Sie können die Tests ausführen, indem Sie das Repo go-elektra klonen und diese beiden Befehle in der Unterverzeichnis ./kdb ausführen:

PKG_CONFIG_PATH=<PATH TO elektra.pc FILE> go test  -run "^(TestKeySetMemory)\$"

PKG_CONFIG_PATH=<PATH TO elektra.pc FILE> go test  -run "^(TestKeySetMemoryWithDelayedClose)\$"

erwartetes Ergebnis

Der Speicher wird nach kdbClose und ksClose freigegeben

Tatsächliche Ergebnis

Der Speicher wird nicht freigegeben

System Information

  • Elektra Version: Meister
  • Betriebssystem: Arch Linux
  • Neueste Go-Elektra-Version
bug urgent work in progress

Alle 32 Kommentare

Es scheint mir unwahrscheinlich, dass so etwas durch unsere Valgrind- und ASAN-Tests verrutschen würde. Ich habe ähnliche Benchmarks in C durchgeführt und konnte diese nicht beobachten. Selbst mit mmap kann ich nichts Ähnliches beobachten. Ich schließe es jedoch nicht aus.

Sind Sie sicher, dass die Bindungen nicht irgendwo undicht sind?

Ja, ich bin sicher - es ist möglich, die Speichernutzung einer go-Anwendung zur Laufzeit zu überprüfen. Diese Statistiken enthalten KEINE Zuweisungen nach C-Code und sind bei weitem nicht annähernd die gesamte Speichernutzung der Anwendung / des Tests, was bedeuten würde, dass die Ein Großteil des Speichers ist in C zugeordnet.

Mein Verdacht ist, dass Schlüssel nicht freigegeben werden, da der Ref-Zähler != 0 , obwohl er 0 .

Das bedeutet, dass die Bindungen die C-API auf eine Weise aufrufen, die dieses Memleak auslöst. Leider weiß ich nicht gehen, deshalb weiß ich nicht, warum das passiert.

Können Sie vielleicht ein kleines C-Beispiel extrahieren, das das Leck auslöst? Ich mache etwas sehr Ähnliches in benchmarks/kdb.c und es gibt dort kein Leck, also muss es genau das tun, was Ihr Benchmark tut, um das Leck auszulösen.

Vielen Dank, dass Sie das Problem gemeldet haben!

Ich stimme @mpranj hier voll und Minimum reduzieren. Bei mehreren beteiligten Sprachen ist es schwierig, etwas zu finden.

Können Sie vielleicht ein C-Programm schreiben, das die von den Go-Bindungen ausgegebene Aufrufsequenz reproduziert? Sobald wir das haben, können wir das C-Programm für einen Testfall minimieren.

Übrigens. Beim Versuch, den obigen Befehl auszuführen, bin ich ziemlich früh gescheitert: # 3159

Ich werde versuchen, dies bis morgen in C zu reproduzieren

Ich habe dieses Problem hier erfolgreich reproduziert. Führen Sie den Benchmark benchmarks/memoryleak.c und beobachten Sie, wie der Speicher beim Abrufen von Keysets nach oben geht - und beim Freigeben NICHT nach unten.

Vergessen Sie nicht, eine bemerkenswerte Anzahl von Schlüsseln in die Datenbank aufzunehmen. Ich habe es mit 100.000 Schlüsseln getestet.

Vielen Dank, dass Sie das Beispiel erstellt haben! Ich werde einen Blick darauf werfen.

Auf den ersten Blick gibt es genau ein direktes Leck, das Sie benötigen, um den parentKey freizugeben:
https://github.com/raphi011/libelektra/blob/afcbcf5c8138be8de6ba9b1a9e559bc673ff887f/benchmarks/memoryleak.c#L22

Dieses kleine Leck ist wahrscheinlich nicht der Grund für Ihre Beobachtungen.

Als Randnotiz: Bei Verwendung des mmap-Cache stelle ich sogar einen WENIGEREN Speicherverbrauch fest als ohne den Cache. Vielleicht muss ich das zu meiner These hinzufügen: smile:. (Cache: ~ 200M vs. kein Cache: ~ 600M)

Es scheint, dass wir Speicher über dlopen() / dlclose() "verlieren". Folgendes kann beobachtet werden, indem kdb aber NICHT kdb-static (nur ein Auszug):

valgrind --leak-check=full --show-leak-kinds=all ./bin/kdb ls user
[...]
==48451== 1,192 bytes in 1 blocks are still reachable in loss record 6 of 6
==48451==    at 0x483AB1A: calloc (vg_replace_malloc.c:762)
==48451==    by 0x400BB11: _dl_new_object (in /usr/lib64/ld-2.30.so)
==48451==    by 0x400642F: _dl_map_object_from_fd (in /usr/lib64/ld-2.30.so)
==48451==    by 0x4009315: _dl_map_object (in /usr/lib64/ld-2.30.so)
==48451==    by 0x400DB24: openaux (in /usr/lib64/ld-2.30.so)
==48451==    by 0x4DFE8C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
==48451==    by 0x400DF6A: _dl_map_object_deps (in /usr/lib64/ld-2.30.so)
==48451==    by 0x4013AC3: dl_open_worker (in /usr/lib64/ld-2.30.so)
==48451==    by 0x4DFE8C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
==48451==    by 0x401363D: _dl_open (in /usr/lib64/ld-2.30.so)
==48451==    by 0x496139B: dlopen_doit (in /usr/lib64/libdl-2.30.so)
==48451==    by 0x4DFE8C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
[...]

Ihr Beispiel öffnet viele Griffe und mehr Griffe. Es sieht also so aus:

==48735== 72,704 bytes in 1 blocks are still reachable in loss record 8 of 8
==48735==    at 0x483880B: malloc (vg_replace_malloc.c:309)
==48735==    by 0x4F860A9: ??? (in /usr/lib64/libstdc++.so.6.0.27)
==48735==    by 0x400FD59: call_init.part.0 (in /usr/lib64/ld-2.30.so)
==48735==    by 0x400FE60: _dl_init (in /usr/lib64/ld-2.30.so)
==48735==    by 0x4013DBD: dl_open_worker (in /usr/lib64/ld-2.30.so)
==48735==    by 0x4A088C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
==48735==    by 0x401363D: _dl_open (in /usr/lib64/ld-2.30.so)
==48735==    by 0x48C739B: dlopen_doit (in /usr/lib64/libdl-2.30.so)
==48735==    by 0x4A088C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
==48735==    by 0x4A08962: _dl_catch_error (in /usr/lib64/libc-2.30.so)
==48735==    by 0x48C7B08: _dlerror_run (in /usr/lib64/libdl-2.30.so)
==48735==    by 0x48C7429: dlopen@@GLIBC_2.2.5 (in /usr/lib64/libdl-2.30.so)

In diesem Beispiel öffnen Sie viele KDB-Handles, wodurch sich dieser Effekt zu einem erheblichen Speicherverbrauch summiert. Wenn Sie nur einen Griff öffnen, sollte dieser kleiner sein. @ raphi011 Ich weiß nicht, ob Sie kdb-static für Ihre Benchmarks verwenden können. Wenn ich die Benchmarks mit der elektra-static -Bibliothek verknüpfe, kann ich dasselbe nicht mehr beobachten:

==54836== HEAP SUMMARY:
==54836==     in use at exit: 0 bytes in 0 blocks
==54836==   total heap usage: 6,456,631 allocs, 6,456,631 frees, 272,753,180 bytes allocated
==54836== 
==54836== All heap blocks were freed -- no leaks are possible
==54836== 
==54836== For lists of detected and suppressed errors, rerun with: -s
==54836== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Ich bin mir derzeit nicht sicher, was wir gegen die Probleme mit dlopen() tun können.

Dies ist eine vereinfachte Simulation des neuen Servers elektrad . Da wir mehrere Benutzer unterstützen möchten, die das System gleichzeitig konfigurieren, MÜSSEN wir mehrere Handles (Konfliktbehandlung usw.) unterstützen.

Ich denke immer noch, dass dies nicht die Wurzel des Problems ist. Das Problem ist, dass Keys / KeySets nicht freigegeben werden. Wenn Sie den übergeordneten Schlüssel in "system" ändern, der viel weniger Schlüssel enthält, werden Sie feststellen, dass der Speicherverbrauch viel geringer ist.

@ markus2330 was denkst du?

EDIT: Vergiss das.

Ich glaube nicht, dass wir dort Erinnerungen verlieren.
https://gist.github.com/mpranj/bbdf00af308ed3f5b3f0f35bc832756f~~

Ich kann dasselbe, was Sie mit HTOP beschreiben, und die Speichernutzung anhand des Codes aus dem obigen Kern beobachten.

Sie sagen mir also, dass es absolut normal ist, dass der Speicherverbrauch von elektra niemals abnimmt?

Sie sagen mir also, dass es absolut normal ist, dass der Speicherverbrauch von elektra niemals abnimmt?

Das sage ich nicht. Ich sage, ich habe das gleiche beobachtet, unabhängig von der Elektra. Ich habe gerade bemerkt, dass meine Beobachtung nur wahr ist, wenn ich mit Valgrind laufe. Vielleicht ist Valgrind aus anderen Gründen bis zum Ende nicht frei ().

https://stackoverflow.com/questions/40917024/memory-leak-after-using-calloc-and-free

Es scheint, als ob Sie den Zeiger ändern, bevor Sie ihn freigeben

Es scheint, als ob Sie den Zeiger ändern, bevor Sie ihn freigeben

Ich mache das nicht und Valgrind zeigt kein Leck für den Code im Kern. Aber vergessen Sie das Beispiel, es ist irrelevant, da es nur nicht sofort freigegeben wird, wenn es in valgrind ausgeführt wird, andernfalls ist der Speicher fast sofort frei () d.

Was ist relevanter: Ich habe beobachtet, was Sie melden, indem ich einfach benchmark_createkeys verwendet habe, das die KDB überhaupt nicht verwendet. Dort sind die Ressourcen auch nicht sofort frei () d, aber valgrind zeigt absolut 0 Lecks. Ich bin verblüfft.

@ markus2330 was denkst du?

Mit KeySets allein sollte es definitiv kein Leck geben. (Mit KDB können wir nicht alles vollständig steuern, da Plugins möglicherweise andere Bibliotheken laden, die auslaufen.)

@ raphi011 Kannst du eine PR erstellen oder mich auf das minimale Beispiel verweisen, das das Problem reproduziert?

https://github.com/raphi011/libelektra/tree/memoryleak los geht's. Ich habe mein vorheriges Beispiel erweitert, indem ich gedruckt habe, wie viele Schlüssel aufgrund von Schlüsselreferenzen > 0 NICHT von ksDel freigegeben wurden.

Wenn Sie den Benchmark ohne Valgrind ausführen, können Sie feststellen, dass die Schlüssel nicht freigegeben sind.

Ich kann dieses Repo nicht kompilieren, ich bekomme den Fehler:

CMake Error: Error processing file: /home/markus/Projekte/Elektra/repos/libelektra/scripts/cmake/ElektraManpage.cmake
make[2]: *** [src/bindings/intercept/env/CMakeFiles/man-kdb-elektrify-getenv.dir/build.make:61: ../doc/man/man1/kdb-elektrify-getenv.1] Fehler 1
make[1]: *** [CMakeFiles/Makefile2:17236: src/bindings/intercept/env/CMakeFiles/man-kdb-elektrify-getenv.dir/all] Fehler 2

Können Sie bitte zum Hauptmaster zurückkehren?

Und bitte machen Sie eine PR, dann ist es viel einfacher, die Änderungen zu sehen.

Wenn Sie den Benchmark ohne Valgrind ausführen, können Sie feststellen, dass die Schlüssel nicht freigegeben sind.

Können Sie hier die Ausgabe eines Laufs kopieren?

Ich habe beobachtet, was Sie melden, indem ich einfach Benchmark_Createkeys verwendet habe, die die KDB überhaupt nicht verwenden. Dort sind die Ressourcen auch nicht sofort frei () d, aber valgrind zeigt absolut 0 Lecks. Ich bin verblüfft.

Ich denke, wir sollten zuerst dieser Spur folgen, da es viel einfacher zu verstehen ist, wenn wir nur KeySets haben.

Vielleicht haben wir Valgrind eine falsche Unterdrückung hinzugefügt?

Als Randnotiz: Wenn ich den mmap-Cache verwende, bemerke ich sogar WENIGER Speicherverbrauch als ohne den Cache. Vielleicht muss ich das zu meinem Diplomarbeitslächeln hinzufügen. (Cache: ~ 200M vs. kein Cache: ~ 600M)

Das sind sicherlich gute Nachrichten.

Vielleicht haben wir Valgrind eine falsche Unterdrückung hinzugefügt?

Valgrind berichtete, dass ich bei den Tests keine Unterdrückung verwendet habe, aber Ihre Laufleistung ist unterschiedlich.

Ich glaube, ich habe das Problem bei meinen Benchmarks gefunden. Für mich ist das Problem die eifrige Zuweisung des Meta-KeySets für jeden Schlüssel. Nach einem schmutzigen Test in meinem Zweig, bei dem die eifrige Meta-KeySet-Zuordnung entfernt wurde, sinkt der Speicherverbrauch nach ksDel () schnell. Vielleicht wird dies durch # 3142 behoben, da es den Code ändert, über den ich spreche.

Ok, dann lassen Sie uns sehen, ob # 3142 das Problem behebt!

@ raphi011 ist das Problem auch vor # 3081

3142 ist jetzt zusammengeführt. @ raphi011 können Sie überprüfen, ob das Problem weiterhin auftritt?

leider ja

Für mich scheint es zumindest ein Problem zu lösen / zu mildern. Ich kann den Benchmark jetzt mit Millionen von Schlüsseln mit ~ 2 GB Speicher ausführen, während er zuvor abgestürzt ist, weil für denselben Benchmark> 20 GB Speicher verwendet wurden.

Für mich scheint es zumindest ein Problem zu lösen / zu mildern. Ich kann den Benchmark jetzt mit Millionen von Schlüsseln mit ~ 2 GB Speicher ausführen, während er zuvor abgestürzt ist, weil für denselben Benchmark> 20 GB Speicher verwendet wurden.

Ich habe es noch nicht mit der vorherigen Version verglichen, aber ich bin mir ziemlich sicher, dass es weniger Speicher benötigt als zuvor. Werde heute nachsehen!

Der Speicher wird jedoch immer noch nicht freigegeben.

Sie müssen malloc_trim(0) nach jedem free aufrufen (lesen Sie: ksDel ). Dies zwingt glibc, den Speicher sofort an das Betriebssystem zurückzugeben. Dies sollte das "seltsame Verhalten" beheben, das ihr seht. Oh und viel Spaß beim Lesen und Stöbern in glibc :-)

Ich habe # 3183 zum weiteren Testen / Reparieren erstellt.

Ich habe ein Memleak gefunden (in einem kdbGet wurden die Rückgabeschlüssel nicht freigegeben).

Aber die zunehmende Anzahl von "ksClose hat 532 Schlüssel NICHT freigegeben" ist wahrscheinlich nur die Warnung, die im parentKey gesammelt wurde. Wenn Sie NUM_RUNS höher machen, z. B. 100, stagniert es irgendwann, da die Anzahl der Warnungen auf 100 begrenzt ist. Für mich geht es auf max. "ksClose hat 901 Schlüssel NICHT freigegeben". Ein parentKey pro Handle würde dieses Problem beheben.

geschlossen von # 3183

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

sanssecours picture sanssecours  ·  3Kommentare

mpranj picture mpranj  ·  4Kommentare

markus2330 picture markus2330  ·  4Kommentare

dominicjaeger picture dominicjaeger  ·  3Kommentare

markus2330 picture markus2330  ·  4Kommentare