Libelektra: KeySetがメモリリークを閉じる

作成日 2019年11月04日  ·  32コメント  ·  ソース: ElektraInitiative/libelektra

新しいelektradのベンチマーク中に、メモリリークの症状が発生しました。
新しいハンドルを作成してkdbGet()を呼び出した後、メモリ使用量は増加しますが、 ksClose()kdbClose()後で減少することはありません。

私の最初の疑いはmmapキャッシングでした-mmapメモリは決して解放されないのでおそらくメモリリークも引き起こします-しかし-DPLUGINS="ALL;-cache"ビルドすることによってキャッシングを無効にしても私の問題は解決しません

問題を再現する手順

Go> = 1.13

go-elektraリポジトリで2つのテストを作成しました。 どちらも100000キーのテストキーセットを作成します。

  1. TestKeySetMemoryは、ループ内にハンドルとkdbGetsキーセットを作成し、1秒間待った後、すぐにキーセットとハンドルを閉じてから最初からやり直します。
  2. TestKeySetMemoryWithDelayedCloseもハンドルを作成し、キーセットにkdbGets入力しますが、20個のキーセットがすべて読み込まれるまで、ハンドルとキーセットを閉じるのを遅らせます。 これは、elektradWebサーバーの動作を模倣します。

両方のテストは、終了後20秒間待機して、テスターがhtopまたは同様のツールを介してテストのメモリ消費量を確認できるようにします。

ハンドルとキーセットをすぐに閉じる最初のテストでは、テストの期間中、同じメモリフットプリントが保持されます。

すべてのキーセットが「ロード」された後にのみハンドルとキーセットを閉じ始める2番目のテストでは、メモリが解放されることはありません。 ガベージコレクションを強制して20秒待った後でも。

現時点では、これらのテストの動作が異なる理由がわかりません。

go-elektraリポジトリのクローンを作成し、 ./kdbサブディレクトリで次の2つのコマンドを実行することにより、テストを実行できます。

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

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

期待される結果

kdbCloseksClose後にメモリが解放されます

実結果

メモリが解放されない

システムインフォメーション

  • エレクトラバージョン:マスター
  • オペレーティングシステム:Arch Linux
  • 最新のgo-elektraバージョン
bug urgent work in progress

全てのコメント32件

そのようなものがvalgrindとASANのテストでスリップする可能性は低いようです。 私はCで同様のベンチマークを実行してきましたが、同じものを観察できませんでした。 mmapを使用しても、同様のことは何も観察できません。 しかし、私はそれを除外していません。

バインディングがどこかに漏れていないことを確認しますか?

はい、確かです-実行時にgoアプリケーションのメモリ使用量を検査することは可能です。これらの統計には、Cコードによる割り当ては含まれていません。また、アプリケーション/テストの合計メモリ使用量にはほど遠いため、メモリの大部分はCで割り当てられます。

私の疑惑がrefカウンターがあるので、キーが解放されていないということです!= 0それがあるべきにもかかわらず、 0

これは、バインディングがこのメモリリークをトリガーする方法でCAPIを呼び出すことを意味します。 残念ながら、私は行くのかわからないので、なぜこれが起こるのかわかりません。

リークを引き起こす小さなCの例を抽出できますか? 私はbenchmarks/kdb.c非常によく似たことをしますが、そこにはリークがないので、リークをトリガーするためにベンチマークが行うことを正確に行う必要があります。

問題を報告していただきありがとうございます。

私はここで@mpranjに完全に同意します。問題を最小限に抑える必要があります。複数の言語が関係しているため、何も見つけるのが困難です。

goバインディングから発行された呼び出しシーケンスを再現するCプログラムを作成できますか? それができたら、テストケースのCプログラムを最小化できます。

ところで。 上記のコマンドを実行しようとすると、かなり早い段階で失敗しました:#3159

これを明日までCで再現してみます

私はこの問題をここでうまく再現しbenchmarks/memoryleak.cベンチマークを実行し、キーセットを取得している間はメモリが増加するのを確認します。解放するときに減少しないでください

DBにかなりの量のキーを追加することを忘れないでください..私は100kのキーでそれをテストしました。

例を作成していただきありがとうございます。 見てみます。

一見したところ、直接リークが1つだけあり、parentKeyを解放する必要があります。
https://github.com/raphi011/libelektra/blob/afcbcf5c8138be8de6ba9b1a9e559bc673ff887f/benchmarks/memoryleak.c#L22

この小さな漏れは、おそらくあなたの観察の理由ではありません。

注意点として:のmmapキャッシュを使用して、私はキャッシュなしの場合よりもさらに少ないメモリ消費量に注意してください。 多分私はこれを私の論文に追加する必要があります

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ハンドルを開いているため、この影響により、メモリが大幅に消費されます。 ハンドルを1つだけ開くと、それより少なくなります。 @ 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で説明しているのと同じこととメモリ使用量を確認できます。

それで、エレクトラのメモリ消費が決して縮小しないのは絶対に正常だと私に言っているのですか?

それで、エレクトラのメモリ消費が決して縮小しないのは絶対に正常だと私に言っているのですか?

私はそれを言っているのではありません。 私はエレクトラに関係なく同じことを観察したと言っています。 私の観察はvalgrindで実行している場合にのみ当てはまることに気づきました。そのため、他の理由でvalgrindが最後までfree()されていない可能性があります。

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

ポインタを解放する前に変更しているためのようです

ポインタを解放する前に変更しているためのようです

私はそれをしていません、そしてvalgrindは要点のコードのリークを示していません。 ただし、例を忘れてください。valgrind内で実行した場合にすぐに解放されないため、関係ありません。それ以外の場合、メモリはほとんどすぐにfree()dされます。

さらに関連性があるのは、KDBをまったく使用しないbenchmark_createkeysを使用するだけで、あなたが報告していることを観察したことです。 リソースもすぐにfree()dされませんが、valgrindは絶対に0のリークを示します。 私は困惑しています。

@ markus2330どう思いますか?

KeySetsだけでは、リークはまったくありません。 (KDBでは、プラグインがリークする他のライブラリをロードする可能性があるため、すべてを完全に制御することはできません。)

@ raphi011 PRを作成するか、問題を再現する最小限の例を

https://github.com/raphi011/libelektra/tree/memoryleakここに行きます。 キー参照> 0ために、 ksDelによって解放されなかったキーの数を出力することにより、前の例を拡張しました。

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

メインマスターにリベースして頂けますか?

そして、PRをしてください。そうすれば、変更を確認するのがはるかに簡単になります。

valgrindを使用せずにベンチマークを実行すると、キーが解放されていないことがわかります。

ここで実行の出力をコピーできますか?

KDBをまったく使用しないbenchmark_createkeysを使用するだけで、あなたが報告していることを観察しました。 リソースもすぐにfree()dされませんが、valgrindは絶対に0のリークを示します。 私は困惑しています。

KeySetしかない方がはるかに理解しやすいので、最初にこのトレースに従う必要があると思います。

valgrindに間違った抑制を追加したのではないでしょうか?

補足として、mmapキャッシュを使用すると、キャッシュがない場合よりもメモリ消費量が少なくなることに気付きます。 多分私はこれを私の論文の笑顔に追加する必要があります。 (キャッシュ:〜200M対キャッシュなし:〜600M)

これらは確かに素晴らしいニュースです。

valgrindに間違った抑制を追加したのではないでしょうか?

Valgrindは、私がテストを実行したときに抑制を使用していないと報告しましたが、あなたのマイレージは異なります。

ベンチマーク中に問題を見つけたと思います。 私にとっての問題は、各キーへのメタKeySetの熱心な割り当てです。 私のブランチでダーティテストを行った後、熱心なメタKeySet割り当てを削除すると、ksDel()の後でメモリ消費量がすぐに減少します。 たぶん、これは私が話しているコードを変更するので、#3142によって修正されます。

では、#3142で問題が解決するかどうか見てみましょう。

@ raphi011問題は#3081より前にも発生しましたか?

3142がマージされました。 @ raphi011問題がまだ発生するかどうかを確認できますか?

残念ながらそうです

私にとっては、少なくともいくつかの問題を解決/軽減する

私にとっては、少なくともいくつかの問題を解決/軽減する

以前のバージョンとはまだ比較していませんが、以前よりもメモリの使用量が少ないと確信しています。 今日チェックします!

ただし、メモリはまだ解放されていません。

freeごとにmalloc_trim(0)を呼び出す必要があります(読み取り: ksDel )。 これにより、glibcはすぐにメモリをOSに戻します。 これにより、皆さんが見ている「奇妙な動作」が修正されるはずです。 ああ、glibcを読んで掘り下げて楽しんでください:-)

さらにテスト/修正するために#3183を作成しました。

1つのメモリリークが見つかりました(kdbGetのリターンキーは解放されませんでした)。

しかし、「ksCloseは532キーを解放しませんでした」の数が増えているのは、おそらくparentKeyに収集された警告だけです。 NUM_RUNSを100などより高くすると、警告の数が100に制限されるため、ある時点で停滞します。私にとっては、最大になります。 「ksCloseは901キーを解放しませんでした」。 ハンドルごとにparentKeyがあると、この問題は修正されます。

#3183で閉鎖

このページは役に立ちましたか?
0 / 5 - 0 評価