あまりまとまっていないのですが,思ったことを表明しておきます.

(2012/11/10 0:41), KOSAKI Motohiro wrote:
> 2012/11/9 Tomoyuki Chikanaga <nagachika00 / gmail.com>:
>> 近永と申します。
>>
>>> 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>'

 signal 用 thread を用意しておくと,deadlock 検出が殆どうまく行かなくな
るような気がするので,やっぱなしでいいかなぁ.いや,trap handler が定義
された瞬間に signal 用 thread を用意してやるのは悪くない気もする.
が,2.0.0 向けじゃないですな.

 小崎さんが「この方針で修正」というのは,具体的にはどういう話でしょう
か.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 はロック出来ているんでしょうか,と
いう疑問があります.

 やはり根本的な解決策は,下記のような感じでしょうか.

(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に書き込みできる人はあれにレースがあるよ、動かないよとコメントしてあげてください。
> 急遽アカウント作ったものの作りたてのアカウントではコメント書き込みできないみたい

 現在,たまたま出来ちゃうのがねえ.

-- 
// SASADA Koichi at atdot dot net