Bug #4072

dRubyで作成したサーバプログラムがsleepしていてもexitしてしまう

Added by 三村 益隆 over 4 years ago. Updated almost 4 years ago.

[ruby-dev:42601]
Status:Closed
Priority:Normal
Assignee:Yukihiro Matsumoto
ruby -v:- Backport:

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

sleep_forever.patch Magnifier (1.23 KB) Tomoyuki Chikanaga, 06/24/2011 11:10 PM


Related issues

Related to Ruby trunk - Bug #4027: Signal.trap で busy loop に陥る Rejected 05/23/2011

Associated revisions

Revision 32225
Added by Tomoyuki Chikanaga almost 4 years ago

  • thread.c (rb_threadptr_check_signal): remove unnecessary th->status backup. fix race condition which may results unexpected main thread's status transition. see #4072

Revision 32225
Added by Tomoyuki Chikanaga almost 4 years ago

  • thread.c (rb_threadptr_check_signal): remove unnecessary th->status backup. fix race condition which may results unexpected main thread's status transition. see #4072

Revision 32226
Added by Tomoyuki Chikanaga almost 4 years ago

  • thread.c (sleep_forever): now Kernel#sleep don't wakeup by signal handler execution. [Bug #4072]

Revision 32226
Added by Tomoyuki Chikanaga almost 4 years ago

  • thread.c (sleep_forever): now Kernel#sleep don't wakeup by signal handler execution. [Bug #4072]

History

#1 Updated by 三村 益隆 over 4 years ago

=begin
すみません。他の環境で実行したところ問題無いようでした。
わたしの環境依存っぽいようなので、チケットの破棄をお願いしたいです。
=end

#2 Updated by ujihisa . over 4 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

#3 Updated by 三村 益隆 over 4 years ago

=begin
三村です。
2010年11月20日7:37 Masatoshi SEKI :

咳といいます。
サーバが終了してしまうのは、DRb.uriをputsした後でしょうか > 三村さん
クライアント側がhelloでメッセージを送信し、サーバ側でメッセージを表示後にsleepが
起きているように見えています。

sleepがシグナル等で置きているのではないかという指摘を受けているのですが、
その可能性があるということでしょうか?

=end

#4 Updated by Tomoyuki Chikanaga over 4 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

#5 Updated by Tomoyuki Chikanaga almost 4 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させてください。

#6 Updated by Motohiro KOSAKI almost 4 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..
*/
_privateextern
_ 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の時代からすでに、最初に時間を測定しておいて、起きてくるたびに
時間を再測定、早く起床しすぎたと思ったらもう一度寝る。というロジックなので戻り値にはあんまり
依存してないはず。

まあ、再現しないなら閉じること自体には反対しませんが、気になったので。

#7 Updated by Tomoyuki Chikanaga almost 4 years ago

そうですね、EINTR でのリトライじゃなく SIGPIPE が SIG_DFL になったことの影響かもしれません。

#8 Updated by Tomoyuki Chikanaga almost 4 years ago

何度もすみません。SIG_DFL じゃなくて SIG_IGN ですね。

あと引数なしの sleep の場合は sleep_forever() で眠りますが、こちらは deadlockable 引数が 0 だと while の条件部が抜けるようになっているのでそのため一度起床するとそのまま抜けてしまってたようです。

#9 Updated by Tomoyuki Chikanaga almost 4 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 になっていることがあるようでまだ終了することがありました。

#10 Updated by Yusuke Endoh almost 4 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
などでシグナルを受けるのを待つもの (なので他のスレッドが生きてようと死んで
ようとデッドロックではない) 、という用途の違いを意識してそうしました。
の前後にそんな感じの話題がちょっとだけあります。

全然違う話だったら無視してください。

--
Yusuke Endoh mame@tsg.ne.jp

#11 Updated by Yusuke Endoh almost 4 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
などでシグナルを受けるのを待つもの (なので他のスレッドが生きてようと死んで
ようとデッドロックではない) 、という用途の違いを意識してそうしました。
の前後にそんな感じの話題がちょっとだけあります。

全然違う話だったら無視してください。

--
Yusuke Endoh mame@tsg.ne.jp

#12 Updated by Tomoyuki Chikanaga almost 4 years ago

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 から起きないようになりました。どうでしょうか。

#13 Updated by Motohiro KOSAKI almost 4 years ago

  • Assignee set to Yukihiro Matsumoto
  • Status changed from Open to Assigned

kosakiです

unblock functionはGVL持たずに呼ばれるので、そこがthread stateによって挙動を変えていたら「死ねばいいのに」レベルだと思っています。よってわたし的にはこのパッチはOK。

ただIRCで聞いたところによると mameさんの意識は

ruby -e 'trap(:INT) { }; sleep'

の時もsleepは解除されるという仕様だったそうです。ここの仕様はまつもとさんに確認
する必要があると思います。

まつもとさん、どう思いますでしょうか?

以下余談。
個人的な感想をいうと、わたしはnagachikaさんの提案している仕様が好きで、
もし、trapでsleep解除という仕様にするなら、現状のシグナルハンドラ実行を契機に
sleep解除はよくなくて、trapハンドラ実行を契機にsleep解除されなければ
いけないと考えます。

スクリプトでタイマースレッドはシグナル受信したけど、まだメインスレッドには配送

されてないので、スクリプトからは不可視。という状況を意識しないといけないのは

コレジャナイ感があります

#14 Updated by Motohiro KOSAKI almost 4 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
などでシグナルを受けるのを待つもの (なので他のスレッドが生きてようと死んで
ようとデッドロックではない) 、という用途の違いを意識してそうしました。
の前後にそんな感じの話題がちょっとだけあります。

全然違う話だったら無視してください。

返信が遅くてすいません。遠藤さんとぐぐる先生のご尽力によりこのへんの仕様は
だいぶクリアーになりました。

現状不明確なのは sigpipe のようなインタプリタ内部で握りつぶしているシグナルや
trap(:INT) { } のような Rubyレベルで握りつぶしている場合もwakeupしてくるが
これは仕様か。という点だけだと思います。

なんとなく、例外だけにしたほうがいい気がするんですよ。Unixシグナルが存在しない
Windows とか JRubyが困るんじゃないかと

#15 Updated by Motohiro KOSAKI almost 4 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
などでシグナルを受けるのを待つもの (なので他のスレッドが生きてようと死んで
ようとデッドロックではない) 、という用途の違いを意識してそうしました。
の前後にそんな感じの話題がちょっとだけあります。

全然違う話だったら無視してください。

返信が遅くてすいません。遠藤さんとぐぐる先生のご尽力によりこのへんの仕様は
だいぶクリアーになりました。

現状不明確なのは sigpipe のようなインタプリタ内部で握りつぶしているシグナルや
trap(:INT) { } のような Rubyレベルで握りつぶしている場合もwakeupしてくるが
これは仕様か。という点だけだと思います。

なんとなく、例外だけにしたほうがいい気がするんですよ。Unixシグナルが存在しない
Windows とか JRubyが困るんじゃないかと

#16 Updated by Yukihiro Matsumoto almost 4 years ago

まつもと ゆきひろです

In message "Re: [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は解除されるという仕様だったそうです。ここの仕様はまつもとさんに確認
|する必要があると思います。
|
|まつもとさん、どう思いますでしょうか?

とくにこだわりはなくて、たぶん偶然そのような挙動になっていた
のだと思います。ので、直すことに賛成します。

#17 Updated by Yukihiro Matsumoto almost 4 years ago

まつもと ゆきひろです

In message "Re: [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は解除されるという仕様だったそうです。ここの仕様はまつもとさんに確認
|する必要があると思います。
|
|まつもとさん、どう思いますでしょうか?

とくにこだわりはなくて、たぶん偶然そのような挙動になっていた
のだと思います。ので、直すことに賛成します。

#18 Updated by Tomoyuki Chikanaga almost 4 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]

Also available in: Atom PDF