Feature #1952
closedcannot stop with Ctrl+C
Description
=begin
以下のスクリプトがCtrl+Cで停止せず、Ctrl+C押下後はkill -9でしか殺せません。
Thread.new do
begin
begin
sleep
ensure
raise
end
rescue
retry
end
end.join
=end
Updated by usa (Usaku NAKAMURA) over 15 years ago
=begin
こんにちは、なかむら(う)です。
In message "[ruby-dev:39107] [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されたスレッド
両方が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 usa@garbagecollect.jp
=end
Updated by matz (Yukihiro Matsumoto) over 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" usa@garbagecollect.jp writes:
|おそらく、(3)のTAG_FATALが忘れ去られるのが問題で、外側のrescue
|節に入らないようにしないといけないのではないかと思います。
|ensure節でraiseするとき、現在TAG_FATALだったらTAG_RAISEでなく
|TAG_FATALで飛ぶべきなんでしょうかねえ。難しい。
うーん、ensureでraiseしたり、その外でrescueでretryしたりして
いるわけですから、無限ループそのものは「意図通り」なのではな
いでしょうか。むしろ、raiseがTAG_FAGALで飛ぶ方が気持ちが悪い
です。
これはmain threadが他スレッド待ちの間に割り込みが効かない方を
直すべきではないでしょうか。
=end
Updated by usa (Usaku NAKAMURA) over 15 years ago
=begin
こんにちは、なかむら(う)です。
In message "[ruby-dev:39127] 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で飛ぶ方が気持ちが悪い
です。
私としてはTAG_FATALが握りつぶし可能であることの方が気持ちが悪
いです。
つまり、いかなる方法でensure節が終了した場合(raiseだろうがreturn
だろうがend到達だろうが)も、TAG_FATALとしての処理を続行すべき
ではないか、ということです。
私の元の文面がよくありませんでしたが、TAG_RAISEでないTAG_FATAL
な例外を発生させる、という意図はありません。
こういう言い方だと「気持ちが悪」くなくなるでしょうか?
それとも、「意図通り」のはずの無限ループが中断されることが「
気持ちが悪い」のでしょうか?
これはmain threadが他スレッド待ちの間に割り込みが効かない方を
直すべきではないでしょうか。
それで1.8と同じ(2回Ctrl+Cでスクリプトを終了できる)にはなりま
すね。
それでは。¶
U.Nakamura usa@garbagecollect.jp
=end
Updated by matz (Yukihiro Matsumoto) over 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" usa@garbagecollect.jp writes:
|In message "[ruby-dev:39127] 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で飛ぶ方が気持ちが悪い
|> です。
|
|私としては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
Updated by usa (Usaku NAKAMURA) over 15 years ago
=begin
こんにちは、なかむら(う)です。
In message "[ruby-dev:39129] Re: [Bug #1952] cannot stop with Ctrl+C"
on Aug.19,2009 17:06:30, matz@ruby-lang.org 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 usa@garbagecollect.jp
=end
Updated by matz (Yukihiro Matsumoto) over 15 years ago
- Status changed from Open to Closed
- % Done changed from 0 to 100
=begin
Applied in changeset r24591.
=end
Updated by usa (Usaku NAKAMURA) over 15 years ago
=begin
こんにちは、なかむら(う)です。
In message "[ruby-dev:39131] 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 @@ 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 usa@garbagecollect.jp
=end
Updated by usa (Usaku NAKAMURA) over 15 years ago
=begin
こんにちは、なかむら(う)です。
In message "[ruby-dev:39140] 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
Updated by matz (Yukihiro Matsumoto) over 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" usa@garbagecollect.jp writes:
|In message "[ruby-dev:39140] Re: [Bug #1952] cannot stop with Ctrl+C"
| on Aug.20,2009 13:22:10, usa@garbagecollect.jp wrote:
|> パッチは用意してみましたが、そもそもなぜここでは例外を無視し
|> ていたのでしょう?>ささださん
|> 普通に考えると、ここで起きうる例外って自身に対する割り込みだ
|> けのような気がするんですが...
|
|既に直ってたの見落としてました orz
自身に対する割り込みだけだと確信できれば、うささんのパッチの
方がだいぶシンプルでいいですよねえ。どうなんでしょ。
=end
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 mame@tsg.ne.jp
=end
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, 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
Updated by mame (Yusuke Endoh) over 14 years ago
=begin
遠藤です。
2010年4月14日10:20 U.Nakamura usa@garbagecollect.jp:
In message "[ruby-dev:40936] 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 が実行されない危険が緩和されるが
本質的に解決はしない。あとダサい
- サブスレッドの ensure が実行されない危険が緩和されるが
くらいを思いつきましたが、どれも問題がある or 面倒ですね。
new feature な気もするので、やるとしても 1.9.3 以降という
ことにして、今回は revert させてもらうことにします。
--
Yusuke ENDOH mame@tsg.ne.jp
=end
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 は同じ
例外を投げるはずなので、正しい気もする
- 気持ち悪いというのは同意しますが、無引数 raise は同じ
のいずれかがいいように思いました。
Feature トラッカに移動しておきます。
--
Yusuke Endoh mame@tsg.ne.jp
=end
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) about 12 years ago
ささださん、現状を教えてください。
--
Yusuke Endoh mame@tsg.ne.jp
Updated by ko1 (Koichi Sasada) about 12 years ago
これはやらんといかんということで,pr2 前か後かわかりませんが,やります.
Updated by kosaki (Motohiro KOSAKI) about 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) about 12 years ago
ついでというわけではないんですが、mameさんのcomment#10のようなスクリプトへの防御力をあげるため、メインスレッドがサブスレッド終了を待ってる時はステータスをTHREAD_KILLEDに変える修正を r37886 で入れました。メインスレッドはすでに終了しているのだから、thr.raiseが動いてしまっているのがそもそもおかしかった。