Bug #4072
closeddRubyで作成したサーバプログラムがsleepしていてもexitしてしまう
Description
=begin
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]にて、
dRubyを以下のようなserverとclient作成し、server->clientを実行すると、
serverプログラムが例外の表示もなくexitします。
ruby 1.9.3dev (2010-11-19 trunk 29830) [x86_64-darwin10.5.0]でも同様に発生します。
ruby 1.8.7 (2010-08-16 patchlevel 302) [i686-darwin10.4.0]では、
exitされないことを確認しております。
https://gist.github.com/706260
- serverプログラム
 require 'drb'
class Hello
def hello(message)
puts message
end
end
begin
DRb.start_service('druby://:12346', Hello.new)
puts DRb.uri
sleep
rescue Object, SystemExit=> e
p e.backtrace
raise e
rescue => e
puts e
end
- clientプログラム
 require 'drb'
 d = DRbObject.new_with_uri('druby://m-mimura-4.local:12346')
 d.hello "message"
 =end
Files
        
           Updated by takkanm (三村 益隆) almost 15 years ago
          Updated by takkanm (三村 益隆) almost 15 years ago
          
          
        
        
      
      =begin
すみません。他の環境で実行したところ問題無いようでした。
わたしの環境依存っぽいようなので、チケットの破棄をお願いしたいです。
=end
        
           Updated by ujihisa (Tatsuhiro Ujihisa) almost 15 years ago
          Updated by ujihisa (Tatsuhiro Ujihisa) almost 15 years ago
          
          
        
        
      
      =begin
こちらの環境でも再現できました。大抵はメッセージの送信に成功するものの、ときどき失敗し、クライアント側に下記のエラーを出力してサーバが終了します。
/Users/ujihisa/git/ruby192/local/lib/ruby/1.9.1/drb/drb.rb:736:in rescue in block in open': druby://localhost:12346 - #<Errno::ECONNREFUSED: Connection refused - connect(2)> (DRb::DRbConnError) from /Users/ujihisa/git/ruby192/local/lib/ruby/1.9.1/drb/drb.rb:730:in block in open'
from /Users/ujihisa/git/ruby192/local/lib/ruby/1.9.1/drb/drb.rb:729:in each' from /Users/ujihisa/git/ruby192/local/lib/ruby/1.9.1/drb/drb.rb:729:in open'
from /Users/ujihisa/git/ruby192/local/lib/ruby/1.9.1/drb/drb.rb:1191:in initialize' from /Users/ujihisa/git/ruby192/local/lib/ruby/1.9.1/drb/drb.rb:1171:in new'
from /Users/ujihisa/git/ruby192/local/lib/ruby/1.9.1/drb/drb.rb:1171:in open' from /Users/ujihisa/git/ruby192/local/lib/ruby/1.9.1/drb/drb.rb:1087:in block in method_missing'
from /Users/ujihisa/git/ruby192/local/lib/ruby/1.9.1/drb/drb.rb:1105:in with_friend' from /Users/ujihisa/git/ruby192/local/lib/ruby/1.9.1/drb/drb.rb:1086:in method_missing'
from c.rb:4:in `'
ただし実験に用いたrubyは ruby 1.9.2p14 (2010-10-02 revision 29393) [x86_64-darwin10.4.1] です。
=end
        
           Updated by takkanm (三村 益隆) almost 15 years ago
          Updated by takkanm (三村 益隆) almost 15 years ago
          
          
        
        
      
      =begin
三村です。
2010年11月20日7:37 Masatoshi SEKI m_seki@mva.biglobe.ne.jp:
咳といいます。
サーバが終了してしまうのは、DRb.uriをputsした後でしょうか > 三村さん
クライアント側がhelloでメッセージを送信し、サーバ側でメッセージを表示後にsleepが
起きているように見えています。
sleepがシグナル等で置きているのではないかという指摘を受けているのですが、
その可能性があるということでしょうか?
=end
        
           Updated by nagachika (Tomoyuki Chikanaga) almost 15 years ago
          Updated by nagachika (Tomoyuki Chikanaga) almost 15 years ago
          
          
        
        
      
      =begin
近永と申します。
trunk で追試してみて発生しないなーと思っていたのですが、
手元で #4027 のチケットに添付したシグナルハンドラ絡みのパッチを適用していたためでした。
パッチを巻き戻してみたところ再現しました。
ruby -v は ruby 1.9.3dev (2010-11-19 trunk 29831) [x86_64-darwin10.5.0] です。
message メソッド実行後に メインスレッドが sleep から抜けてきているようです。
必ず起きるというわけではなくて、何回かは message を呼べることもありました。
なお sleep のかわりに DRb.thread.join にすれば大丈夫でした。
ruby に -d オプションをつけて実行してみたところ、DRbMessage#send_reply で Errno::EPIPE が
発生している時に sleep から抜けているようです。
おそらく SIGPIPE のシグナルハンドラ(sigpipe) がメインスレッドで実行されてしまった時に
pthread_cond_wait から戻り値=0 errno=EINTR で抜けています。
シグナルハンドラが必ずタイマースレッドで実行されるようになれば直ると思います。
しかし前述のパッチをあてた版では逆に message を実行後に SIGINT や SIGTERM で終了しなくなって
しまっているのでまだ問題がありそうです。
=end
        
           Updated by nagachika (Tomoyuki Chikanaga) over 14 years ago
          Updated by nagachika (Tomoyuki Chikanaga) over 14 years ago
          
          
        
        
      
      - Category set to core
- Status changed from Open to Closed
これですがおそらく r31482 のあたりで native_cond_wait() が EINTR でリトライするようになったので
直っているのではないかと思います。
手元では ruby 1.9.2dev (2010-05-10 revision 27709) で再現していたのが trunk では起きなくなっていました。
ruby_1_9_2 にもマージ済みなので 1.9.2-head でも同じではないかと思います(未確認ですが)。
というわけでcloseさせて頂きます。もし再現するようだったらreopenさせてください。
        
           Updated by kosaki (Motohiro KOSAKI) over 14 years ago
          Updated by kosaki (Motohiro KOSAKI) over 14 years ago
          
          
        
        
      
      いま、Macのpthread_cond_wait()見てるんですが、
http://www.opensource.apple.com/source/Libc/Libc-594.9.4/pthreads/pthread_cond.c
/*
- Suspend waiting for a condition variable.
- Note: we have to keep a list of condition variables which are using
- this same mutex variable so we can detect invalid 'destroy' sequences.
- If isconforming < 0, we skip the _pthread_testcancel(), but keep the
- remaining conforming behavior..
 */
 private_extern int
 _pthread_cond_wait(pthread_cond_t *cond,
 pthread_mutex_t *mutex,
 const struct timespec abstime,
 int isRelative,
 int isconforming)
 {
 (snip)
 } else {
 if (wait_res < 0) {
 if (errno == ETIMEDOUT) {
 return ETIMEDOUT;
 } else if (errno == EINTR) {
 /
 ** EINTR can be treated as a spurious wakeup unless we were canceled.
 */
 return 0;
 }
 return EINVAL;
 }
 return 0;
 }
 }
と、関数の最後でEINTRを0に差し替える処理があるので、EINTRでリトライうんぬんは違うんじゃないかなぁ。
あと、sleepの実体であるsleep_timeval()は1.9.2の時代からすでに、最初に時間を測定しておいて、起きてくるたびに
時間を再測定、早く起床しすぎたと思ったらもう一度寝る。というロジックなので戻り値にはあんまり
依存してないはず。
まあ、再現しないなら閉じること自体には反対しませんが、気になったので。
        
           Updated by nagachika (Tomoyuki Chikanaga) over 14 years ago
          Updated by nagachika (Tomoyuki Chikanaga) over 14 years ago
          
          
        
        
      
      そうですね、EINTR でのリトライじゃなく SIGPIPE が SIG_DFL になったことの影響かもしれません。
        
           Updated by nagachika (Tomoyuki Chikanaga) over 14 years ago
          Updated by nagachika (Tomoyuki Chikanaga) over 14 years ago
          
          
        
        
      
      何度もすみません。SIG_DFL じゃなくて SIG_IGN ですね。
あと引数なしの sleep の場合は sleep_forever() で眠りますが、こちらは deadlockable 引数が 0 だと while の条件部が抜けるようになっているのでそのため一度起床するとそのまま抜けてしまってたようです。
        
           Updated by nagachika (Tomoyuki Chikanaga) over 14 years ago
          Updated by nagachika (Tomoyuki Chikanaga) over 14 years ago
          
          
        
        
      
      - Status changed from Closed to Open
すみません、調査不足でした。再度 open します。
kosaki さんのご指摘の通り引数なしの Kernel#sleep がシグナルによって起きてしまうという問題は残っていました。 trunk で以下のように空のシグナルハンドラを設定して sleep すると darwin10.7.0 では SIGINT で sleep から目覚めてしまいました。
Signal.trap(:INT){}
sleep
これは spurious wakeup 対策不足ということだと思うので直したほうが良いと思います。
while の条件部を変更すれば良いかと思って修正してみたのですが、RUBY_VM_CHECK_INTS() を抜けた後で th->status が THREAD_RUNNABLE になっていることがあるようでまだ終了することがありました。
        
           Updated by mame (Yusuke Endoh) over 14 years ago
          Updated by mame (Yusuke Endoh) over 14 years ago
          
          
        
        
      
      - ruby -v changed from ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0] to -
遠藤です。
2011年6月24日14:43 KOSAKI Motohiro kosaki.motohiro@jp.fujitsu.com:
ところで、話は変わるのですが thread.stopも process.sleep の「thread.run するまで
スリープする」と書いて有るのにシグナル受信したときの挙動が違うのはなぜなんでしょうね?
deadlock check作った遠藤さんに聞くべきなのかも知れませんが。
文脈を全く理解してないんですが、Thread.stop が deadlock 検出して Kernel#
sleep がしないのはなぜか、という話?
Thread.stop は別スレッドから Thread#run されるのを待つもの (なので他の
スレッドも止まったらデッドロック) 、Kernel#sleep は別プロセスから Ctrl-C
などでシグナルを受けるのを待つもの (なので他のスレッドが生きてようと死んで
ようとデッドロックではない) 、という用途の違いを意識してそうしました。
[ruby-dev:35330] の前後にそんな感じの話題がちょっとだけあります。
全然違う話だったら無視してください。
--
Yusuke Endoh mame@tsg.ne.jp
        
           Updated by mame (Yusuke Endoh) over 14 years ago
          Updated by mame (Yusuke Endoh) over 14 years ago
          
          
        
        
      
      遠藤です。
2011年6月24日14:43 KOSAKI Motohiro kosaki.motohiro@jp.fujitsu.com:
ところで、話は変わるのですが thread.stopも process.sleep の「thread.run するまで
スリープする」と書いて有るのにシグナル受信したときの挙動が違うのはなぜなんでしょうね?
deadlock check作った遠藤さんに聞くべきなのかも知れませんが。
文脈を全く理解してないんですが、Thread.stop が deadlock 検出して Kernel#
sleep がしないのはなぜか、という話?
Thread.stop は別スレッドから Thread#run されるのを待つもの (なので他の
スレッドも止まったらデッドロック) 、Kernel#sleep は別プロセスから Ctrl-C
などでシグナルを受けるのを待つもの (なので他のスレッドが生きてようと死んで
ようとデッドロックではない) 、という用途の違いを意識してそうしました。
[ruby-dev:35330] の前後にそんな感じの話題がちょっとだけあります。
全然違う話だったら無視してください。
--
Yusuke Endoh mame@tsg.ne.jp
        
           Updated by nagachika (Tomoyuki Chikanaga) over 14 years ago
          Updated by nagachika (Tomoyuki Chikanaga) over 14 years ago
          
          
        
        
      
      - File sleep_forever.patch sleep_forever.patch added
RUBY_VM_CHECK_INTS() を抜けた後で th->status が THREAD_RUNNABLE になっていることがあるようで
この原因は rb_threadptr_check_signal() でタイマースレッドがメインスレッドの status を変更しているためではないかと思います。
mth->status = THREAD_RUNNABLE にセットしてから rb_threadptr_interrupt() でメインスレッドへシグナル送信して元の status に戻していますが、戻す前にメインスレッドが rb_threadptr_execute_interrupts_rec() 内で th->status のバックアップを取ってしまうと誤って THREAD_RUNNABLE に遷移してしまいます。
status の変更は割り込みを受けとったスレッドでやっているので rb_threadptr_check_signal() では必要ないのではないかと思います。
添付のパッチのように変更すると sleep から起きないようになりました。どうでしょうか。
        
           Updated by kosaki (Motohiro KOSAKI) over 14 years ago
          Updated by kosaki (Motohiro KOSAKI) over 14 years ago
          
          
        
        
      
      - Status changed from Open to Assigned
- Assignee set to matz (Yukihiro Matsumoto)
kosakiです
unblock functionはGVL持たずに呼ばれるので、そこがthread stateによって挙動を変えていたら「死ねばいいのに」レベルだと思っています。よってわたし的にはこのパッチはOK。
ただIRCで聞いたところによると mameさんの意識は
ruby -e 'trap(:INT) { }; sleep'
の時もsleepは解除されるという仕様だったそうです。ここの仕様はまつもとさんに確認
する必要があると思います。
まつもとさん、どう思いますでしょうか?
以下余談。
個人的な感想をいうと、わたしはnagachikaさんの提案している仕様が好きで、
もし、trapでsleep解除という仕様にするなら、現状のシグナルハンドラ実行を契機に
sleep解除はよくなくて、trapハンドラ実行を契機にsleep解除されなければ
いけないと考えます。
スクリプトでタイマースレッドはシグナル受信したけど、まだメインスレッドには配送¶
されてないので、スクリプトからは不可視。という状況を意識しないといけないのは¶
コレジャナイ感があります¶
        
           Updated by kosaki (Motohiro KOSAKI) over 14 years ago
          Updated by kosaki (Motohiro KOSAKI) over 14 years ago
          
          
        
        
      
      遠藤です。
2011年6月24日14:43 KOSAKI Motohiro kosaki.motohiro@jp.fujitsu.com:
ところで、話は変わるのですが thread.stopも process.sleep の「thread.run するまで
スリープする」と書いて有るのにシグナル受信したときの挙動が違うのはなぜなんでしょうね?
deadlock check作った遠藤さんに聞くべきなのかも知れませんが。文脈を全く理解してないんですが、Thread.stop が deadlock 検出して Kernel#
sleep がしないのはなぜか、という話?Thread.stop は別スレッドから Thread#run されるのを待つもの (なので他の
スレッドも止まったらデッドロック) 、Kernel#sleep は別プロセスから Ctrl-C
などでシグナルを受けるのを待つもの (なので他のスレッドが生きてようと死んで
ようとデッドロックではない) 、という用途の違いを意識してそうしました。
[ruby-dev:35330] の前後にそんな感じの話題がちょっとだけあります。全然違う話だったら無視してください。
返信が遅くてすいません。遠藤さんとぐぐる先生のご尽力によりこのへんの仕様は
だいぶクリアーになりました。
現状不明確なのは sigpipe のようなインタプリタ内部で握りつぶしているシグナルや
trap(:INT) { } のような Rubyレベルで握りつぶしている場合もwakeupしてくるが
これは仕様か。という点だけだと思います。
なんとなく、例外だけにしたほうがいい気がするんですよ。Unixシグナルが存在しない
Windows とか JRubyが困るんじゃないかと
        
           Updated by kosaki (Motohiro KOSAKI) over 14 years ago
          Updated by kosaki (Motohiro KOSAKI) over 14 years ago
          
          
        
        
      
      遠藤です。
2011年6月24日14:43 KOSAKI Motohiro kosaki.motohiro@jp.fujitsu.com:
ところで、話は変わるのですが thread.stopも process.sleep の「thread.run するまで
スリープする」と書いて有るのにシグナル受信したときの挙動が違うのはなぜなんでしょうね?
deadlock check作った遠藤さんに聞くべきなのかも知れませんが。文脈を全く理解してないんですが、Thread.stop が deadlock 検出して Kernel#
sleep がしないのはなぜか、という話?Thread.stop は別スレッドから Thread#run されるのを待つもの (なので他の
スレッドも止まったらデッドロック) 、Kernel#sleep は別プロセスから Ctrl-C
などでシグナルを受けるのを待つもの (なので他のスレッドが生きてようと死んで
ようとデッドロックではない) 、という用途の違いを意識してそうしました。
[ruby-dev:35330] の前後にそんな感じの話題がちょっとだけあります。全然違う話だったら無視してください。
返信が遅くてすいません。遠藤さんとぐぐる先生のご尽力によりこのへんの仕様は
だいぶクリアーになりました。
現状不明確なのは sigpipe のようなインタプリタ内部で握りつぶしているシグナルや
trap(:INT) { } のような Rubyレベルで握りつぶしている場合もwakeupしてくるが
これは仕様か。という点だけだと思います。
なんとなく、例外だけにしたほうがいい気がするんですよ。Unixシグナルが存在しない
Windows とか JRubyが困るんじゃないかと
        
           Updated by matz (Yukihiro Matsumoto) over 14 years ago
          Updated by matz (Yukihiro Matsumoto) over 14 years ago
          
          
        
        
      
      まつもと ゆきひろです
In message "Re: [ruby-dev:43895] [Ruby 1.9 - Bug #4072][Assigned] dRubyで作成したサーバプログラムがsleepしていてもexitしてしまう"
on Sat, 25 Jun 2011 09:20:27 +0900, Motohiro KOSAKI kosaki.motohiro@gmail.com writes:
|ただIRCで聞いたところによると mameさんの意識は
|
|ruby -e 'trap(:INT) { }; sleep'
|
|の時もsleepは解除されるという仕様だったそうです。ここの仕様はまつもとさんに確認
|する必要があると思います。
|
|まつもとさん、どう思いますでしょうか?
とくにこだわりはなくて、たぶん偶然そのような挙動になっていた
のだと思います。ので、直すことに賛成します。
        
           Updated by matz (Yukihiro Matsumoto) over 14 years ago
          Updated by matz (Yukihiro Matsumoto) over 14 years ago
          
          
        
        
      
      まつもと ゆきひろです
In message "Re: [ruby-dev:43895] [Ruby 1.9 - Bug #4072][Assigned] dRubyで作成したサーバプログラムがsleepしていてもexitしてしまう"
on Sat, 25 Jun 2011 09:20:27 +0900, Motohiro KOSAKI kosaki.motohiro@gmail.com writes:
|ただIRCで聞いたところによると mameさんの意識は
|
|ruby -e 'trap(:INT) { }; sleep'
|
|の時もsleepは解除されるという仕様だったそうです。ここの仕様はまつもとさんに確認
|する必要があると思います。
|
|まつもとさん、どう思いますでしょうか?
とくにこだわりはなくて、たぶん偶然そのような挙動になっていた
のだと思います。ので、直すことに賛成します。
        
           Updated by nagachika (Tomoyuki Chikanaga) over 14 years ago
          Updated by nagachika (Tomoyuki Chikanaga) over 14 years ago
          
          
        
        
      
      - Status changed from Assigned to Closed
- % Done changed from 0 to 100
This issue was solved with changeset r32226.
三村, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.
- thread.c (sleep_forever): now Kernel#sleep don't wakeup by
 signal handler execution. [Bug #4072]