Travis CI から Broken のメールが飛んできているのに気づく
今年も、Advent Calendar 賑わってますね。僕も何か書きたいなぁ、golangかmrubyで小ネタないかなぁと思いながら、ふとメールを見ていると、Travis CI から見慣れぬメールが飛んできていました。
Subject: [CRON] Broken: bamchoh/mruby-expat#16 (master - a763a44)
中身を見て見てると、僕の作ったmrbgemsのmruby-expat
が何やらビルドエラーになっているもよう。mrbgem側は何も変更を加えていないので、本体側の修正の影響でコケてるんだろうなぁと思ったところで、ピンときました。
「そうだ、これの修正をネタにしよう!あわよくばmruby本体にコントリビューションするチャンス!」
そう思って、調査開始。
不具合原因のコミットを特定
僕はmruby-expat
のCIを一週間に一度、土曜日に仕掛けてるんですが、ビルドログを見てみると、直近の2個がBrokenの状態でした。(直近一個前の奴はなぜ気づかなかったのか。。。)
なんしか、直近の2週間(11/18以降)のmruby本体のコミットによってBrokenが引き起こされていることになります。
gitリポジトリを特定のコミットの状態に戻す
github でコミットログを見てみると、11/18近辺でコミットがあることが確認できました。一旦そのコミットまで戻してみます(*1)
$ git reset --hard 825e93eba41909f51d7c98a54f7c52fe1835d8ec
プラグインの設定をして、make test
を実行。パスしました。
$ git reflog
からHEADのコミットを取得して $git reset --hard
でHEADに戻り、またコミット番号を入力し、テストを実行し...と繰り返すこと数回。ようやく原因のコミットを突き止めることに成功。
Add MRB_METHOD_TABLE_INLINE
option.
これが原因だったようです。(中身を見てもなんのこっちゃでしたが。)
Travis CI のビルドエラー内容との照合
Travis CI のビルドエラーでは以下のようなエラーが表示されていました。
TypeError: XmlParser#parse(root) => Can't get cfunc env from non-cfunc proc. (mrbgems: mruby-expat)
このエラーメッセージが起こるのが、上記コミットで言うところの、src/proc.c
の147行目になります。
140 MRB_API mrb_value
141 mrb_proc_cfunc_env_get(mrb_state *mrb, mrb_int idx)
142 {
143 struct RProc *p = mrb->c->ci->proc;
144 struct REnv *e;
145
146 if (!p || !MRB_PROC_CFUNC_P(p)) {
147 mrb_raise(mrb, E_TYPE_ERROR, "Can't get cfunc env from non-cfunc proc.");
148 }
149 e = MRB_PROC_ENV(p);
150 if (!e) {
151 mrb_raise(mrb, E_TYPE_ERROR, "Can't get cfunc env from cfunc Proc without REnv.");
152 }
153 if (idx < 0 || MRB_ENV_STACK_LEN(e) <= idx) {
154 mrb_raisef(mrb, E_INDEX_ERROR, "Env index out of range: %S (expected: 0 <= index < %S)",
155 mrb_fixnum_value(idx), mrb_fixnum_value(MRB_ENV_STACK_LEN(e)));
156 }
157
158 return e->stack[idx];
159 }
差分を見る限りでは特別何か特殊なことが行われているようには思えません。MRB_PROC_FUNC_P(p)
の実装も変化があるようには思えませんでした。
if文の前にprintfを仕込んでpの中身を確認してみるとnullだったので、mrb->c->ci->proc
に何も入っていないことが原因のようです。
プラグイン側から追っかけてみる
mrb->c->ci->proc
がどこで代入されているのか特定するのが難しそうだったので、一旦、プラグイン側から追っかけて見ることにします。いろんなところにprintf文を仕掛けてどこでエラーが発生しているのか特定することにしました。問題の箇所はsrc/mrb_expat.c
のmrb_expat_parse
内のmrb_funcall
にあるようでした。
このmrb_funcall
で何をしているかと言うと、mrblib/mrb_expat.rb
でattr_accessor
で定義したroot
メソッドを呼びたいんですね。それを呼ぶことでインスタンス変数の@root
の値を取得して戻り値として返したいという思いがありました。
mrb_funcallがおかしい?
いえ、本体で使われているmrb_fancallが動作していることからその可能性は低いと考えられます。呼び方が間違っていんだろうなぁという感覚はありました。
現に attr_accessor
をやめて、rootメソッドを定義すると動作します
# attr_accessor :root
# こっちはイケる
def root
@root
end
C側からattr_accessorのメソッドをmrb_funcallで呼び出すのが問題?
実験として、mrblibでrootメソッドを呼び出して見ました。XmlParser.parse
メソッドを以下のように書き換えて、C側のmrb_funcall
をコメントアウトして実行したところ、うまく動きます。
class XmlParser
def self.parse(str)
o = self.new
o.__sys_parse__(str)
o.root
end
...
というところで時間切れ
attr_reader
で定義しためそっどをmrb_funcall
で呼び出すのがマズいというところまでは突き止めたのですが、なぜマズいのか。以前はなぜ動いていたのか。と言うところまでは突き止められませんでした。
修正方法
mrb_funcall
を使わずに、インスタンス変数を直接読み出すことで解決させました。
まとめ
自作プラグインのエラーを解消するためにmrubyの動作を確認しつつ、どのように回収すればいいかを検討しました。今回はmruby本体の不具合ではなさそうですが、今後も自作プラグインを改修する中で色々な問題にぶち当たると思います。その都度、mrubyの理解を深め、最終的にはmruby本体にコントリビュートできるようになっていけたらいいなと思います。
追記(2017-12-05 02:15)
なんかモヤモヤするので、もうちょっと調べてみようと思い、件のコミットログの差分を見ていると、ci->proc
が削除されている箇所を発見しました。
この削除された部分は、少し下のProc/Funcのタイプ判定の部分のelse文で追加されています。
そして、今回の問題となっているattr_accessor
はこのelse文には入りません。理由は、attr_accessor
を定義するときに内部的に呼ばれるmrb_proc_new_cfunc
の中でMRB_PROC_CFUNC_FL
フラグをONにしており、このフラグがONになっていると、判定文の最初の分岐に入ってしまうからです。
試しに、最初の分岐の中でprocを代入し、make test
を通してみると、エラーなく動作することが確認できます。
if (MRB_METHOD_CFUNC_P(m)) {
if (MRB_METHOD_PROC_P(m)) { // +
ci->proc = MRB_METHOD_PROC(m); // + この部分を追加
} // +
ci->nregs = (int)(argc + 2);
stack_extend(mrb, ci->nregs);
}
ただ、この修正をPRすることはないと思います。理由としては、このお試し修正が mruby
本体の設計思想にマッチするのかどうかわからないこと、mrb_funcall
という副作用の強い関数を使っていたこと、rubyスクリプト側からの呼び出しでは正常に動作すること、エラーが出てるのが私だけである可能性が高いこと等色々な理由により、修正のわりにはメリットが少ないと感じたからです。
ともあれ、どの部分が修正されたことによって今回の問題が発生したのかの原因追求が自分の納得のいく形でできましたので、これで今回のお話は終わりにしようと思います。
参考文献
(*1) コミットを戻すのに参考にしたサイト:
https://qiita.com/yaotti/items/e37c707938847aee671b