Libelektra: KeySet cerrar fuga de memoria

Creado en 4 nov. 2019  ·  32Comentarios  ·  Fuente: ElektraInitiative/libelektra

Mientras evaluaba el nuevo elektrad, experimenté síntomas de pérdida de memoria.
El uso de la memoria aumentaría después de crear nuevos identificadores y llamar a kdbGet() pero nunca bajaría después de ksClose() y kdbClose() .

Mi primera sospecha fue el almacenamiento en caché de mmap, que probablemente también causa pérdidas de memoria porque la memoria mmaped nunca se libera, pero deshabilitar el almacenamiento en caché compilando con -DPLUGINS="ALL;-cache" no resolvió mi problema.

Pasos para reproducir el problema

Ir> = 1,13

He creado dos pruebas en el repositorio de go-elektra . Ambos crean un conjunto de claves de prueba con 100000 claves.

  1. TestKeySetMemory crea identificadores y kdbGets claves en un bucle y después de esperar 1 segundo - inmediatamente cierra el conjunto de claves + asa de nuevo antes de comenzar de nuevo.
  2. TestKeySetMemoryWithDelayedClose también crea identificadores y llena conjuntos de claves con kdbGets , pero retrasa el cierre del identificador y el conjunto de claves hasta que se hayan cargado los 20 conjuntos de claves. Esto imita el comportamiento del servidor web elektrad.

Ambas pruebas esperan 20 segundos después de finalizar para permitir que el evaluador vea el consumo de memoria de la prueba a través de htop o herramientas similares.

La primera prueba, que cierra inmediatamente la manija y el juego de llaves, conserva la misma huella de memoria a lo largo de la prueba.

La segunda prueba que comienza a cerrar las manijas y los conjuntos de claves solo después de que cada conjunto de claves está "cargado" nunca libera memoria. Incluso después de forzar la recolección de basura y esperar 20 segundos.

Por el momento, no tengo ni idea de por qué difiere el comportamiento de estas pruebas.

Puede ejecutar las pruebas clonando el repositorio go-elektra y ejecutando estos dos comandos en la subdirección ./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)\$"

Resultado Esperado

La memoria se libera después de kdbClose y ksClose

Resultado actual

La memoria no se libera

Información del sistema

  • Versión de Elektra: maestro
  • Sistema operativo: Arch Linux
  • El más reciente versión de go-Elektra
bug urgent work in progress

Todos 32 comentarios

Me parece poco probable que algo así se escape a nuestras pruebas de valgrind y ASAN. He estado ejecutando puntos de referencia similares en C y no pude observar lo mismo. Incluso con mmap no puedo observar nada similar. Sin embargo, no lo descarto.

¿Estás seguro de que las fijaciones no tienen fugas en alguna parte?

Sí, estoy seguro: es posible inspeccionar el uso de memoria de una aplicación go durante el tiempo de ejecución, estas estadísticas NO contienen asignaciones por código C y no se acercan al uso total de memoria de la aplicación / prueba, lo que significaría que el la mayor parte de la memoria se asigna en C.

Mi sospecha es que las claves no se liberan porque el contador de referencias es != 0 aunque debería ser 0 .

Eso significa que los enlaces llaman a la API de C de una manera que desencadena esta fuga de memorias. Desafortunadamente, no sé ir, así que no sé por qué sucede esto.

¿Puede tal vez extraer un pequeño ejemplo de C que desencadena la fuga? Hago algo muy similar en benchmarks/kdb.c y no hay fugas allí, por lo que debe hacer exactamente lo que hace su punto de referencia para desencadenar la fuga.

¡Gracias por reportar el problema!

Estoy completamente de acuerdo con @mpranj aquí, necesitamos reducir el problema al mínimo, con varios idiomas involucrados, es difícil encontrar algo.

¿Puede tal vez escribir un programa en C que reproduzca la secuencia de llamadas emitida desde los enlaces go? Una vez que tengamos eso, podemos minimizar el programa C para un caso de prueba.

Por cierto. tratando de ejecutar el comando anterior fallé bastante temprano: # 3159

Intentaré reproducir esto en C hasta mañana.

Reproduje con éxito este problema aquí . Ejecute el punto benchmarks/memoryleak.c referencia NO disminuye al liberar.

No olvide agregar una cantidad notable de claves en la base de datos. Lo probé con 100k claves.

¡Gracias por crear el ejemplo! Le daré un vistazo.

A primera vista, hay exactamente una fuga directa, necesita liberar el parentKey:
https://github.com/raphi011/libelektra/blob/afcbcf5c8138be8de6ba9b1a9e559bc673ff887f/benchmarks/memoryleak.c#L22

Esta pequeña fuga probablemente no sea el motivo de sus observaciones.

Como nota al margen: al usar el caché mmap, noto incluso MENOS consumo de memoria que sin el caché. Tal vez necesite agregar esto a mi tesis: sonríe :

Parece que estamos "perdiendo" memoria a través de dlopen() / dlclose() . Se puede observar lo siguiente usando kdb pero NO usando kdb-static (solo un extracto):

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

Su ejemplo abre muchos identificadores más identificadores, por lo que se ve así:

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

En el ejemplo, está abriendo muchos identificadores de KDB, lo que hace que este efecto se sume a un consumo de memoria significativo. Si abre solo una manija, debería ser menos. @ raphi011 No sé si puede usar kdb-static para sus puntos de referencia. Al vincular los puntos de referencia a la biblioteca elektra-static , ya no puedo observar lo mismo:

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

Actualmente no estoy seguro de qué podemos hacer con los problemas con dlopen() .

Esta es una simulación simplificada del nuevo servidor elektrad . Dado que queremos admitir que varios usuarios configuren el sistema al mismo tiempo, NECESITAMOS admitir múltiples identificadores (manejo de conflictos, etc.).

Sigo pensando que esta no es la raíz del problema. El problema es que las claves / conjuntos de claves no se liberan. Si cambia la clave principal a "system" que tiene muchas menos claves, verá que el consumo de memoria es mucho menor.

@ markus2330 ¿qué opinas?

EDITAR: olvídate de esto.

No creo que estemos perdiendo memoria allí.
https://gist.github.com/mpranj/bbdf00af308ed3f5b3f0f35bc832756f~~

Puedo observar lo mismo que está describiendo con HTOP y el uso de la memoria usando el código de la esencia anterior.

¿Me estás diciendo que es absolutamente normal que el consumo de memoria de elektra nunca se reduzca?

¿Me estás diciendo que es absolutamente normal que el consumo de memoria de elektra nunca se reduzca?

No estoy diciendo eso. Estoy diciendo que observé lo mismo independientemente de elektra. Me acabo de dar cuenta de que mi observación solo es cierta cuando se ejecuta con valgrind, por lo que tal vez valgrind no sea libre () hasta el final por otras razones.

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

Parece que es porque estás cambiando el puntero antes de liberarlo.

Parece que es porque estás cambiando el puntero antes de liberarlo.

No estoy haciendo eso y valgrind no muestra ninguna fuga para el código en la esencia. Pero olvide el ejemplo, es irrelevante ya que solo no se libera inmediatamente cuando se ejecuta dentro de valgrind, de lo contrario, la memoria es free () d casi inmediatamente.

Lo que es más relevante: observé lo que está informando simplemente usando benchmark_createkeys que no usa el KDB en absoluto. Allí, los recursos tampoco son gratuitos () d inmediatamente, pero valgrind muestra absolutamente 0 fugas. Estoy desconcertado.

@ markus2330 ¿qué opinas?

Con KeySets solo, definitivamente no debería haber ninguna fuga. (Con KDB no podemos controlar completamente todo, ya que los complementos pueden cargar otras bibliotecas que se filtran).

@ raphi011 ¿ puede crear un PR o señalarme el ejemplo mínimo que reproduce el problema?

https://github.com/raphi011/libelektra/tree/memoryleak aquí tienes. Extendí mi ejemplo anterior imprimiendo cuántas claves NO fueron liberadas por ksDel debido a referencias clave > 0 .

Si ejecuta el benchmark sin valgrind, puede ver que las claves no se liberan.

No puedo compilar este repositorio, aparece el error:

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

¿Puedes cambiar la base al maestro principal, por favor?

Y por favor haga un PR, entonces es mucho más fácil ver los cambios.

Si ejecuta el punto de referencia sin valgrind, puede ver que las claves no se liberan.

¿Puedes copiar el resultado de una ejecución aquí?

Observé lo que está informando simplemente usando benchmark_createkeys que no usa el KDB en absoluto. Allí, los recursos tampoco son gratuitos () d inmediatamente, pero valgrind muestra absolutamente 0 fugas. Estoy desconcertado.

Creo que primero deberíamos seguir este rastro, ya que es mucho más fácil de entender si solo tenemos KeySets.

¿Quizás agregamos una supresión incorrecta a valgrind?

Como nota al margen: al usar el caché mmap, noto incluso MENOS consumo de memoria que sin el caché. Quizás necesito agregar esto a mi sonrisa de tesis. (caché: ~ 200 M frente a sin caché: ~ 600 M)

Sin duda, estas son buenas noticias.

¿Quizás agregamos una supresión incorrecta a valgrind?

Valgrind informó que no usó supresión cuando realicé las pruebas, pero su kilometraje puede variar.

Creo que encontré el problema durante mis evaluaciones comparativas. Para mí, el problema es la ansiosa asignación del meta KeySet para cada clave. Después de una prueba sucia en mi rama, al eliminar la ansiosa asignación de meta KeySet, el consumo de memoria disminuye rápidamente después de ksDel (). Quizás esto esté arreglado por # 3142 ya que cambia el código del que estoy hablando.

Ok, entonces veamos si # 3142 soluciona el problema.

@ raphi011, ¿el problema también ocurrió antes del # 3081?

3142 ahora está fusionado. @ raphi011 ¿ puede comprobar si el problema

por desgracia sí

Para mí, parece que al menos resuelve / mitiga algún problema. Ahora puedo ejecutar el punto de referencia con millones de claves usando ~ 2GB de memoria, mientras que antes fallaba debido a que usaba más de 20GB de memoria para el mismo punto de referencia.

Para mí, parece que al menos resuelve / mitiga algún problema. Ahora puedo ejecutar el punto de referencia con millones de claves usando ~ 2GB de memoria, mientras que antes fallaba debido a que usaba más de 20GB de memoria para el mismo punto de referencia.

Aún no lo he comparado con la versión anterior, pero estoy bastante seguro de que usa menos memoria que antes. ¡Lo comprobaré hoy!

Sin embargo, la memoria aún no se ha liberado.

Debe llamar a malloc_trim(0) después de cada free (lea: ksDel ). Esto obliga a glibc a devolver inmediatamente la memoria al sistema operativo. Esto debería solucionar el "comportamiento extraño" que están viendo. Ah, y diviértete leyendo y explorando glibc :-)

Creé el # 3183 para realizar más pruebas / correcciones.

Encontré un memleak (en un kdbGet, las claves de retorno no se liberaron).

Pero el número creciente de "ksClose NO liberó claves 532" es probablemente solo las advertencias recopiladas en parentKey. Si aumenta NUM_RUNS, por ejemplo, 100, se estanca en algún momento, ya que el número de advertencias está limitado a 100. Para mí, llega al máximo. "ksClose NO liberó claves 901". Tener un parentKey por identificador solucionaría este problema.

cerrado por # 3183

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

markus2330 picture markus2330  ·  3Comentarios

mpranj picture mpranj  ·  3Comentarios

sanssecours picture sanssecours  ·  3Comentarios

markus2330 picture markus2330  ·  4Comentarios

mpranj picture mpranj  ·  4Comentarios