Bug #3674

dRuby サーバプロセスを停止する時に時間がかかることがある

Added by Tomoyuki Chikanaga almost 4 years ago. Updated about 3 years ago.

Status:Closed
Priority:Normal
Assignee:Masatoshi Seki
Category:lib
Target version:1.9.3
ruby -v:- Backport:

Description

=begin
CentOS release 5.4 Linux のマシンで dRuby のサーバプロセスを停止させる時に CPU 利用した状態で
数秒から十数秒程度時間がかかることがあります。
Redhat Enterprise Linux, Ubuntu 8.10, openSUSE 11.1 等の他の環境で同じものを動かしていますが
今のところこの現象がみられるのは CentOS のところだけのようです。
(CPU の種類/速度や libpthread のバージョンも異なるのでその影響もあるかもしれません)

たとえば以下のようなサンプルでも稀に発生します。

  • server.rb
    require "drb"

    class S
    def m1
    puts "S#m1 called"
    sleep 10
    end
    end

    begin
    front = S.new
    uri = ARGV[0] || "druby://localhost:0"
    puts DRb.start_service(uri, front).uri

    DRb.thread.join
    rescue Interrupt
    ensure
    DRb.stop_service
    end

  • client.rb
    require "drb"

    obj = DRbObject.new_with_uri ARGV[0]
    obj.m1

  • 実行例
    $ ruby server.rb druby://localhost:10000
    druby://localhost:10000
    S#m1 called
    <- Ctrl-C で中断

    (別端末で実行)
    $ ruby client.rb druby://localhost:10000

    発生した時に gdb attach してみると DRbServer#kill_sub_thread で dRuby 要求を処理している
    Thread を kill して回っているところで止まっているようです。
    以下のように Thread.pass を挿入すると発生しなくなりました。

    Index: lib/drb/drb.rb

    --- lib/drb/drb.rb (revision 28880)
    +++ lib/drb/drb.rb (working copy)
    @@ -1421,6 +1421,7 @@
    list.each do |th|
    th.kill if th.alive?
    end

  •     Thread.pass
       list = @grp.list
     end
    end
    

    =end

History

#1 Updated by Tomoyuki Chikanaga almost 4 years ago

=begin
の咳さんの返信にフォローします。

えっと、Rubyのスレッドかなにか問題の報告と考えてよいのでしょうか?

ここにThread.passが必要な理由がわかりません。
私がなにかしたほうがよいのでしょうか?

なにを指摘しているのかあいまいな報告ですみませんでした。

Thread#kill のソース(rb_thread_kill)をみると、特に明示的なスレッドの切り替えを
発生させるようなところがなかったため、
DRbServer#kill_sub_thread の以下の while ループはタイマーで強制的に切り替えが発生するまで
ぐるぐる回り続けるのではないかと考えました。

list = @grp.list
while list.size > 0
list.each do |th|
th.kill if th.alive?
end
list = @grp.list
end

先刻 irb でちょっと実験してみましたが kill で停止した Thread は
実際に終了するまで(?) ThreadGroup に残っているようです。

tg = ThreadGroup.new
=> #ThreadGroup:0x8e7cc20
thr = Thread.new{ tg.add Thread.current; sleep }
=> #
thr.kill; p tg.list
[#]
=> [#]
tg.list
=> []

ということで Thread#kill で停止させた Thread が本当に停止して ThreadGroup から消えるには
明示的なスレッド切り替えが必要なのではないか(充分とは限りませんが)と考えた結果が先のパッチです。
しかし Thread#kill または ThreadGroup の挙動が意図したものでないという可能性もあります。
これはよくわかりません。どうなのでしょうか?
個人的には Thread#kill するたびに切り替えするのは重そうなので現状のほうがいいような気がします。
=end

#2 Updated by Yusuke Endoh almost 4 years ago

=begin
遠藤です。

2010年8月16日9:46 Tomoyuki Chikanaga redmine@ruby-lang.org:

えっと、Rubyのスレッドかなにか問題の報告と考えてよいのでしょうか?

ここにThread.passが必要な理由がわかりません。
私がなにかしたほうがよいのでしょうか?

なにを指摘しているのかあいまいな報告ですみませんでした。

Thread#kill のソース(rb_thread_kill)をみると、特に明示的なスレッドの切り替えを
発生させるようなところがなかったため、
DRbServer#kill_sub_thread の以下の while ループはタイマーで強制的に切り替えが発生するまで
ぐるぐる回り続けるのではないかと考えました。

そうだと思います。 と同じような問題ですね。

ということで Thread#kill で停止させた Thread が本当に停止して ThreadGroup から消えるには
明示的なスレッド切り替えが必要なのではないか(充分とは限りませんが)と考えた結果が先のパッチです。

1.9 では Thread.pass はアーキテクチャによってはあてにならないらしい
( の最後参照) ので、 のように join
した方がいいかと思いました。

--
Yusuke Endoh mame@tsg.ne.jp

=end

#3 Updated by Yusuke Endoh almost 4 years ago

=begin
遠藤です。

2010年8月17日0:00 Masatoshi SEKI :

Thread#joinって最後の値を取り出しますよね。たしか例外で終了した
場合は例外が再びあがるんじゃなかったけ。

はい。

begin
thread.kill.join
rescue なんとかえらー
end

みたいにしたほうがよいものですか?

kill_sub_thread の文脈を理解していないのですが、例外が上がる
可能性があるならそうしたほうがよさそうですね。

あと、join で死ぬのを待つなら while list.size > 0 は要らない
かも。

diff --git a/lib/drb/drb.rb b/lib/drb/drb.rb
index cacca14..cfed15c 100644
--- a/lib/drb/drb.rb
+++ b/lib/drb/drb.rb
@@ -1417,11 +1417,11 @@ module DRb
grp = ThreadGroup.new
grp.add(Thread.current)
list = @grp.list
- while list.size > 0
- list.each do |th|
- th.kill if th.alive?
+ list.each do |th|
+ begin
+ th.kill.join if th.alive?
+ rescue Exception
end
- list = @grp.list
end
end
end

--
Yusuke Endoh mame@tsg.ne.jp

=end

#4 Updated by Tomoyuki Chikanaga almost 4 years ago

=begin
近永です。

ありがとうございます。
この差分でしばらく試してみます。

2010年8月18日4:13 Masatoshi SEKI :

咳といいます。

On 2010/08/16, at 9:46, Tomoyuki Chikanaga wrote:

チケット #3674 が更新されました。 (by Tomoyuki Chikanaga)

の咳さんの返信にフォローします。

えっと、Rubyのスレッドかなにか問題の報告と考えてよいのでしょうか?

ここにThread.passが必要な理由がわかりません。
私がなにかしたほうがよいのでしょうか?

なにを指摘しているのかあいまいな報告ですみませんでした。

Thread#kill のソース(rb_thread_kill)をみると、特に明示的なスレッドの切り替えを
発生させるようなところがなかったため、
DRbServer#kill_sub_thread の以下の while ループはタイマーで強制的に切り替えが発生するまで
ぐるぐる回り続けるのではないかと考えました。

遠藤さんの指摘をもとにこんな風に変更したらどうかと思いました。
もしよかったら、試していただけませんか?

Index: drb.rb

--- drb.rb (revision 29026)
+++ drb.rb (working copy)
@@ -1419,7 +1419,10 @@
list = @grp.list
while list.size > 0
list.each do |th|
- th.kill if th.alive?
+ begin
+ th.kill.join if th.alive?
+ rescue Exception
+ end
end
list = @grp.list
end

=end

#5 Updated by Tomoyuki Chikanaga almost 4 years ago

=begin
近永と申します。

確認が遅くなってすみません。
頂いた修正を加えて、現象が発生していた環境で再現スクリプトを2000回くらい回して起きなくなったことを確認しました。

ただ差分を見ていてちょっと気がついたのですが、kill した Thread に ensure 節の処理があって、
それがブロックする可能性があるものだと、その Thread が終了するまで時間がかかることがあり、
その間は他の Thread が kill されず動き続けると思います。
別に手元のプログラムで問題が発生したというわけではないのですが、
元の動作に近づけるにはまず全体に kill を呼んでおいてから join したほうがいいかなと思いました。

Index: lib/drb/drb.rb
===================================================================
--- lib/drb/drb.rb (revision 29026)
+++ lib/drb/drb.rb (working copy)
@@ -1418,8 +1418,14 @@
grp.add(Thread.current)
list = @grp.list
while list.size > 0
+ list = list.map do |th|
+ th.kill if th.alive?
+ end.compact
list.each do |th|
- th.kill if th.alive?
+ begin
+ th.join
+ rescue Exception
+ end
end
list = @grp.list
end

=end

#6 Updated by Shyouhei Urabe almost 4 years ago

  • Status changed from Open to Assigned
  • Assignee set to Masatoshi Seki

=begin

=end

#7 Updated by Yui NARUSE over 3 years ago

=begin
これってどうなってますか?
=end

#8 Updated by Tomoyuki Chikanaga over 3 years ago

=begin
えっと、わたしとしてはローカルで上記のパッチをあててずっと運用していて、うちの用途では問題もないので適用していただきたいと思います。

=end

#9 Updated by Yui NARUSE over 3 years ago

  • Target version set to 1.9.3

=begin

=end

#10 Updated by Yusuke Endoh over 3 years ago

=begin
遠藤です。

2010年11月15日22:55 Tomoyuki Chikanaga redmine@ruby-lang.org:

えっと、わたしとしてはローカルで上記のパッチをあててずっと運用していて、うちの用途では問題もないので適用していただきたいと思います。

なんか途中投げにした格好ですみません。私も近永さんのパッチに賛成です。

--
Yusuke Endoh mame@tsg.ne.jp

=end

#11 Updated by Koichi Sasada about 3 years ago

これは,咳さん待ちということになるんでしょうか.

#12 Updated by Motohiro KOSAKI about 3 years ago

http://d.hatena.ne.jp/nagachika/20110613/ruby_trunk_changes_32028_32070r32028 の解説を読むと、すでに直っているように読めますが、咳さんの認識はいかがでしょうか?

#13 Updated by Tomoyuki Chikanaga about 3 years ago

  • Category set to lib
  • Status changed from Assigned to Closed

kill_sub_thread はなくなったので閉じます。

#14 Updated by Tomoyuki Chikanaga about 3 years ago

  • ruby -v changed from ruby 1.9.3dev (2010-08-05 trunk 28840) [i686-linux] to -

近永と申します。

失礼しました、チケットは閉じておきました。

そういえば

ruby 1.9.3dev (2011-06-12 trunk 32027) [x86_64-darwin10.7.0]

ころのRubyではdRuby+forkするスクリプトを書いてたら、終了時に
暴走することが多かったです。
再現させられなかったので不確かですが、r32064 で kosaki さんが
GVL の変更をしてスレッド間の切り替えのしかたが変化したので
もしかすると最新の trunk だと起きなくなっているかもしれません。

もしよろしかったら最新版でも試してみていただけますか?

Also available in: Atom PDF