nagachika's tumblr

2 リアクション

Ruby 2.0.0 のメンテナンス申し送り

2013年2月の Ruby 20周年にリリースされた安定版 2.0.0 のブランチメンテナとしてここ1年あまり活動してきましたが、Rubyアソシエーション様の 2014年度「Ruby安定版保守委託」の内容が 2.0.0 のメンテナンスとなっていますので[1]、4月からは 2.0.0 のメンテナを交代することになるかもしれません。わたしはRubyアソシエーションの中のひとではないので募集状況などどうなりそうなのか知らないので、たぶん、ですが。先日リリースした 2.0.0-p451 がわたしの手による 2.0.0 のパッチレベルリリースとしては最後のものになるかもしれません。その後もバックポートは続けてるのでまだ活動はしてますけどね。

またそうした状況は置いておくとしても、Ruby 2.0.0 のメンテナンスにおけるポリシーや、個々の事例についてわたしがどう考えて判断したのかについて書き残しておくことはそれなりに有用であろうということで、この機にいくつか書いておこうと思います。英語で書くのはそれなりに大変だしまあ引き継ぐとしてもその人も日本語が読める公算は高いので日本語で失礼します。

メンテナンスポリシー

まず、Ruby のメンテナンスポリシーについては偉大な先人による文書があるのでこちらを参照されたい[2]。これが書かれた当時と異なっている点は…と思ったのだけど、今読み返すと定期リリースが年2回(を目指す)というのは最近再確認されたコンセンサスとも一致しており、なんというかあんまり変わりがないですね。また「安定版とはなにか」「バグ修正とはなにか」についての深淵な議論が含まれており是非一読ください。

わたしのメンテナンスポリシー[3] もこれを下敷きとしており、バックポートはバックポートリクエストのチケットにより行う、不具合修正のみバックポートする、などの基本的方針はここから(および 1.9.3 のメンテナンスポリシーから)取ってます。ちょっと補足しておくと、去年 2.0.0 メンテナンスポリシーを書いた時にはバックポートリクエストは専用のプロジェクトに新規にチケットを切るという方法(もしくは既存のチケットからバックポート用のプロジェクトに移動する)がメインだったのですが、現在は安定版が複数存在するという状況を鑑みて、ruby-trunk プロジェクトのチケットには Backport というカスタムフィールドが存在していて、ここに各ブランチへのバックポートの要、不要、完了などの状態を記載することでも管理できるようになっています。今まだどっちをメインに使うか生煮え状態な感じですが、少なくともわたしは ruby-trunk の status: closed で Backport に 2.0.0: REQUIRED が存在するチケットというのも監視しています。ここ実はちょっとした tips で、たまにバックポートして欲しいからと Backport 欄を埋めるのはいいんだけど status を Open とか Assigned に戻す人がいるんですよね。それやると逆に埋もれます。あと experimental の機能についての議論というのは要するに Refinements についてで、これも後述しますが要約すると「Matz の判断を仰ぐ」となっています。

メンテナンスポリシー(本音版)

まあそうはいってもどれをバックポートすべきか決めるのに何を考えているのかというのはあるので、ちょっと書いておくと

  • SEGV など rb_bug() (いわゆる [BUG] のメッセージが出る異常終了)の修正。ま、普通これが出てたらバグですから
  • ドキュメント修正。最初割と積極的に入れてましたが、次第に基本リクエストがあるものを入れるように
  • テストの改善。これは特に envutil.rb の修正はある程度追随しておかないとバックポートでテストが動かなくて困ることが多いので、気がついたら入れる。同梱版の test/unit の assertion 追加とかは、一応機能追加というべきなんでしょうけど、実質 make test-all のために存在しているとも言えるのでなかなか悩ましいところです。何が言いたいかというと独自の assertion 追加はできれば envutil.rb でやって欲しい
  • include とか prepend とか Refinements と define_method とか alias とか super が絡んで、呼び出しが固まる系。無限ループ状態になるのはまあ明白にバグですよね
  • 同上で、ただし「こっちのメソッドが呼ばれるはずなのにこっちが呼ばれる」みたいなやつ。 Refinements が絡んだら念のため Matz 采配を仰ぎます。あとは個別に考える。あんまり迷ったケースはなかったような(だいたい不具合と考えるべきものだった)。逆にいうと、こういうので「あれっ、こうなってるんだー」と不自然だと思ったら、そこは変わるところかもしれないのでそれに依存するのではなくこれバグじゃね? と報告するのが生きやすくなるコツです
  • ○○でビルドできない系。極力頑張るという程度に…
  • 標準添付ライブラリ○○で○○に対応していなかったのに対応した系。これ機能追加だろ…とか思わなくもないけど入れる時もある。

見落しはあると思うので、この修正は入れるべきなのに入ってないよみたいなのはこっそり教えてください。

RubyGems

2.0.0 に同梱されている RubyGems のバージョンは 2.0 系です。trunk(2.2)/2.1 に同梱されているのは現在のところ 2.2 系。

これまで安定版ブランチで RubyGems のバージョンが大きく更新されたことがあったかどうかよく知らないのですが、2013年に RubyGems の security fix (CVE-2013-4363)があって、2.1 系に上げるのではなくて、2.0 系の最新版に更新するパッチを当てるという方針にしました。当時 RubyGems の 2.1 系はかなり不安定で、2.1 に上げるのはトラブルの元であろうというのと、2.0系を RubyGems の安定版としてメンテナンスするという意思を確認して、更新しました。

RubyGems は更新すると test-all が壊れるなどよく問題が起きるので慎重に行きたかったですし、rubygems は別途配布されているので rubygems のみ更新することも可能なので、同梱版は修正せずにおくという方法も考えられましたが、ものが security fix でしたし、更新しなければ結局「Ruby 2.0.0 をインストールしたら rubygems update —system する」というバッドノウハウが蔓延するだけだろうという気がしたので、エンドユーザに余計な手間をかけさせないようにしよう、ということで決断しました。これが正しい判断だったのかどうか、まだよくわからないですね。

ただ RubyGems 2.0 系はなんだかあまりメンテナンスされていないっぽくて、少し先行きが心配です。 2.0.0-p451 には upstream には報告したものの取り込まれなかった修正(2.2 では修正されているコミットをバックポートしたもの)(と書いているうちに zzak がマージしてくれました。 Thank you zzak!!)が入っているので、同梱版は rubygems のリポジトリのものとは少し乖離しています。 [4] [5]

configure.in

configure については、まず trunk では r44034 で autoconf の要求するバージョンを 2.62 から 2.69 に引き上げています(従って 2.1 でも)。サイトからダウンロードできる .tar.bz2 とか .zip のパッケージは configure が生成された状態で入っているので、あんまり関係ないんですけど、svn や github のリポジトリから直接チェックアウトしたソースからビルドするような時には古い autoconf だと動かなくなってしまうので、2.0.0 にはバックポートしていません。この頃メジャーなクラウドサービス上の CentOS などには autoconf 2.69 はデフォルトでは入っていないので、これを入れるとそれまでビルドできていたところで追加のパッケージを入れないとビルドできなくなってしまうと思ったからです。このへんは実際に業務でサーバやミドルウウェアの準備をしている同僚達の苦労をみていて、依存バージョンを上げてしまうとあちこちで追加の作業が発生してしまうなぁということから判断しました。

それから configure.in には一時導入されて結局消えた unexpand_shvar という関数の導入があって、このあたりで 2.69 に依存した書きかたが含まれている可能性があってうかつにバックポートできないという状態があり、結果 trunk と 2.0.0 はかなり乖離している状態です。あと trunk で処理の順序の入れ替えが行なわれていたりして、これも追随できてない(うかつに場所の移動だけ模倣すると順番に依存関係があったりして不具合を入れる可能性がある。実際 trunk でも移動してみたらバグってて追加修正、というのがよくある)ので、configure.in は鬼門です。ksh で動かないとかは、できればなんとかしてあげたいけど、パッチをもらえるとかでないと無理っぽい。もっと言うとパッチもらってもそれが様々な環境でOKか検証できないといけないのでかなりツラい。

Bignum

2.1/trunk(2.2) の bignum.c は Bignum の実装に GMP を使うようにしたというのもありますが、その他にも akr さんによって大規模なリファクタリング、というか作り直しがされていて、もう bignum.c の変更はたいてい簡単にはバックポートできない状態です。というかある変更がバックポートが必要かどうかを判断するのに、その部分がいつ導入されて、それ以前に類似の処理がなかったか…みたいなのを研究しないといけない状態です。

まあ幸い今のところ Bignum に関して重要なバックポート要求はなくて(たまに大きな整数文字列のパースが 2.0.0 では遅い、みたいな報告があったりはしますが、それは 2.1 の new feature なんですよおめでとう、みたいなノリでかわします)、今すでに懸案の issue があるわけではないですが、何かきたら大変そう。

それからこのリファクタリングの副産物として、rb_integer_pack() と rb_integer_unpack() という C API が追加されていて、これを利用した実装になった部分の変更とかもちょっと困るかもしれません。そんなに数はなくて今のところ困ったことはないので問題になる可能性は低そうですけど。

RGenGC にまつわるマクロ追加の対応

RGenGC には write barrier による書き換えの保護のしくみがあるのですが、これがきちんと追加されていない場合にはそのオブジェクトを shady (shady という語彙はやめて non-WB-protected にしよう、という変更がされているので今ソース内で shady という単語は ないと思…あれ、あるな、GPR_FLAG_MAJOR_BY_SHADY とか。まあともかく、shady って使わないほうがいいと思うのですが non-WB-protected って長いのでつい shady って書いちゃいますね)にしてマーク対象にするようにしています。できるだけ shady にしないほうが世代別 GC の恩恵が期待できるので、この対策として RARRAY_PTR() のポインタアクセス用のマクロは参照専用の時には RARRAY_CONST_PTR() とか RARRAY_AREF() といったマクロが導入されています。

もちろんこれが意味をもつのは 2.1 以降なのですが、2.0.0 でも同名のマクロを持っておくと拡張ライブラリの互換性対策にはいいかなと思っていたのですが、そういうのは本体に追加するより拡張ライブラリで使えるヘッダ群として提供するとかのほうがいいんじゃないかという意見もあり入っていません。まあ本体に入れてもパッチレベルの途中から入ってます、というのでは確かに嬉しさは半減しますからねぇ。

まとめ

思いつくままに書き連ねてきたのでなにか思い出したらまた追加します。

立つ鳥跡を濁さずといきたいところですがなかなかそうも行きません。これが現実です。安定版ブランチのメンテというのは非情な現実との泥仕合なので、うまくないところがあったらみなさまからの支援をお待ちしております。

[1] http://www.ruby.or.jp/ja/news/20140122.html

[2] http://shyouhei.tumblr.com/post/682953115/ruby-1-8-7

[3] http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/47051

[4] https://github.com/rubygems/rubygems/pull/833

[5] https://bugs.ruby-lang.org/issues/9224

  1. kjunichinagachikaからリブログしました
  2. nagachikaの投稿です