今日はpdns-mrubybackend を作っていてハマった落とし穴の話です。
pdns-mrubybackend は、C++で書いています。 もともとPowerDNSにはC++で書かれたrandombackendというサンプルのようなバックエンドモジュールが付属しており、 私のpdns-mrubybackend は、初めこのrandombackendを書き換えていく形で実装を始めました。
randombackend.cc は100行少々しかないコードで、PowerDNSのモジュールを 書いたことのない私にとっては最高のお手本でした。
ここで最初の落とし穴にはまります。
巷に数多あるmrubyのお手本となるコードは大半がC++ではなくCで書かれています。
CもC++も良く分かっていない私はおもむろに全てのコードを(C++の) class MrubyBackend
に突っ込みました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class MrubyBackend : public DNSBackend { public: MrubyBackend(const string &suffix = "") { mrb = mrb_open(); struct RClass *rclass = mrb_define_class(mrb, "Powerdns", mrb->object_class); mrb_define_class_method(mrb, rclass, "module_version", mrb_get_module_info,MRB_ARGS_NONE()); } mrb_value mrb_get_module_info(mrb_state *mrb, mrb_value self) { return mrb_str_new_lit(mrb, __TIMESTAMP__); } }
いろいろ端折ってますが、イメージはこんな感じです。関数mrb_get_module_info
も躊躇わずclass MrubyBackend
に放り込んでます。
そうするとまぁコンパイル時に怒られます。
1 2 mrubybackend .cc: In member function ‘void MrubyBackend ::init()’:mrubybackend .cc:64 : error: argument of type ‘mrb_value (MrubyBackend ::)(mrb_state*, mrb_value)’ does not match ‘mrb_value (*)(mrb_state*, mrb_value)’
んで。いろいろ試しているうちに、以下のように書けばコンパイルが通るなーと理解しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 mrb_value mrb_get_module_info(mrb_state *mrb, mrb_value self) { return mrb_str_new_lit(mrb, __TIMESTAMP__); } class MrubyBackend : public DNSBackend { public: MrubyBackend(const string &suffix = "") { mrb = mrb_open(); struct RClass *rclass = mrb_define_class(mrb, "Powerdns", mrb->object_class); mrb_define_class_method(mrb, rclass, "module_version", mrb_get_module_info,MRB_ARGS_NONE()); } }
このように常に固定の文字列の返るメソッドの場合や、シングルスレッドで動作する場合には(恐らく)このコードでも問題が起きません。
では以下の場合はどうでしょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 mrb_value mrb_pdns_answer_records; mrb_value pdns_mrb_get_pdns_mruby_answer(mrb_state *mrb, mrb_value self) { return mrb_pdns_answer_records; } mrb_value mrb_get_module_info(mrb_state *mrb, mrb_value self) { return mrb_str_new_lit(mrb, __TIMESTAMP__); } class MrubyBackend : public DNSBackend { public: MrubyBackend(const string &suffix = "") { mrb = mrb_open(); mrb_pdns_answer_records = mrb_ary_new(mrb); struct RClass *rclass = mrb_define_class(mrb, "Powerdns", mrb->object_class); mrb_define_class_method(mrb, rclass, "module_version", mrb_get_module_info,MRB_ARGS_NONE()); mrb_define_class_method(mrb, rclass, "answer",pdns_mrb_get_pdns_mruby_answer, MRB_ARGS_NONE()); } }
mrubyのスクリプト内でPowerdns::answer
を配列として扱います。 これもシングルスレッドの場合には問題が起きない(すぐ気付かない)です。
ただし、マルチスレッドになると事情が変わります。mrb_pdns_answer_records
を誰(どのスレッド)が初期化したり書き換えたりしているか分からないのです。
私のpdns-mrubybackendでも単体のdigでは問題が起きないのに、同時に名前解決リクエストを受け取るとその瞬間に死ぬ現象が起きました。
ここに至り、私は深く反省し、改めて人様のソースコードを読み mrb_iv_set
を使うのか。ふむふむ。 Cとmrubyの間でやり取りする変数は野ざらしにするんではなくて、mrb_stateのインスタンス変数として保持しておくんだな。 と学習したのでした。
人様のコードをよく読む、というのは大事ですね。
駆け足でまとまりのない記事ですが、pdns-mrubybackendを書いていてハマったしょぼい落とし穴の話でした!