今回の話は 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 になって落ちています。