Feature #1952

cannot stop with Ctrl+C

Added by Usaku NAKAMURA over 4 years ago. Updated over 1 year ago.

[ruby-dev:39107]
Status:Closed
Priority:High
Assignee:Koichi Sasada
Category:core
Target version:2.0.0

Description

=begin
以下のスクリプトがCtrl+Cで停止せず、Ctrl+C押下後はkill -9でしか殺せません。

Thread.new do
begin
begin
sleep
ensure
raise
end
rescue
retry
end
end.join
=end


Related issues

Related to ruby-trunk - Bug #1963: redo'ing deadlock causes [BUG] Closed 08/20/2009
Related to ruby-trunk - Bug #2558: r24591 causes Segfault Closed 01/05/2010
Related to ruby-trunk - Bug #4285: Ruby don't have asynchrounous exception safe syntax and I... Closed 01/17/2011
Related to ruby-trunk - Bug #6131: Ctrl-C handler do not work from exec process (Windows) Closed 03/12/2012
Related to ruby-trunk - Bug #5368: ensure節でsleepするようなThreadがあるとインタプリタが終了しない Assigned 09/26/2011

Associated revisions

Revision 37875
Added by Motohiro KOSAKI over 1 year ago

  • thread.c (rbthreadterminate_all): broadcast eTerminateSignal again when Ctrl-C was pressed. [Feature #1952]

History

#1 Updated by Usaku NAKAMURA over 4 years ago

=begin
こんにちは、なかむら(う)です。

In message " [Bug #1952] cannot stop with Ctrl+C"
on Aug.18,2009 23:50:32, redmine@ruby-lang.org wrote:

以下のスクリプトがCtrl+Cで停止せず、Ctrl+C押下後はkill -9でしか殺せません。

調査を進めてみました。

当初はpthreadな環境依存かと思っていましたが、mswin32でも同じ
現象が起きました。
1.8だと、1回目のCtrl+Cでは停止せず、2回目のCtrl+Cで死にます。

とりあえずtrunkベースで現象を眺めてみると、

(1) Ctrl+C押下時点でmainスレッドとThread.newされたスレッド
両方がTHREADTOKILLとなる。

(2) mainスレッドはTAGFATALでrblongjmp()された後、他のスレ
ッドの死亡待ちループに入る。

(3) Thread.newされたスレッドの方はTAGFATALでrblongjmp()さ
れ、sleepが中断される。
そこで内側のbegin〜end内のensure節に入るのでraiseが実行
される。
今度は外側のbegin〜end内のrescue節に入り、retryが実行さ
れる。
以上の過程で、そもそもTAG_FATALだったことが忘れ去られて
しまい、再びsleepが実行される。

(4) Thread.newされたスレッドがsleepで寝たきりなので、mainス
レッドは延々と死亡待ちループを続ける。
再度Ctrl+Cを押しても、(3)が繰り返されるだけ。

ということになってるようです。
おそらく、(3)のTAGFATALが忘れ去られるのが問題で、外側のrescue
節に入らないようにしないといけないのではないかと思います。
ensure節でraiseするとき、現在TAG
FATALだったらTAGRAISEでなく
TAG
FATALで飛ぶべきなんでしょうかねえ。難しい。

それでは。
--
U.Nakamura usa@garbagecollect.jp

=end

#2 Updated by Yukihiro Matsumoto over 4 years ago

=begin
まつもと ゆきひろです

In message "Re: Re: [Bug #1952] cannot stop with Ctrl+C"
on Wed, 19 Aug 2009 15:47:18 +0900, "U.Nakamura" usa@garbagecollect.jp writes:

|おそらく、(3)のTAGFATALが忘れ去られるのが問題で、外側のrescue
|節に入らないようにしないといけないのではないかと思います。
|ensure節でraiseするとき、現在TAG
FATALだったらTAGRAISEでなく
|TAG
FATALで飛ぶべきなんでしょうかねえ。難しい。

うーん、ensureでraiseしたり、その外でrescueでretryしたりして
いるわけですから、無限ループそのものは「意図通り」なのではな
いでしょうか。むしろ、raiseがTAG_FAGALで飛ぶ方が気持ちが悪い
です。

これはmain threadが他スレッド待ちの間に割り込みが効かない方を
直すべきではないでしょうか。

=end

#3 Updated by Usaku NAKAMURA over 4 years ago

=begin
こんにちは、なかむら(う)です。

In message " Re: [Bug #1952] cannot stop with Ctrl+C"
on Aug.19,2009 16:00:28, matz@ruby-lang.org wrote:

うーん、ensureでraiseしたり、その外でrescueでretryしたりして
いるわけですから、無限ループそのものは「意図通り」なのではな
いでしょうか。むしろ、raiseがTAG_FAGALで飛ぶ方が気持ちが悪い
です。

私としてはTAGFATALが握りつぶし可能であることの方が気持ちが悪
いです。
つまり、いかなる方法でensure節が終了した場合(raiseだろうがreturn
だろうがend到達だろうが)も、TAG
FATALとしての処理を続行すべき
ではないか、ということです。
私の元の文面がよくありませんでしたが、TAGRAISEでないTAGFATAL
な例外を発生させる、という意図はありません。

こういう言い方だと「気持ちが悪」くなくなるでしょうか?
それとも、「意図通り」のはずの無限ループが中断されることが「
気持ちが悪い」のでしょうか?

これはmain threadが他スレッド待ちの間に割り込みが効かない方を
直すべきではないでしょうか。

それで1.8と同じ(2回Ctrl+Cでスクリプトを終了できる)にはなりま
すね。

それでは。
--
U.Nakamura usa@garbagecollect.jp

=end

#4 Updated by Yukihiro Matsumoto over 4 years ago

=begin
まつもと ゆきひろです

In message "Re: Re: [Bug #1952] cannot stop with Ctrl+C"
on Wed, 19 Aug 2009 16:35:56 +0900, "U.Nakamura" usa@garbagecollect.jp writes:

|In message " Re: [Bug #1952] cannot stop with Ctrl+C"
| on Aug.19,2009 16:00:28, matz@ruby-lang.org wrote:
|> うーん、ensureでraiseしたり、その外でrescueでretryしたりして
|> いるわけですから、無限ループそのものは「意図通り」なのではな
|> いでしょうか。むしろ、raiseがTAGFAGALで飛ぶ方が気持ちが悪い
|> です。
|
|私としてはTAG
FATALが握りつぶし可能であることの方が気持ちが悪
|いです。

これなんですが、そもそもなぜInterruptがTAGFATALなんでしょう
か。1.8では普通にTAG
RAISEだと思います。もちろん、うささんに
聞いてもしょうがないんですが。

|つまり、いかなる方法でensure節が終了した場合(raiseだろうがreturn
|だろうがend到達だろうが)も、TAGFATALとしての処理を続行すべき
|ではないか、ということです。
|私の元の文面がよくありませんでしたが、TAG
RAISEでないTAG_FATAL
|な例外を発生させる、という意図はありません。
|
|こういう言い方だと「気持ちが悪」くなくなるでしょうか?
|それとも、「意図通り」のはずの無限ループが中断されることが「
|気持ちが悪い」のでしょうか?

私の感想は

  • C-cでTAG_FATALが発生する現状が気持ち悪い

  • ensure + raise で例外を握り潰すことができるのは以前から
    知られていたことであり、それをできなくする必然性がわから
    ない

    ということです。さらに

  • kill -9を必要とするのは問題である。ので、main threadは割
    り込み可能にしておくべき

  • C-cでTAG_RAISEであるべき(だと思う)

  • C-cのことは別にして、うささんがおっしゃるようにTAGFATAL
    状態でensureした場合、たとえ途中で通常のraiseがあってもそ
    れは無視して、TAG
    FATALを継続するという仕様は十分ありえる

    と思います。

                             まつもと ゆきひろ /:|)
    

=end

#5 Updated by Usaku NAKAMURA over 4 years ago

=begin
こんにちは、なかむら(う)です。

In message " Re: [Bug #1952] cannot stop with Ctrl+C"
on Aug.19,2009 17:06:30, matz@ruby-lang.org wrote:

これなんですが、そもそもなぜInterruptがTAGFATALなんでしょう
か。1.8では普通にTAG
RAISEだと思います。もちろん、うささんに
聞いてもしょうがないんですが。

あれ、と思って追い直してみたところ、TAGFATALはvmexec()の中
で消尽されていて、eval.c内にはTAG_RAISEで処理されていました。

すみません...

というわけで、

  • kill -9を必要とするのは問題である。ので、main threadは割 り込み可能にしておくべき

とりあえずこれですね。
ちなみに、thread.c:320の/* ignore exception */のところになり
ます。

  • C-cのことは別にして、うささんがおっしゃるようにTAGFATAL 状態でensureした場合、たとえ途中で通常のraiseがあってもそ れは無視して、TAGFATALを継続するという仕様は十分ありえる

これはそのうちTAG_FATALを起こす方法を考えて研究してみます。

それでは。
--
U.Nakamura usa@garbagecollect.jp

=end

#6 Updated by Yukihiro Matsumoto over 4 years ago

  • Status changed from Open to Closed
  • % Done changed from 0 to 100

=begin
Applied in changeset r24591.
=end

#7 Updated by Usaku NAKAMURA over 4 years ago

=begin
こんにちは、なかむら(う)です。

In message " Re: [Bug #1952] cannot stop with Ctrl+C"
on Aug.19,2009 17:49:23, usa@garbagecollect.jp wrote:

  • kill -9を必要とするのは問題である。ので、main threadは割 り込み可能にしておくべき

とりあえずこれですね。
ちなみに、thread.c:320の/* ignore exception */のところになり
ます。

パッチは用意してみましたが、そもそもなぜここでは例外を無視し
ていたのでしょう?>ささださん
普通に考えると、ここで起きうる例外って自身に対する割り込みだ
けのような気がするんですが...

Index: thread.c
===================================================================
--- thread.c (revision 24567)
+++ thread.c (working copy)
@@ -298,6 +298,7 @@ rbthreadterminateall(void)
{
rb
threadt *th = GETTHREAD(); /* main thread */
rbvmt *vm = th->vm;
+ int state;
if (vm->mainthread != th) {
rb
bug("rbthreadterminateall: called by child thread (%p, %p)",
(void *)vm->main
thread, (void *)th);
@@ -311,14 +312,12 @@ rbthreadterminateall(void)
thread
debug("rbthreadterminateall (main thread: %p)\n", (void *)th);
st
foreach(vm->livingthreads, terminatei, (stdatat)th);

  • while (!rbthreadalone()) {
  • state = 0;
  • while (state == 0 && !rbthreadalone()) { PUSH_TAG();
  • if (EXEC_TAG() == 0) {
  • if ((state = EXECTAG()) == 0) { rbthread_schedule(); }
  • else {
  • /* ignore exception */
  • } POPTAG(); } rbthreadstoptimer_thread(); それでは。 -- U.Nakamura usa@garbagecollect.jp

=end

#8 Updated by Usaku NAKAMURA over 4 years ago

=begin
こんにちは、なかむら(う)です。

In message " Re: [Bug #1952] cannot stop with Ctrl+C"
on Aug.20,2009 13:22:10, usa@garbagecollect.jp wrote:

パッチは用意してみましたが、そもそもなぜここでは例外を無視し
ていたのでしょう?>ささださん
普通に考えると、ここで起きうる例外って自身に対する割り込みだ
けのような気がするんですが...

既に直ってたの見落としてました orz

それでは。
--
U.Nakamura usa@garbagecollect.jp

=end

#9 Updated by Yukihiro Matsumoto over 4 years ago

=begin
まつもと ゆきひろです

In message "Re: Re: [Bug #1952] cannot stop with Ctrl+C"
on Thu, 20 Aug 2009 14:47:18 +0900, "U.Nakamura" usa@garbagecollect.jp writes:

|In message " Re: [Bug #1952] cannot stop with Ctrl+C"
| on Aug.20,2009 13:22:10, usa@garbagecollect.jp wrote:
|> パッチは用意してみましたが、そもそもなぜここでは例外を無視し
|> ていたのでしょう?>ささださん
|> 普通に考えると、ここで起きうる例外って自身に対する割り込みだ
|> けのような気がするんですが...
|
|既に直ってたの見落としてました orz

自身に対する割り込みだけだと確信できれば、うささんのパッチの
方がだいぶシンプルでいいですよねえ。どうなんでしょ。

=end

#10 Updated by Yusuke Endoh about 4 years ago

  • Status changed from Closed to Open
  • Assignee set to Yukihiro Matsumoto
  • Priority changed from Normal to High

=begin
遠藤です。

このチケットのために r24591

 * thread.c (rb_thread_terminate_all): do not ignore interrupt when
   reaping threads on termination.  

という変更が入りましたが、rbthreadterminate_all の後は
main thread 以外は死んでいるという仮定があるので、非常に
都合が悪いです。

具体的には、以下のコードで、まだ生きているスレッドがいる
のに vm が destruct されて、SEGV します (たまに) 。

begin
  100.times do |i|
    begin
      Thread.start(Thread.current) {|u| u.raise }
      raise
    rescue
    ensure
    end
  end
rescue
  p 100
end

「終了時には全スレッドに例外を投げ、死ぬまで待つ」という
仕様だと考えると、その例外を潰してしまうようなスレッドが
いれば、終了時に固まるのは当然ではないでしょうか。

begin; sleep; rescue Exception; retry; end

が Ctrl+C で止められないのと同じだと思います。

ということで、当該コミットを revert し、このチケットは
rejected とすることを提案します。

--
Yusuke ENDOH mame@tsg.ne.jp
=end

#11 Updated by Usaku NAKAMURA about 4 years ago

=begin
こんにちは、なかむら(う)です。

In message " Bug #1952 cannot stop with Ctrl+C"
on Apr.09,2010 00:47:49, redmine@ruby-lang.org wrote:

「終了時には全スレッドに例外を投げ、死ぬまで待つ」という
仕様だと考えると、その例外を潰してしまうようなスレッドが
いれば、終了時に固まるのは当然ではないでしょうか。

begin; sleep; rescue Exception; retry; end

が Ctrl+C で止められないのと同じだと思います。

元の再現コードはCtrl+C(=Interrupt)を潰していません。
なので、同じではないと思います。間違ってるかしら。

ということで、当該コミットを revert し、このチケットは
rejected とすることを提案します。

SEGVは困ったもんなので、このこと自体に反対はしません。

それでは。
--
U.Nakamura usa@garbagecollect.jp

=end

#12 Updated by Yusuke Endoh about 4 years ago

=begin
遠藤です。

2010年4月14日10:20 U.Nakamura usa@garbagecollect.jp:

In message " Bug #1952 cannot stop with Ctrl+C"
on Apr.09,2010 00:47:49, redmine@ruby-lang.org wrote:

「終了時には全スレッドに例外を投げ、死ぬまで待つ」という
仕様だと考えると、その例外を潰してしまうようなスレッドが
いれば、終了時に固まるのは当然ではないでしょうか。

begin; sleep; rescue Exception; retry; end

が Ctrl+C で止められないのと同じだと思います。

元の再現コードはCtrl+C(=Interrupt)を潰していません。
なので、同じではないと思います。間違ってるかしら。

全く同じではないですが、「終了しろー」という例外を潰している
点が同じだと思います。

Interrupt によってメインスレッドがプロセスの終了処理を開始し、
生きているサブスレッドに eTerminateSignal (Ruby レベルからは
見えない仮想的な例外) を投げてサブスレッドを全消ししようとし
ているのに、サブスレッド側がその例外潰している、というのが、
実際に起きていることです。
そう考えると、これは仕様かなーと思うのでした。

ということで、当該コミットを revert し、このチケットは
rejected とすることを提案します。

SEGVは困ったもんなので、このこと自体に反対はしません。

どうしても直したいならば、

  • サブスレッドの終了待ち状態で SIGINT を受け取ったら、
    eTerminateSignal を再送する

    • しつこく Ctrl+C を押していればいつか終了できる、かも
  • eTerminateSignal を捕捉できない例外とする

    • サブスレッドの ensure が実行されない
  • eTerminateSignal を投げて数秒しても終わってくれない場合、
    捕捉できない例外を投げる

    • サブスレッドの ensure が実行されない危険が緩和されるが 本質的に解決はしない。あとダサい

    くらいを思いつきましたが、どれも問題がある or 面倒ですね。

    new feature な気もするので、やるとしても 1.9.3 以降という
    ことにして、今回は revert させてもらうことにします。

    Yusuke ENDOH mame@tsg.ne.jp

=end

#13 Updated by Yusuke Endoh almost 4 years ago

=begin
遠藤です。

遅くなりましたが、一旦 revert しました。

1.9.2 はとりあえず現状を仕様とすればいいと思いますが、将来的に
すっきりさせるならば、

  • 終了時のスレッド全消しも Thread#kill も TAG_FATAL でなく
    ただの例外を投げる

    • 子スレッド側でブロックした場合は自己責任とする
    • TAG_FATAL 自体、ほとんど不要?
  • TAGFATAL で ensure 節が呼ばれた時に再 raise したら、
    TAG
    FATAL を投げる

    • 気持ち悪いというのは同意しますが、無引数 raise は同じ 例外を投げるはずなので、正しい気もする

    のいずれかがいいように思いました。

    Feature トラッカに移動しておきます。

    Yusuke Endoh mame@tsg.ne.jp
    =end

#14 Updated by Shyouhei Urabe over 3 years ago

  • Status changed from Open to Assigned

=begin

=end

#15 Updated by Hiroshi Nakamura about 2 years ago

  • Description updated (diff)

現在の挙動。

% cat foo.rb
Thread.new do
begin
begin
p :sleep
sleep
ensure
p :raise
raise
end
rescue
p :retry
retry
end
end.join
% ruby foo.rb
:sleep
C:raise
:retry
:sleep
CCCCCCCZ

#16 Updated by Akira Tanaka about 2 years ago

  • Assignee changed from Yukihiro Matsumoto to Koichi Sasada
  • Priority changed from High to Normal
  • % Done changed from 100 to 0

開発者会議で笹田さんなどと議論したところ、
以下のスクリプトで、2回め以降の C で 1 が表示されないのはバグであるという
結論になりました。

% ruby -ve '
Thread.new do
begin
begin
p 1
sleep
ensure
raise
end
rescue
retry
end
end.join
'
ruby 2.0.0dev (2012-03-16 trunk 35049) [x86_64-linux]
1
C1
CCCCCCC

C でメインスレッドが死んだ後、他のスレッドを殺すのに
各スレッドに例外を投げるわけですが、一発で死なない場合、
それ以降人間が C を送っても無視される、というのが問題で、
人間が C を送る度に例外を再度投げるのが適切であろう、
という点には合意に達して、笹田さんが直すとのことです。

なお、何回投げても死なない、というのはプログラムがそのように書かれているということで、
それを無理やり殺す、というのは、New Feature であろう、ということで
上記の直す範囲には入りません。

#17 Updated by Koichi Sasada over 1 year ago

  • Priority changed from Normal to High

I'll fix it soon, at least before 2.0.

#18 Updated by Yusuke Endoh over 1 year ago

ささださん、現状を教えてください。

Yusuke Endoh mame@tsg.ne.jp

#19 Updated by Koichi Sasada over 1 year ago

これはやらんといかんということで,pr2 前か後かわかりませんが,やります.

#20 Updated by Motohiro KOSAKI over 1 year ago

  • Status changed from Assigned to Closed
  • % Done changed from 0 to 100

This issue was solved with changeset r37875.
Usaku, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


  • thread.c (rbthreadterminate_all): broadcast eTerminateSignal again when Ctrl-C was pressed. [Feature #1952]

#21 Updated by Motohiro KOSAKI over 1 year ago

ついでというわけではないんですが、mameさんのcomment#10のようなスクリプトへの防御力をあげるため、メインスレッドがサブスレッド終了を待ってる時はステータスをTHREAD_KILLEDに変える修正を r37886 で入れました。メインスレッドはすでに終了しているのだから、thr.raiseが動いてしまっているのがそもそもおかしかった。

Also available in: Atom PDF