遠藤です。

2010年5月6日20:02 Tanaka Akira <akr / fsij.org>:
> 2010年5月6日1:24 Yusuke Endoh <redmine / ruby-lang.org>:
>
>> 2 について、現状は Thread#raise には以下のような race が存在します。
>>
>>   t1: begin 節を実行している
>>   t2: t1.raise する
>>   t1: rescue/ensure 節の実行を開始する
>>   t3: t1.raise する
>>   t1: rescue/ensure 節が実行されないまま再度例外が発生する
>
>> これを、Thread#raise の前に Mutex#lock するというルールにすれば、
>> race を避けて使うことができるようになります。と思います。
>
> なにか後始末が必要な処理があるとします。
> たとえば、open したものは close しないといけないとして、
> 以下のコードを考えます。
>
>   begin
>      f = open(filename)
>   ensure
>      f.close
>   end
>
> ここで、open が終了した後、f に代入する前にコンテキストスイッチが起こると
> どうでしょうか。
> コンテキストスイッチの結果、他のスレッドが動いて、上記のコードを動かしている
> スレッドを raise したとします。
>
> そうすると、ensure 節が実行されるわけですが、close はできません。
> なんでかというと、open で生成した IO オブジェクトがどこにも
> 記録されていないからです。
>
> これは上記の Mutex#lock を使っても防げません。
> 一回しか Thread#raise していないからです。


Thread.raise を受け付けたらまずい期間は mutex をロックしておけ、という
ことで解決できないでしょうか。

  # Thread 1
  begin
    m.synchronize do
      f = open(filename)
    end
    # ...
  ensure
    f.close if f
    m.unlock
  end

  # Thread 2
  m.lock
  th.raise


>> # ちなみにこの race はシグナルにも存在します
>
> POSIX シグナルでは、受け付けたシグナルは signal handler 内でマスクされるので、
> handler 内で再度そのシグナルで割り込まれる、という心配はありません。

いえ、そういう心配ではないです。
Ctrl+C を連打した場合、ensure の実行開始直後でもう一回 Interrupt が
発生してしまうと、ensure 節の中身が実行されない可能性があるのでは
ないかという心配です。

# ちゃんとは確認してないのですが、ひょっとしたら何か対策されている?

SIGINT が Interrupt 例外に自動変換されるのが問題なので、trap(:INT) など
で自力で対策すれば問題なくなると思います。


ところで、Ruby レベルの trap が実行中にシグナルを受け取ったら、別の
trap を実行してしまうことが (ささださんによると) あるらしいのですが、
まずいですかね。

-- 
Yusuke Endoh <mame / tsg.ne.jp>