Project

General

Profile

Actions

Feature #1952

closed

cannot stop with Ctrl+C

Added by usa (Usaku NAKAMURA) about 15 years ago. Updated almost 12 years ago.

Status:
Closed
Target version:
[ruby-dev:39107]

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 6 (0 open6 closed)

Related to Ruby master - Bug #1963: redo'ing deadlock causes [BUG]Closed08/20/2009Actions
Related to Backport191 - Backport #2948: failing test - test_thread.rb in ruby 1.9.1 p378 on linux redhatClosedyugui (Yuki Sonoda)Actions
Related to Ruby master - Bug #2558: r24591 causes SegfaultClosed01/05/2010Actions
Related to Ruby master - Bug #4285: Ruby don't have asynchrounous exception safe syntax and It should have.Closedko1 (Koichi Sasada)01/17/2011Actions
Related to Ruby master - Bug #6131: Ctrl-C handler do not work from exec process (Windows)Closedh.shirosaki (Hiroshi Shirosaki)03/12/2012Actions
Related to Ruby master - Bug #5368: ensure節でsleepするようなThreadがあるとインタプリタが終了しないClosedkosaki (Motohiro KOSAKI)Actions
Actions #1

Updated by usa (Usaku NAKAMURA) about 15 years ago

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

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

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

調査を進めてみました。

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

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

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

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

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

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

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

それでは。

U.Nakamura

=end

Actions #2

Updated by matz (Yukihiro Matsumoto) about 15 years ago

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

In message "Re: [ruby-dev:39126] Re: [Bug #1952] cannot stop with Ctrl+C"
on Wed, 19 Aug 2009 15:47:18 +0900, "U.Nakamura" writes:

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

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

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

=end

Actions #3

Updated by usa (Usaku NAKAMURA) about 15 years ago

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

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

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

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

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

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

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

それでは。

U.Nakamura

=end

Actions #4

Updated by matz (Yukihiro Matsumoto) about 15 years ago

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

In message "Re: [ruby-dev:39128] Re: [Bug #1952] cannot stop with Ctrl+C"
on Wed, 19 Aug 2009 16:35:56 +0900, "U.Nakamura" writes:

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

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

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

私の感想は

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

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

ということです。さらに

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

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

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

と思います。

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

=end

Actions #5

Updated by usa (Usaku NAKAMURA) about 15 years ago

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

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

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

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

すみません...

というわけで、

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

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

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

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

それでは。

U.Nakamura

=end

Actions #6

Updated by matz (Yukihiro Matsumoto) about 15 years ago

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

=begin
Applied in changeset r24591.
=end

Actions #7

Updated by usa (Usaku NAKAMURA) about 15 years ago

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

In message "[ruby-dev:39131] Re: [Bug #1952] cannot stop with Ctrl+C"
on Aug.19,2009 17:49:23, 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 @@ rb_thread_terminate_all(void)
{
rb_thread_t th = GET_THREAD(); / main thread */
rb_vm_t *vm = th->vm;

  • int state;
    if (vm->main_thread != th) {
    rb_bug("rb_thread_terminate_all: called by child thread (%p, %p)",
    (void *)vm->main_thread, (void *)th);
    @@ -311,14 +312,12 @@ rb_thread_terminate_all(void)
    thread_debug("rb_thread_terminate_all (main thread: %p)\n", (void *)th);
    st_foreach(vm->living_threads, terminate_i, (st_data_t)th);
  • while (!rb_thread_alone()) {
  • state = 0;
  • while (state == 0 && !rb_thread_alone()) {
    PUSH_TAG();
  • if (EXEC_TAG() == 0) {
  • if ((state = EXEC_TAG()) == 0) {
    rb_thread_schedule();
    }
  • else {
  •  /* ignore exception */
    
  • }
    POP_TAG();
    }
    rb_thread_stop_timer_thread();

    それでは。
    --
    U.Nakamura

=end

Actions #8

Updated by usa (Usaku NAKAMURA) about 15 years ago

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

In message "[ruby-dev:39140] Re: [Bug #1952] cannot stop with Ctrl+C"
on Aug.20,2009 13:22:10, wrote:

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

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

それでは。

U.Nakamura

=end

Actions #9

Updated by matz (Yukihiro Matsumoto) about 15 years ago

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

In message "Re: [ruby-dev:39141] Re: [Bug #1952] cannot stop with Ctrl+C"
on Thu, 20 Aug 2009 14:47:18 +0900, "U.Nakamura" writes:

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

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

=end

Actions #10

Updated by mame (Yusuke Endoh) over 14 years ago

  • Status changed from Closed to Open
  • Assignee set to matz (Yukihiro Matsumoto)
  • Priority changed from Normal to 5

=begin
遠藤です。

このチケットのために r24591 で

 * thread.c (rb_thread_terminate_all): do not ignore interrupt when
   reaping threads on termination.  [ruby-dev:39107]

という変更が入りましたが、rb_thread_terminate_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
=end

Actions #11

Updated by usa (Usaku NAKAMURA) over 14 years ago

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

In message "[ruby-dev:40936] Bug #1952 cannot stop with Ctrl+C"
on Apr.09,2010 00:47:49, wrote:

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

begin; sleep; rescue Exception; retry; end

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

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

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

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

それでは。

U.Nakamura

=end

Actions #12

Updated by mame (Yusuke Endoh) over 14 years ago

=begin
遠藤です。

2010年4月14日10:20 U.Nakamura :

In message "[ruby-dev:40936] Bug #1952 cannot stop with Ctrl+C"
on Apr.09,2010 00:47:49, 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

=end

Actions #13

Updated by mame (Yusuke Endoh) over 14 years ago

=begin
遠藤です。

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

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

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

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

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

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

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

--
Yusuke Endoh
=end

Actions #14

Updated by shyouhei (Shyouhei Urabe) about 14 years ago

  • Status changed from Open to Assigned

=begin

=end

Updated by nahi (Hiroshi Nakamura) over 12 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
^C^C^C^C^C^C^C^Z

Updated by akr (Akira Tanaka) over 12 years ago

  • Assignee changed from matz (Yukihiro Matsumoto) to ko1 (Koichi Sasada)
  • Priority changed from 5 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
^C^C^C^C^C^C^C

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

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

Updated by ko1 (Koichi Sasada) about 12 years ago

  • Priority changed from Normal to 5

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

Updated by mame (Yusuke Endoh) almost 12 years ago

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

--
Yusuke Endoh

Updated by ko1 (Koichi Sasada) almost 12 years ago

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

Actions #20

Updated by kosaki (Motohiro KOSAKI) almost 12 years 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 (rb_thread_terminate_all): broadcast eTerminateSignal
    again when Ctrl-C was pressed. [Feature #1952] [ruby-dev:39107]

Updated by kosaki (Motohiro KOSAKI) almost 12 years ago

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

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0