Libelektra: KeySet закрыть утечку памяти

Созданный на 4 нояб. 2019  ·  32Комментарии  ·  Источник: ElektraInitiative/libelektra

Во время тестирования нового elektrad я испытал симптомы утечки памяти.
Использование памяти увеличится после создания новых дескрипторов и вызова kdbGet() но никогда не уменьшится после ksClose() и kdbClose() .

Моим первым подозрением было кэширование mmap - которое, вероятно, также вызывает утечки памяти, потому что память mmaped никогда не освобождается, - но отключение кеширования путем сборки с помощью -DPLUGINS="ALL;-cache" не решило мою проблему.

Шаги по воспроизведению проблемы

Перейти> = 1,13

Я создал два теста в репозитории go-elektra . Оба создают тестовый набор ключей из 100000 ключей.

  1. TestKeySetMemory создает дескрипторы и kdbGets наборы ключей в цикле и после ожидания в течение 1 секунды сразу же снова закрывает набор ключей + дескриптор, прежде чем начать заново.
  2. TestKeySetMemoryWithDelayedClose также создает дескрипторы и заполняет наборы ключей kdbGets но откладывает закрытие дескриптора и набора ключей до тех пор, пока не будут загружены все 20 наборов ключей. Это имитирует поведение веб-сервера elektrad.

Оба теста ждут 20 секунд после завершения, чтобы тестер мог увидеть потребление памяти тестом с помощью htop или аналогичных инструментов.

Первый тест, который немедленно закрывает ручку и набор ключей, сохраняет тот же отпечаток памяти на протяжении всего теста.

Второй тест, который запускает закрытие дескрипторов и наборов клавиш только после того, как каждый набор ключей «загружен», никогда не освобождает память. Даже после принудительной сборки мусора и ожидания в течение 20 секунд.

На данный момент я не понимаю, почему поведение этих тестов различается.

Вы можете запустить тесты, клонировав репозиторий go-elektra и выполнив эти две команды в подкаталоге ./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)\$"

ожидаемый результат

Память освобождается после kdbClose и ksClose

Фактический результат

Память не освобождается

Системная информация

  • Электра Версия: мастер
  • Операционная система: Arch Linux
  • Новейшая версия go-elektra
bug urgent work in progress

Все 32 Комментарий

Мне кажется маловероятным, что что-то подобное ускользнет от наших тестов valgrind и ASAN. Я проводил аналогичные тесты на C и не мог наблюдать того же. Даже с mmap ничего подобного не наблюдаю. Но я не исключаю этого.

Вы уверены, что крепления где-то не протекают?

Да, я уверен - можно проверить использование памяти приложением go во время выполнения, эта статистика НЕ ​​содержит распределения по коду C, и они далеки от общего использования памяти приложением / тестом, что означало бы, что большая часть памяти выделена в C.

Я подозреваю, что ключи не освобождаются, потому что счетчик ссылок равен != 0 хотя он должен быть 0 .

Это означает, что привязки вызывают API C таким образом, чтобы вызвать эту утечку памяти. К сожалению, я не знаю go, поэтому не знаю, почему это происходит.

Можете ли вы извлечь небольшой пример C, который вызывает утечку? Я делаю нечто очень похожее в benchmarks/kdb.c и там нет утечки, поэтому он должен делать именно то, что делает ваш тест, чтобы вызвать утечку.

Спасибо, что сообщили о проблеме!

Я полностью согласен с @mpranj здесь, нам нужно свести проблему к минимуму, с использованием нескольких языков трудно что-либо найти.

Сможете ли вы написать программу на C, которая воспроизводит последовательность вызовов, полученную из привязок go? Получив это, мы можем минимизировать программу C для тестового примера.

Кстати. пытаясь запустить команду выше, я потерпел неудачу довольно рано: # 3159

Я постараюсь воспроизвести это на C до завтра

Я успешно воспроизвел эту проблему здесь . Запустите тест benchmarks/memoryleak.c и посмотрите, как увеличивается объем памяти при получении наборов ключей - и НЕ снижается при освобождении.

Не забудьте добавить заметное количество ключей в БД. Я тестировал его со 100к ключами.

Спасибо за создание примера! Я взгляну.

На первый взгляд прямая утечка ровно одна, нужно освободить parentKey:
https://github.com/raphi011/libelektra/blob/afcbcf5c8138be8de6ba9b1a9e559bc673ff887f/benchmarks/memoryleak.c#L22

Эта небольшая утечка, вероятно, не повод для ваших наблюдений.

В качестве побочного примечания: используя кеш mmap, я замечаю даже МЕНЬШЕ потребление памяти, чем без кеша. Может мне нужно добавить это к моему тезису: smile:. (кеш: ~ 200 МБ против отсутствия кеша: ~ 600 МБ)

Похоже, мы "протекаем" память через dlopen() / dlclose() . Следующее можно наблюдать, используя kdb но НЕ используя kdb-static (просто отрывок):

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

В вашем примере открывается много дескрипторов, больше дескрипторов, поэтому он выглядит так:

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

В этом примере вы открываете множество дескрипторов KDB, что приводит к тому, что этот эффект приводит к значительному потреблению памяти. Если открывать только одну ручку, должно быть меньше. @ raphi011 Я не знаю, можно ли использовать kdb-static для своих тестов. При связывании тестов с библиотекой elektra-static я больше не могу наблюдать то же самое:

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

В настоящее время я не уверен, что мы можем сделать с проблемами с dlopen() .

Это упрощенная симуляция нового сервера elektrad . Поскольку мы хотим поддерживать одновременную настройку системы несколькими пользователями, нам НЕОБХОДИМО поддерживать несколько дескрипторов (обработка конфликтов и т. Д.).

Я все же считаю, что проблема не в этом. Проблема в том, что ключи / наборы ключей не освобождаются. Если вы измените родительский ключ на "system" который имеет намного меньше ключей, вы увидите, что потребление памяти намного ниже.

@ markus2330 как ты думаешь?

РЕДАКТИРОВАТЬ: забудьте об этом.

Не думаю, что у нас происходит утечка памяти.
https://gist.github.com/mpranj/bbdf00af308ed3f5b3f0f35bc832756f~~

Я могу наблюдать то же самое, что вы описываете, с использованием HTOP и использования памяти, используя код из приведенной выше сути.

То есть, вы говорите мне, что потребление памяти elektra никогда не уменьшается? Это нормально?

То есть, вы говорите мне, что потребление памяти elektra никогда не уменьшается? Это нормально?

Я этого не говорю. Я говорю, что наблюдал то же самое независимо от электры. Я только что заметил, что мои наблюдения верны только при работе с valgrind, поэтому, возможно, valgrind не работает до конца по другим причинам.

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

Похоже, это потому, что вы меняете указатель, прежде чем освободить его

Похоже, это потому, что вы меняете указатель, прежде чем освободить его

Я этого не делаю, и valgrind не показывает утечки кода в сущности. Но забудьте про пример, он не имеет значения, так как он не освобождается сразу при запуске внутри valgrind, иначе память освобождается () d почти сразу.

Что более уместно: я наблюдал, о чем вы сообщаете, просто используя benchmark_createkeys который вообще не использует KDB. Там ресурсы тоже не free () d сразу, но valgrind показывает абсолютно 0 утечек. Я сбит с толку.

@ markus2330 как ты думаешь?

Только с KeySets точно не должно быть никакой утечки. (С KDB ​​мы не можем полностью контролировать все, поскольку плагины могут загружать другие библиотеки, которые протекают.)

@ raphi011 вы можете создать пиар или указать мне на минимальный пример, воспроизводящий проблему?

https://github.com/raphi011/libelektra/tree/memoryleak вот и все. Я расширил свой предыдущий пример, указав, сколько ключей НЕ было освобождено ksDel из-за ссылок на ключи > 0 .

Если вы запустите тест без valgrind, вы увидите, что ключи не освобождены.

Я не могу скомпилировать репо, получаю сообщение об ошибке:

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

Не могли бы вы сделать ребаз до главного мастера?

И, пожалуйста, сделайте пиар, тогда намного легче увидеть изменения.

Если вы запустите тест без valgrind, вы увидите, что ключи не освобождены.

Можете ли вы скопировать сюда результат прогона?

Я наблюдал, о чем вы сообщаете, просто используя benchmark_createkeys, который вообще не использует KDB. Там ресурсы тоже не free () d сразу, но valgrind показывает абсолютно 0 утечек. Я сбит с толку.

Я думаю, что мы должны сначала проследить эту трассировку, так как это намного легче понять, если у нас есть только KeySets.

Может, мы добавили в valgrind неправильное подавление?

В качестве побочного примечания: используя кеш mmap, я замечаю даже МЕНЬШЕ потребление памяти, чем без кеша. Может мне нужно добавить это к моему тезису smile. (кеш: ~ 200 МБ против отсутствия кеша: ~ 600 МБ)

Это, безусловно, отличные новости.

Может, мы добавили в valgrind неправильное подавление?

Valgrind сообщил, что не использовал подавления, когда я проводил тесты, но ваш опыт может варьироваться.

Я думаю, что обнаружил проблему во время тестов. Для меня проблема в том, что мета KeySet постоянно выделяется для каждого ключа. После грязного теста в моей ветке, удалив нетерпеливое выделение мета KeySet, потребление памяти быстро снизилось после ksDel (). Возможно, это исправлено с помощью # 3142, поскольку он меняет код, о котором я говорю.

Хорошо, тогда давайте посмотрим, решит ли проблему # 3142!

@ raphi011 проблема также возникала до # 3081?

3142 теперь объединен. @ raphi011 вы можете проверить,

К сожалению, да

Для меня это, по крайней мере, решает / смягчает какую-то проблему. Теперь я могу запустить тест с миллионами ключей, используя ~ 2 ГБ памяти, тогда как раньше он давал сбой из-за использования> 20 ГБ памяти для того же теста.

Для меня это, по крайней мере, решает / смягчает какую-то проблему. Теперь я могу запустить тест с миллионами ключей, используя ~ 2 ГБ памяти, тогда как раньше он давал сбой из-за использования> 20 ГБ памяти для того же теста.

Я еще не сравнивал его с предыдущей версией, но уверен, что он использует меньше памяти, чем раньше. Проверю сегодня!

Однако память все еще не освобождена.

Вам нужно вызывать malloc_trim(0) после каждого free (читай: ksDel ). Это заставляет glibc немедленно вернуть память в ОС. Это должно исправить "странное поведение", которое вы наблюдаете. О, и получайте удовольствие, читая и копаясь в glibc :-)

Я создал # 3183 для дальнейшего тестирования / исправления.

Я обнаружил одну утечку памяти (в kdbGet ключи возврата не освобождались).

Но растущее число «ksClose НЕ освободило 532 ключа», вероятно, связано только с предупреждениями, собранными в parentKey. Если вы сделаете NUM_RUNS больше, например 100, в какой-то момент он застаивается, так как количество предупреждений ограничено 100. Для меня это максимальное значение. «ksClose НЕ освободил 901 ключ». Наличие parentKey для каждого дескриптора решит эту проблему.

закрыто # 3183

Была ли эта страница полезной?
0 / 5 - 0 рейтинги