mruby-redisのリークを直した話
プルリクにした以上のことはないんですが、mrbgem CIを作って以来ずっと懸案だったmruby-redisのメモリリークが直りました。
懸案だった理由なんですが、なんやかんや人気あるmrbgemなのと、よく壊れていたからです。
最近、よく壊れるmrbgemの座がmruby-jsonに移りつつあるのでmake mruby-json great againなんてPRが作られていてOSSウォッチングではこっちの方が楽しいです。
h2oに急に採用されて、まあ諸々アレだったところを修正してるのが原因なので実際は着実によくなっているので、mruby-iijsonなどで性能に不満のある方は是非mruby-jsonをご検討を。
直した内容
「例外安全」性です。
Visual Studioにいい解説記事があるので、一読することをおすすめします。
他に「Exceptional C++」という本もあるらしいので、若干古いですが根底にある考え方は変わっていないはずなので読んでみたいです。(わたしは読んだことがないです。)
今回は、例外が飛ぶ時に確保したリソースが解放されていないことが多かったためにそれなりに苦労しました。
leak sanitizerでメモリがリークしたことはわかったんですが、どこでリークしたのかを特定するのが困難で、とりあえず、テストケースを1個ずつ skip
して減ったかどうかを確認していってました。
地味に時間がかかるんですが、減るところがわかれば切り出し作業はできているのでまあなんとかなりました。
mrubyでは、例外は内部的にsetjmp/longjmpかC++の例外を利用しています。
なので、例外が送出された場合スタック上に関連付けられた解放処理の必要なオブジェクトごとスタックが巻き戻されます。
C++のRAII設計されたオブジェクトの場合は、libunwindがよろしく処理してくれますが、ただのCのオブジェクトの場合は、そのままリークするのでつらいです。
今回は、例外が飛びそうなところで、既にオブジェクトが確保されていた場合その直前に解放処理を挟んでいます。
こういった処理はどうしてもなぜかさけられがちな goto
文で書くと楽に書けます。
他のmrbgemでもけっこう似たようなバグがいっぱいあるので、異常系をテストしていない場合は気をつけた方がいいかと思います。
テストしていないバグはsanitizerでも基本的に検出できません。
今後
#69で既に立っているんですが、内部用のAPIである、 MRB_THROW
と MRB_CATCH
で実装されている部分を修正しないといけないです。