Fiddle で C のメモリを free した時にやらかした話
備忘録。
少し前に Fiddle を使って C 関数を呼び出す Ruby プログラムを書いた時にやらかした時のメモ。
Fiddle とは
とてもざっくりというと dlopen
/dlsym
のラッパー。以下は C の strlen
を Ruby から呼び出す例がドキュメントにある。
起きたこと
以下のような C ライブラリを Ruby から呼び出す必要があった。
// ライブラリが提供する構造体 struct foo_t; int main(void) { // ライブラリ内で malloc & 初期化した foo_t を取得 foo_t *foo = new_foo(); // foo を介してライブラリの機能を使う work_with_foo(foo); // アプリケーション側の責務として取得した foo を free で開放する必要がある free(foo); }
これを Ruby から Fiddle 経由で使うために(だいぶ簡易化しているが)以下のようなコードを書いた。((実際には Fiddle::Importer
を使った))
require 'Fiddle' # libfoo のラッパー module LibFoo extend Fiddle::Importer dlload 'libfoo.so' extern 'void *new_foo(void)' extern 'void work_with_foo(void *)' end # free を呼び出すための libc のラッパー module LibC extend Fiddle::Importer dlload 'libc.so.6' extern 'void free(void *)' end # foo ライブラリを利用する foo = LibFoo.new_foo LibFoo.work_with_foo(foo) # free で開放 LibC.free(foo)
動きそうだが、最後の LibC.free
の呼び出しで abort してしまった。
何が原因だったか
abort する Ruby プロセスを確認したところ、malloc
/free
が libc から libjemalloc に置き換わっていることがわかった(LD_PRELOAD
を使っていた)。つまり以下のような動きになっていた。
LibFoo.new_foo
内部のmalloc
呼び出しはプログラムがリンクしている libjemalloc のmalloc
が呼び出されるLibC.free
は明示的に libc のfree
が呼び出される
つまり jemalloc が管理しているメモリを libc の free
で解放しようとして死んでいた。
どうするべきか/どうあるべきか
実際にこの問題に遭遇した時はプログラムが libc
と libjemalloc
のどちらを使っているかを判断して、Fiddle でオープンする共有ライブラリを使い分けるようにした。
Fiddle.free
というそれっぽい関数もあるが、これは Fiddle.malloc
で確保したメモリにしか使ってはならないので断念。(おそらく Ruby インタプリタ内部のメモリ使用量管理と絡んでいる?)
そもそもの話として「アプリケーションコードに free
呼び出す責務がある」というライブラリ側の仕様が微妙な気もする。「開放処理も delete_foo
などでラップする」「ライブラリ内部で使うアロケータを利用者が指定できるようにする」などの仕様の方がいいかもしれない。