ruby-trunk-changes r38382 - r38410

今日は主に IO のブロック処理(GVL を外すところ)の方針の変更や書き込み用のロックの recursive lock 対策、シグナルハンドラ用の専用マシンスタックのサイズの調節などがありました。

kosaki:r38382 2012-12-15 03:08:40 +0900

rb_thread_polling() とそこで使われている sleep_for_polling() という関数を削除して、rb_file_flock() で flock が EAGAIN や EWOULDBLOCK を返した時に rb_thread_polling() を利用していたのをその場で rb_thread_wait_for() を呼んで sleep するようにしています。 rb_thread_polling() は他に Thread がいる時だけ sleep しますが flock() が EAGAIN を返した(ロックされてた)時は Thread に関係なく待つ必要があるので rb_thread_polling() を使っていたのは間違っていました。 ただ rb_thread_polling() の削除は r38391 で revert されています。

ngoto:r38383 2012-12-15 03:47:02 +0900

ext/.document に fiddle/pointer.c, fiddle/handle.c, fiddle/win32/lib など dl からコピーしてできたファイル名を追記しています。

nobu:r38384 2012-12-15 03:59:12 +0900

r38371 でコンパイラの最適化避け(?)のために、理由はわからないけど追加したら直ったという変数宣言が未使用変数の警告を出していたので RB_UNUSED_VAR() を使って抑制しようとしています。

kosaki:r38385 2012-12-15 04:25:29 +0900

include/ruby/intern.h から rb_thread_polling() の宣言を消した、と ChangeLog にだけ書いてあって実際には消されてないみたいです。

drbrain:r38386 2012-12-15 06:18:00 +0900

mingw32 の環境で __sync_val_compare_and_swap がなくて ATOMIC_CAS() が定義できなくてコンパイルエラーになるのを、gcc 拡張のチェックを linux に限定していたので os をチェック対象から外して mingw32 でも利用できるように configure を修正しています。 [ruby-core:50424] [Bug #7485]

kosaki:r38387 2012-12-15 08:11:26 +0900

test/ruby/test_thread.rb で利用する Thread を継承した TestThread::Thread で Thread#abort_on_exception *1の変更を消しています。グローバルな変更をするべきではない、ということなのですが Thread.abort_on_exception= (クラスメソッド)は全ての Thread の挙動を変えますが Thread#abort_on_exception= (インスタンスメソッド)はその Thread のみ指定するので global flag というのとはちょっと違うかも。デフォルトの挙動でテストすべきというのはわかりますが。

kosaki:r38388 2012-12-15 08:11:36 +0900

Thread のテスト test/ruby/test_thread.rb に存在していた ThreadGroup に関するテストを test/ruby/test_threadgroup.rb に切り出しています。また ThreadGroup のテストクラス内に定義されているにもかかわらず ThreadGroup に関連しないテストメソッドは TestThread に移動(ファイルはそのまま)しています。
また Thread#join による後始末の追加をしています。

nobu:r38389 2012-12-15 08:14:21 +0900

r38388 で追加されたファイルの svn property 設定。

tarui:r38390 2012-12-15 08:40:02 +0900

r38283 で追加された RubyGems の拡張ライブラリのビルド方法の cmake を使う builder のテストで cmake のディレクトリ移動のメッセージを誤検出するのを防いでいるようです。

nobu:r38391 2012-12-15 10:22:44 +0900

r38382 で削除した rb_thread_polling() は復活させて、ただし deprecated として宣言するようにしています。

nobu:r38392 2012-12-15 10:39:43 +0900

r38388 と r38390 の ChangeLog エントリの typo 修正およびフォーマットの修正。

nobu:r38393 2012-12-15 10:39:47 +0900

知らなかったのですが実はビルド時に GNUmakefile というファイルが生成されていてそこから Makefile が include されているのですが、そこに override MFLAGS で -jN オプションが消されるようにしています。並列ビルドで時々問題が出るので無効にしているってことでしょうか。

nobu:r38394 2012-12-15 10:39:49 +0900

configure で環境毎の「なにもしないコマンド」(現状は ":" か "true" を試す)を検出するのに if の分岐をネストさせていたのをループでチェックするようにリファクタリングしています。

kosaki:r38395 2012-12-15 14:39:25 +0900

rb_thread_wait_fd_rw() で rb_thread_alone() を呼んでメインスレッドのみの時に rb_wait_for_single_fd() を呼んで poll(2) などで fd のイベント待ちをせずにすぐに返って(そしておそらく block するシステムコールなどを呼ぶ)いたのを削除しています。 Ruby の Thread まわりはまだまだ色々残ってますねー。

kosaki:r38396 2012-12-15 14:39:36 +0900

rb_io_wait_writable() で errno=EINTR の時に rb_thread_fd_writable() を呼んでいたのを消しています。 EINTR はシステムコールがシグナルで割り込まれたということなのですぐに再実行して良いので。

kosaki:r38397 2012-12-15 14:39:46 +0900

というわけで r38396 で rb_io_wait_writable の errno=EINTR の時に rb_thread_fd_writable() を消しましたが、かわりに rb_thread_wait_fd() を呼んで読み込み可能になるまで待つようにしています。うーんこれはなぜなんでしょう。

kosaki:r38398 2012-12-15 14:39:57 +0900

io.c に internal_write_func2() という関数を追加しています。 これは rb_thread_call_without_gvl2() という割り込みチェックをさきにして割り込まれてたら GVL 解放した処理に入らないようにする API を利用するようにしたものです。 そして IO#write はこれを利用するように変更されています。 この変更はシグナルのトラップハンドラ内で IO を使うと(puts など)メインスレッドで出力のため IO の個別の描き込みロックを取得した状態でトラップハンドラに入り、そこで再度同じ IO に対して書き込みをしようとして recursive lock で例外発生してしまうという問題の対処みたいです。割り込みを検出したら FALSE(0) でかえり、呼び元で一旦 write_lock を解放して割り込み処理するので、 write_lock 保有したままトラップハンドラにら入らないようになります。 [ruby-core:47880] [Bug #7134]

kosaki:r38399 2012-12-15 14:40:07 +0900

io_flush_buffer_async2() という関数も追加しています。これも rb_thread_call_without_gvl2() を使うようにした io_flush_buffer_async() の代替です。 そして io_flush_buffer() で io_flush_buffer_async2() を使うように変更しています。

kosaki:r38400 2012-12-15 14:40:18 +0900

io_flush_buffer() で rb_io_t::write_lock のロック取得を既に自分が取得済みだったらスキップするようにしています。 これが [ruby-core:47880] [Bug #7134] のシグナルトラップハンドラでの IO の recursive lock 問題の直接の対処だと思われます。
まだ IO の finalize 時に recursive lock が発生する可能性があることが ChangeLog には記述されています。

kosaki:r38401 2012-12-15 14:40:29 +0900

IO の finalizer で呼ばれる finish_writeconv() でも rb_io_t::write_lock を取得済みだったら rb_write_internal2() を使うようにしています。これで r38400 の ChangeLog に書かれてた IO の finalzer からの recursive lock も回避しているということかなと思いますが、ロック取得していない時に rb_write_internal() を使う理由がよくわかりません。そもそも rb_write_internal() は write_lock を取得しないように思えるのでここで recursive lock になるのかなぁ。 ChangeLog に書かれてた関数呼び出しのツリーと少し違うのでそれとは別の変更なのかもしれません。
[追記]コメント欄も参照。ここで GVL を解放するとそこでトラップハンドラが呼ばれるかもしれなくて、そこで同じ IO に書き込みしようとして recursive lock になりうるということでした。[/追記]

kosaki:r38402 2012-12-15 14:40:41 +0900

r38398 の ChangeLog エントリの typo 修正。

nobu:r38403 2012-12-15 15:10:01 +0900

parse.y のインデント修正のみ。

nobu:r38404 2012-12-15 15:10:04 +0900

rb_binding_new_with_cfp() でオブジェクトの確保を cfp == 0 のチェック(例外が発生する)の後にして無駄なオブジェクト生成をしないようにしています。

kazu:r38405 2012-12-15 16:18:07 +0900

r38388 の ChangeLog エントリの typo 修正。

nobu:r38406 2012-12-15 16:29:43 +0900

RubyGems のテストで http_proxy などの環境変数を破壊的に変更していたのを後で復旧するようにしています。

tadf:r38407 2012-12-15 18:01:22 +0900

rational.c の float_rationalize() で同じ計算を繰り返していたところを結果を変数に格納して使い回すようにするリファクタリングをしています。

tadf:r38408 2012-12-15 18:25:13 +0900

rational.c の f_round_common() で指数計算した結果のオーバーフローのチェックを追加しています。

kosaki:r38409 2012-12-15 23:20:12 +0900

Ruby はシグナルハンドラ(C レベルの sighandler(2) で登録するほう)に可能なら sigaltstack(2) で専用のマシンスタックを利用させているのですが、そのサイズの計算を ALT_STACK_SIZE ではなく rb_sigaltstack_size() という関数を追加して最低でも 8192 は確保するようにしています。 Linux の MINSIGSTKSZ は 2048 で、これでは SIGSEGV の処理中にスタックオーバフローして再度 SEGV になってしまうという問題があったようです。 [ruby-dev:46213] [Bug #7141]

kosaki:r38410 2012-12-15 23:20:24 +0900

signal.c の default_handler() で SIGSEGV のハンドラを再設定する時に rb_register_sigaltstack() を呼んでいたのを削除しています。メインスレッドは Init_signal() で、その他のスレッドは thread_start_func_2() でスレッド生成する時に呼ぶので再度呼ぶ必要はありませんでした。

*1:通常はメインスレッド以外の Thread が例外などで停止してもプロセス全体は影響を受けないのですが、これを true にしておくと全体が異常終了する