>>>> 1.現状のインプリが正しい。トラップハンドラはグローバル変数に書き込んですぐ抜けるべし >>>> 2.mutexはトラップハンドラで使えないが、IOは使えるべき。 >>>> 3.mutexもIOもトラップハンドラから使えるべき >>> なんでも使いたいユーザー目線としては 3 がいいのでしょうが、わたしの感覚では 2 だと思います。シグナルハンドラ内で mutex >>> 使えないというのは納得感がありますが IO できないというのはあまり自明でないというのと、次に書くように実際の用途があると思います。 >>> >>> 子プロセスの終了を SIGCHLD で検出するために、トラップハンドラ内でロックが使えないので pipe >>> を使ってトラップハンドラで書込み、監視するスレッドで IO.select >>> で待って読むというのをやったことがあります。ロックも使えないしIOもできないとなると、変数に代入して監視するほうは polling >>> で検出するしかなくなり、その場合シグナルの喪失がありえると思います。 >> >> この意見はなっとくできますし、他に意見もでてないようなので2で進めます。ただ制限が緩くなる方に変更するのはいつでもできるので蒸し返しは歓迎します。 > > ええと,「Mutex が使えない」というのは「trap handler は main thread と > 同じ context なので,main thread が Mutex を lock している場合,trap > handler 内で Mutex を掴もうとすると例外が出てしまうから使えない」という > 意味であってますよね. あってます。 > 以前 ruby-core で,Queue に true を突っ込むように書いたら Mutex が > main thread と競合してしまって使えない,という話があって,そのときは > trap では複雑なことが出来ないことが多いから flag を突っ込むだけにしよ > う,という返事をしました.要するに近永さんがされた pipe trick みたいなこ > とを Queue を使ってやる,って話なのですが,そりゃ自然な話だよなぁ,とい > う気がしていました. > > require 'thread' > q = Queue.new > trap(:SIGINT){ > q << true > } > > q.pop # block until sigint > > ... と思ったら,上記コードはデッドロック検出が働いて動かないんですなぁ. > > /home/ko1/build/ruby/trunk/lib/thread.rb:192:in `sleep': No live threads > left. Deadlock? from > /home/ko1/build/ruby/trunk/lib/thread.rb:192:in `block in pop' > from <internal:prelude>:10:in `synchronize' > from /home/ko1/build/ruby/trunk/lib/thread.rb:184:in `pop' > from ../trunk/test.rb:7:in `<main>' そうです。いまパッチがある方針2だとこれは救えません。 > signal 用 thread を用意しておくと,deadlock 検出が殆どうまく行かなくな > るような気がするので,やっぱなしでいいかなぁ.いや,trap handler が定義 > された瞬間に signal 用 thread を用意してやるのは悪くない気もする. > が,2.0.0 向けじゃないですな. trap handlerを使うケースはもともとdeadlock検出はちゃんと動いていないのでこれ以上悪化するかなあというのが疑問だったり。 あと、わたしはsignal用threadはtrap handler処理専用スレッド的なものをイメージしていたので、スレッド作成タイミングで挙動が変わるとは思っていなかった > 小崎さんが「この方針で修正」というのは,具体的にはどういう話でしょう > か.IO のロック時に semaphore や monitor のようにして,「同じスレッドで > ロックしても無視」にする感じでしょうか.semaphore だと単にデッドロックし > て死亡なので,monitor かな.そうすると, > > 1. [main thread] IO を lock > 2. [main thread] signal により trap を起動 > 3. [trap handler] monitor#lock > 4. [trap handler] IO する > 5. [trap handler] monitor#unlock > 6. [main thread] IO する > 7. [main thread] monitor#unlock > > こんな感じで,lock で守りたかった IO はロック出来ているんでしょうか,と > いう疑問があります. いいえ、それは[Bug #7134] でバグ報告者が主張していた方針だと思うのですが(recursive mutexを使う)、メモリ破壊が起きるのでやりたくないと返事をしています。 現在の実装は thread->trap_enabled というフラグを追加し、fptr->write_lockをとるときにこれを disableに変えてしまう。 そして、RUBY_VM_CHECK_INTS()で trap_enabledが0のあいだは、たとえ thread->interrupt_flag が非0でもtrap handlerを呼び出さない (今気づいたけど、添付したパッチは thread->interrupt_flagの 0x2以外も無視してるのでよくないですね。なおさないと) この方針だと ・fptr->write_lockだけが特別扱い。もともとrubyには見えてないこっそりロックだから挙動を変えても非互換ではない ・stdoutへのIOはすべて許される ・pipeへのwriteはwrite_nonblockを使うか、読み出し側をサブスレッドにしないとIOでブロッキングして刺さることはありえる(現状どおり) ・trap handler とmain threadの両方で mutex classを使った場合は、deadlock検出で死ぬことはありうる(現状どおり) という感じ focusが trap { puts "hoge" } を救うことに集中してるバンドエイドパッチ。 > やはり根本的な解決策は,下記のような感じでしょうか. > > (1) trap ハンドラを設定すると,signal が配送されるのをまつ待機 thread を > 生成する (signal thread と呼称する) > (2) trap ハンドラがすべて削除されると,その thread は削除される > (3) trap ハンドラ内での全ての例外は main thread へ Thread#raise される > > でしょうか. わたしのイメージはそんな感じ > てきとーに用語を定義しないでフィーリングで読んでもらうと,こんな感じ. > スレッドセーフじゃないのはご愛敬. > > def trap(sig, &block) # 面倒なので cmd は略 > if block > @@trap_handlers[sig] = block > @@signal_thread = Thread.new{ > until @@trap_handlers.empty? > sig = sigwait() > begin > @@trap_handlers[sig].call(sig) > rescue CheckTrapHandler > # ignore > rescue Exception => e > main_thread.kill(e) > end > end > } > end unless @@signal_thread > else > @@trap_handlers.delete(sig) > @@signal_thread.raise(CheckTrapHandler) if @@signal_thread > end > end > > > 半年くらい早くからこの議論をしていれば,こんなふうに変えるとすっきりし > たんじゃないかと思います.2.0.0 ではどうしますかねぇ. > > >>>> ・そもそもトラップからthrowして制御を戻すのは合法か? >>> 感覚的なことしか言えなくて申し訳ないのですが、これはさすがに無しじゃないでしょうか。 >> >> だれか stackoverflowに書き込みできる人はあれにレースがあるよ、動かないよとコメントしてあげてください。 >> 急遽アカウント作ったものの作りたてのアカウントではコメント書き込みできないみたい > > 現在,たまたま出来ちゃうのがねえ. 現状すでに何回かに一回はぎゃっとなるというコードしか書けない状態なので、throwも非同期になげるようにしても自体はあんまり悪化しないのでは。というあまり根拠のない予感があったり。だって、いま動いてないんだもん。 またはtrap handlerからthrowしたときに常にエラーになるようにエラーチェックを強化するというのも、何回かに一回失敗するよりかは親切なので、許される方針という認識