今回の話は Ruby のコア開発の話で一般ユーザーには関係ありません。
ruby の make test-all で、 test/drb/drbtest.rb がファイルを読み込まれた時にスレッドを起動して、 関係ないテストの時にもスレッドが動きっぱなしで CI での SEGV などの時に余計な情報が出ていたので、 テストの前後で実行・停止するように変更しました。
対象バージョン
- ruby 2.6.0dev (2018-10-20)
背景
Ruby の CI でたまにバックトレースが出る時に rinda や drb のスレッドが出ていて気になっていて、 rinda の方は 以前に止めた のですが、 意外と時間がかかったので、 drb の方は保留にしていたので、 Ruby 開発合宿 (日帰り) のタイミングで対処することにしました。
方針
lib の方はできるだけいじらずに test の方だけ変更するという方針で対応しました。
ただし、 remove_server で @primary_server を戻す のは lib の方を直さないと regist_server での再登録がちゃんとできないので入れました。
@queue.pop の返り値で @thread が止まるようにする のも manager.instance_variable_get(:@thread)&.kill で無理やり止めるより、 manager.instance_variable_get(:@queue)&.push(nil) で止めた方が安全そうと思って入れました。
instance_variable_get はすでに他のインスタンス変数へのアクセスに使われていたので、ここで使っても問題ないと思って使っています。
drb の動き
druby://localhost:8787 のような URI で表されるホストのポートで待ち受けて、 サイズ + Marshal したオブジェクトをやりとりして、 リモートのメソッド呼び出しをします。
直接 Marshal できないオブジェクトは DRb::DRbObject でラップされて参照として渡されます。
extservm の動き
まず lib/drb/extserv.rb に DRb::ExtServ が、 lib/drb/extservm.rb に DRb::ExtServManager があります。
ExtServManager が親プロセス側で DRb::ExtServ を使う子プロセス (テストの場合は test/drb/ut_*.rb) を起動して、 子プロセス側から親プロセスの DRb::ExtServManager#regist を呼び出して使えるようになったことを通知して、 親プロセス側から使い、 子プロセスは DRb::ExtServManager#unregist で登録解除してから終了、 という流れになっています。
DRbTests::DRbService の動き
test/drb/drbtest.rb の DRbTests::DRbService で DRb::ExtServManager を管理しています。
まずファイルを読み込まれた時点で add_service_command でサービス名と実行するファイルの対応を登録します。 DRb::ExtServManager.command はグローバルなハッシュなので、 test/drb/test_drbssl.rb と test/drb/test_drbunix.rb で登録される先も同じです。
initialize で DRb::ExtServManager のインスタンスを作成して、 start で DRb::ExtServManager#regist などをうけるための DRb::DRbServer を作成しています。 サブクラスで drbssl や drbunix にできるように initialize の中で直接作成せずに別メソッドに切り出しています。
ext_service で実際に子プロセスを起動して regist 待ちをしています。
最後に finish で後始末をしています。
solaris での問題
変更を入れてみたところ、 Solaris だけで CI が失敗していました。
unstable11s は ext_service での regist 待ちがタイムアウトしています。
unstable11x と unstable10x はサイズの読み込みの read で BUG になって落ちています。