Issue #6701 has been updated by Nobuyoshi Nakada.

Description updated

----------------------------------------
Bug #6701: once literal doesn't care escape
https://bugs.ruby-lang.org/issues/6701#change-52015

* Author: Koichi Sasada
* Status: Closed
* Priority: Normal
* Assignee: Koichi Sasada
* ruby -v: ruby 2.0.0dev (2012-07-05 trunk 36311) [i386-mswin32_100]
* Backport: 
----------------------------------------
# 概要

`/#{expr}/o` は,`expr` はたかだか一回しか実行されない,後から評価したときは `expr` の評価値(を用いた正規表現)が返されるという意味になります.しかし,`expr` 中で例外などで大域脱出が発生し,再度評価しようとすると,まだ `expr` は実行中であると認識されるため,ブロックします.

# 現象

次のようなコードが止まりません.

~~~ruby
  2.times{
    catch(:escape){
      p:before
      r = /#{throw :escape}/o
      # まだ,1度目の処理が終わってないとみなされているため,
      # 2回目に実行しようとすると,その1度目の終了を待つ
      # (もちろん,1度目は throw によって cancel されている→デッドロック)
      p:after
    }
  }
~~~

スレッドを絡めるとこんな感じです.

~~~ruby
  (1..2).map{
    Thread.new{
      begin
        r = /#{raise}/o
        # あるスレッドが実行しようとするが,例外でキャンセルされる
        # 別のスレッドは,キャンセルされた実行の終了を待つ
        # (もちろん,終わるわけがないのでデッドロック)
      rescue
        p :raised
      ensure
        p :exit
      end
    }
  }.each{|t| t.join}
~~~

# 修正案

`expr` が例外でキャンセルされたら,ちゃんと「未実行状態」に戻して,待ってるスレッドがいればそのスレッドが `expr` をやり直すべきではないかと思います.具体的には,ちゃんと `ensure` 的な処理を入れる様に改造します.

特に何も意見がなければ,2.0 はこの方針で直します.

1.9 は,もうこのまま,でしょうか.これまで文句が来たことが無いので,誰も `/#{expr}/o` なんて使ってないってことですかね.

別の選択肢として,例外で抜けたら `expr` の評価値を `nil` にする,という案もありますが,なぜ `nil` なのか,とかあまり説得力のある理由が思いつきません.

# 余談

ところで,これを考えていて,次の様な例が思い当たりました.

~~~ruby
  def foo
    r = /#{foo}/o
  end
  
  foo
~~~

これ,どうするべきなんだろう.こんなこと書くな,でしょうか.それとも,deadlock というか,recursive な once なのでエラー,とするのがいいでしょうか(エラーがいい気がするな).

ちなみに,1.8 だと問答無用で実行しちゃうようで:

~~~
  t.rb:3:in `foo': stack level too deep (SystemStackError)
~~~






-- 
https://bugs.ruby-lang.org/