Shogo's Blog

Feb 1, 2014 - 1 minute read - perl redis

Redis::Fast 0.06 released

こんにちは、もうすぐ17才と100ヶ月を迎えるいっちーです。 今朝、Redis::Fast 0.06をリリースしました。 主な変更点はメモリーリークの修正と、エラー発生時にSegmentation Faltで落ちる問題の修正です。

メモリーリーク

Redis::Fastをサブスクライバーモードで動作させると、メモリを無限に食い続ける問題をついに!ついに!修正しました。 原因は、一言で言ってしまえば、Perlのリファレンスカウントの扱いの勉強不足です・・・。

XSの中でPerlのオブジェクトを作るとき、プログラマが手動でリファレンスカウントを制御する必要があります。 とはいうものの、全てのオブジェクトのリファレンスカウントを制御するのは大変なので、 XSには「揮発性」という考え方があります。 sv_2motralを使って変数を揮発性に設定しておけば、よしななタイミングでオブジェクトを解放してくれます。 gfx先生のブログにもあるように、 オブジェクト作成したら原則sv_2motralをつけるようにすれば、 メモリーリークはほとんどなくなるはずです。

SV * s = newSVpv("Hello World",0);  // Perl の文字列オブジェクト
sv_2motral(s) // 揮発性にすることで、使われなくなったら自動的に解放してくれる

この「よしななタイミング」をよく理解していなかったのでリークしてました・・・。 XSからオブジェクトへアクセスできなくなったときでないとオブジェクトを解放できないので、 揮発性のオブジェクトが実際に解放されるのは「XSで書かれた関数が終了してPerlに戻るとき」です。 メッセージを待ち続けるwait_for_messages関数は (タイムアウトをしない限り)ずっと終了しないので、 揮発性のオブジェクトを解放するタイミングが一切なかったのです。

不要になったら解放されるよう、揮発性オブジェクトの有効範囲を明示的に指定しました。

sv_2motral(s);
ENTER;
SAVETMPS;
sv_2motral(v);
FREETMPS;
LEAVE;
// v はココで解放される
// s は生き残ってる

perlcallとかちゃんとドキュメントを読みましょう > 自分

Segmentation Falt

同期的にコマンドを実行してる最中にSIGNAL等で実行が中断されると、 Segmentation Faltが起こる問題を修正しました。 Redis::Fastは同期モードでコマンドを発行したときでも、 hiredisの非同期モードの機能を使って通信しています。 コマンド実行中にエラーが発生すると、 コールバック関数の呼び出しタイミングが変わってしまい、 メモリの確保・解放のタイミングが狂ってしまっていました。

このバグ、試した環境の中ではUbuntu+Perl5.14でしか再現しませんでした。 他の環境ではたまたま解放後もアクセスできてしまって、 正常に動作してしまっていたようです。 嫌なバグだ・・・。

まとめ

C言語でメモリ管理するコードは書くべきでない。