Libelektra: KeySet ferme la fuite de mémoire

Créé le 4 nov. 2019  ·  32Commentaires  ·  Source: ElektraInitiative/libelektra

Lors de l'analyse comparative du nouvel elektrad, j'ai ressenti des symptômes de fuite de mémoire.
L'utilisation de la mémoire augmenterait après la création de nouveaux descripteurs et l'appel de kdbGet() mais jamais après ksClose() et kdbClose() .

Mon premier soupçon était la mise en cache mmap - qui provoque probablement également des fuites de mémoire car la mémoire mmaped n'est jamais libérée - mais la désactivation de la mise en cache en construisant avec -DPLUGINS="ALL;-cache" n'a pas résolu mon problème.

Étapes pour reproduire le problème

Aller> = 1,13

J'ai créé deux tests dans le repo go-elektra . Les deux créent un jeu de clés de test avec 100 000 clés.

  1. TestKeySetMemory crée des poignées et des jeux kdbGets clés
  2. TestKeySetMemoryWithDelayedClose crée également des poignées et remplit les jeux de clés avec kdbGets - mais retarde la fermeture de la poignée et du jeu de clés jusqu'à ce que les 20 jeux de clés aient été chargés. Cela imite le comportement du serveur Web elektrad.

Les deux tests attendent 20 secondes après la fin pour permettre au testeur de voir la consommation de mémoire du test via htop ou des outils similaires.

Le premier test, qui ferme immédiatement la poignée et le jeu de clés, conserve la même empreinte mémoire sur toute la durée du test.

Le deuxième test qui commence à fermer les poignées et les jeux de clés seulement après que chaque jeu de clés est «chargé» ne libère jamais de mémoire. Même après avoir forcé le ramassage des ordures et avoir attendu 20 secondes.

Pour le moment, je ne sais pas pourquoi le comportement de ces tests diffère.

Vous pouvez exécuter les tests en clonant le dépôt go-elektra et en exécutant ces deux commandes dans le sous-répertoire ./kdb :

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

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

résultat attendu

La mémoire est libérée après kdbClose et ksClose

Résultat actuel

La mémoire ne se libère pas

Informations système

  • Version Elektra: maître
  • Système d'exploitation: Arch Linux
  • Nouvelle Version go-elektra
bug urgent work in progress

Tous les 32 commentaires

Il me semble peu probable que quelque chose comme ça échappe à nos tests valgrind et ASAN. J'ai utilisé des benchmarks similaires en C et je n'ai pas pu observer la même chose. Même avec mmap, je ne peux rien observer de similaire. Je ne l'exclus pas cependant.

Êtes-vous sûr que les liaisons ne fuient pas quelque part?

Oui, je suis sûr - il est possible d'inspecter l'utilisation de la mémoire d'une application go pendant l'exécution, ces statistiques ne contiennent PAS d'allocations par code C et elles sont loin de l'utilisation totale de la mémoire de l'application / test, ce qui signifierait que le la majeure partie de la mémoire est allouée en C.

Je soupçonne que les clés ne sont pas libérées car le compteur de références est != 0 même s'il devrait être 0 .

Cela signifie que les liaisons appellent l'API C d'une manière qui déclenche ce memleak. Malheureusement, je ne sais pas y aller, donc je ne sais pas pourquoi cela se produit.

Pouvez-vous peut-être extraire un petit exemple en C qui déclenche la fuite? Je fais quelque chose de très similaire dans benchmarks/kdb.c et il n'y a pas de fuite, il doit donc faire exactement ce que fait votre benchmark pour déclencher la fuite.

Merci d'avoir signalé le problème!

Je suis entièrement d'accord avec @mpranj ici, nous devons réduire le problème à un minimum, avec plusieurs langues impliquées, il est difficile de trouver quoi que ce soit.

Pouvez-vous peut-être écrire un programme C qui reproduit la séquence d'appels telle qu'elle est issue des liaisons go? Une fois que nous avons cela, nous pouvons minimiser le programme C pour un cas de test.

D'ailleurs. en essayant d'exécuter la commande ci-dessus, j'ai échoué assez tôt: # 3159

J'essaierai de reproduire ça en C jusqu'à demain

J'ai reproduit avec succès ce problème ici . Exécutez le benchmark benchmarks/memoryleak.c et regardez la mémoire augmenter lors de l'obtention des jeux de clés - et NE PAS descendre lors de la libération.

N'oubliez pas d'ajouter une quantité notable de clés dans la base de données .. Je l'ai testé avec 100k clés.

Merci d'avoir créé l'exemple! Je regarderai.

À première vue, il y a exactement une fuite directe, vous devez libérer le parentKey:
https://github.com/raphi011/libelektra/blob/afcbcf5c8138be8de6ba9b1a9e559bc673ff887f/benchmarks/memoryleak.c#L22

Cette petite fuite n'est probablement pas la raison de vos observations.

Comme une note de côté: en utilisant le cache mmap, je remarque encore moins la consommation de mémoire que sans le cache. Peut-être que j'ai besoin d'ajouter ceci à ma thèse: sourire :. (cache: ~ 200 Mo vs pas de cache: ~ 600 Mo)

Il semble que nous «fuyions» de la mémoire via dlopen() / dlclose() . Ce qui suit peut être observé en utilisant kdb mais PAS en utilisant kdb-static (juste un extrait):

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)
[...]

Votre exemple ouvre de nombreuses poignées plus de poignées, donc cela ressemble à ceci:

==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)

Dans l'exemple, vous ouvrez de nombreuses poignées KDB, ce qui entraîne une consommation de mémoire importante. Si vous n'ouvrez qu'une seule poignée, elle devrait être inférieure. @ raphi011 Je ne sais pas si vous pouvez utiliser kdb-static pour vos benchmarks. En liant les benchmarks à la bibliothèque elektra-static je ne peux plus observer la même chose:

==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)

Je ne suis actuellement pas sûr de ce que nous pouvons faire pour résoudre les problèmes liés à dlopen() .

Ceci est une simulation simplifiée du nouveau serveur elektrad . Puisque nous voulons prendre en charge plusieurs utilisateurs configurant le système en même temps, nous devons prendre en charge plusieurs poignées (gestion des conflits, etc.).

Je pense toujours que ce n'est pas la racine du problème. Le problème est que les clés / jeux de clés ne sont pas libérés. Si vous changez la clé parente en "system" qui a beaucoup moins de clés, vous verrez que la consommation de mémoire est beaucoup plus faible.

@ markus2330 qu'en pensez-vous?

EDIT: oubliez ça.

Je ne pense pas que nous fuyions de la mémoire là-bas.
https://gist.github.com/mpranj/bbdf00af308ed3f5b3f0f35bc832756f~~

Je peux observer la même chose que vous décrivez avec HTOP et l'utilisation de la mémoire en utilisant le code de l'essentiel ci-dessus.

Alors vous me dites qu'il est tout à fait normal que la consommation de mémoire d'elektra ne diminue jamais?

Alors vous me dites qu'il est tout à fait normal que la consommation de mémoire d'elektra ne diminue jamais?

Je ne dis pas ça. Je dis que j'ai observé la même chose indépendamment d'elektra. Je viens de remarquer que mon observation n'est vraie que lorsque je cours avec valgrind, donc peut-être que valgrind n'est pas libre () jusqu'à la fin pour d'autres raisons.

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

On dirait que c'est parce que vous changez le pointeur avant de le libérer

On dirait que c'est parce que vous changez le pointeur avant de le libérer

Je ne fais pas cela et valgrind ne montre aucune fuite pour le code dans l'essentiel. Mais oubliez l'exemple, il n'est pas pertinent car il ne se libère pas immédiatement lorsqu'il est exécuté à l'intérieur de valgrind, sinon la mémoire est libre () d à peu près immédiatement.

Ce qui est plus pertinent: j'ai observé ce que vous signalez en utilisant simplement benchmark_createkeys qui n'utilise pas du tout la KDB. Là, les ressources ne sont pas non plus libres () immédiatement, mais valgrind montre absolument 0 fuite. Je suis déconcerté.

@ markus2330 qu'en pensez-vous?

Avec KeySets seul, il ne devrait absolument aucune fuite. (Avec KDB, nous ne pouvons pas tout contrôler complètement car les plugins peuvent charger d'autres bibliothèques qui fuient.)

@ raphi011 pouvez-vous créer un PR ou me

https://github.com/raphi011/libelektra/tree/memoryleak , c'est parti. J'ai étendu mon exemple précédent en imprimant combien de clés n'ont PAS été libérées par ksDel cause des références de clés > 0 .

Si vous exécutez le benchmark sans valgrind, vous pouvez voir que les clés ne sont pas libérées.

Je ne peux pas compiler ce repo, j'obtiens l'erreur:

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

Pouvez-vous revenir au maître principal s'il vous plaît?

Et s'il vous plaît, faites un PR, il est alors beaucoup plus facile de voir les changements.

Si vous exécutez le benchmark sans valgrind, vous pouvez voir que les clés ne sont pas libérées.

Pouvez-vous copier la sortie d'une exécution ici?

J'ai observé ce que vous signalez en utilisant simplement benchmark_createkeys qui n'utilise pas du tout la KDB. Là, les ressources ne sont pas non plus libres () immédiatement, mais valgrind montre absolument 0 fuite. Je suis déconcerté.

Je pense que nous devrions d'abord suivre cette trace, car il est beaucoup plus facile à comprendre si nous n'avons que des KeySets.

Peut-être avons-nous ajouté une mauvaise suppression à valgrind?

En guise de remarque: en utilisant le cache mmap, je remarque encore moins de consommation de mémoire que sans le cache. Peut-être que j'ai besoin d'ajouter ceci à mon sourire de thèse. (cache: ~ 200 Mo vs pas de cache: ~ 600 Mo)

Ce sont certainement de bonnes nouvelles.

Peut-être avons-nous ajouté une mauvaise suppression à valgrind?

Valgrind a signalé n'avoir utilisé aucune suppression lorsque j'ai effectué les tests, mais votre kilométrage varie.

Je pense avoir trouvé le problème lors de mes benchmarks. Pour moi, le problème est l'attribution impatiente du méta KeySet pour chaque clé. Après un test sale sur ma branche, en supprimant l'allocation impatiente de méta KeySet, la consommation de mémoire diminue rapidement après ksDel (). Peut-être que cela est corrigé par # 3142 car cela change le code dont je parle.

Ok, voyons si # 3142 résout le problème!

@ raphi011 est-ce que le problème est survenu avant le # 3081?

3142 est maintenant fusionné. @ raphi011 pouvez-vous vérifier si le problème persiste?

Malheureusement oui

Pour moi, cela semble au moins résoudre / atténuer certains problèmes. Je peux maintenant exécuter le benchmark avec des millions de clés en utilisant ~ 2 Go de mémoire, alors qu'il s'est écrasé auparavant en raison de l'utilisation de> 20 Go de mémoire pour le même benchmark.

Pour moi, cela semble au moins résoudre / atténuer certains problèmes. Je peux maintenant exécuter le benchmark avec des millions de clés en utilisant ~ 2 Go de mémoire, alors qu'il s'est écrasé auparavant en raison de l'utilisation de> 20 Go de mémoire pour le même benchmark.

Je ne l'ai pas encore comparé à la version précédente, mais je suis presque sûr qu'il utilise moins de mémoire qu'avant. Va vérifier aujourd'hui!

Cependant, la mémoire n'est toujours pas libérée.

Vous devez appeler malloc_trim(0) après chaque free (lire: ksDel ). Cela force la glibc à renvoyer immédiatement la mémoire au système d'exploitation. Cela devrait corriger le "comportement étrange" que vous voyez. Oh et amusez-vous à lire et à fouiller dans la glibc :-)

J'ai créé # 3183 pour d'autres tests / corrections.

J'ai trouvé un memleak (dans un kdbGet, les clés de retour n'étaient pas libérées).

Mais le nombre croissant de "ksClose n'a PAS libéré 532 clés" n'est probablement que les avertissements collectés dans parentKey. Si vous augmentez NUM_RUNS, par exemple 100, cela stagne à un moment donné, car le nombre d'avertissements est limité à 100. Pour moi, cela atteint max. "ksClose n'a PAS libéré 901 clés". Avoir un parentKey par handle résoudrait ce problème.

fermé par # 3183

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

markus2330 picture markus2330  ·  4Commentaires

sanssecours picture sanssecours  ·  3Commentaires

markus2330 picture markus2330  ·  3Commentaires

mpranj picture mpranj  ·  3Commentaires

sanssecours picture sanssecours  ·  4Commentaires