安達@沖データと申します。

ruby-dev:8341でまつもとさんに、『もちっと考えたら(意訳)』と言われたん
で考えてみた結果を報告しますです。

#自慢できるものではないのですが、もしかして誰か期待している人とか、さ
#らに発展させてくれる人が居ないとも限らないんで。

長所:
1) マルチスレッド対応。Thread.current['error_dict']で参照する
2) エラーハンドラを内部で定義する場合、スレッドの先頭で、
  ErrorDict::at_thread_startupを呼ぶ。
3) at_thread_startupは、Arrayを作るだけで比較的軽い
4) 最初に参照した時に一度だけコピーを行う
5) コピーは、無指定の場合にはメインスレッドから行う
5) コピー元は、指定すれば任意のスレッド固有の辞書から行える
6) コピーは、Hashへの参照だけをコピーするので比較的軽い
7) 各スレッド内部で、文脈に応じて複数のハンドラを使える(save/restore)

#まつもとさんの言っていたことはクリアできたかなぁ?

短所:
1) raiseをCレベルで再定義しないと、使い物にならない
2) raiseの定義の中で、ErrorDictやThread.current['error_dict']といった
  固有名詞を参照しなければいけないのが、非常に気持悪い
3) 言ってはみたものの、今一つうまい使い方を思い付けない

長所の方はまぁ良いんですが、短所の方は数が少ない割に致命的なものばかり
です。私には、とりあえずどーにもなりません。

作ったソースを張り付けておきます。このまま実行すると、最後に(たぶん)

  ed.rb:213:in `open': No such file or directory - henoheno (Errno::ENOENT)
          from ed.rb:213

と言われますが、これがraiseをCレベルで再定義する必要性の具体例です。

#スレッドだと自分の親が分からないことが、結構不便ですね。悩みました。
#ついでに、Arrayのinitializeの呪いも忘れていました。newを実行した時だ
#けでもinitializeを実行してくれると、私的にはなかなか嬉しいんですが。
-- error_dict.rb
#!/usr/local/bin/ruby

class DictStack < Array
  alias get_at []
  alias each_dict_ each

  def dup (deep=false)
    tmp = Array.new
    aHash = {}
    if (deep)
      each_pair { | key, value |
        aHash[key] = value.dup
      }
    else
      each_dict { | h |
        tmp.push(h)
      }
    end
    tmp.push(aHash)
    tmp
  end

  def []= (key, value)
    self.get_at(-1)[key] = value
  end

  def each_dict
    each_dict_ { | dict |
      yield dict
    }
  end

  def [] (key)
    v = nil
    reverse_each { | dict |
      break if ((v = dict[key]))
    }
    v
  end

  def each_key 
    keys = []
    each_dict { | dict |
      keys += dict.keys
    }
    keys.uniq!
    keys.each { | key |
      yield key
    }
  end

  def each_pair
    each_key { | key |
      yield(key, self[key])
    }
  end

  def freeze
    each { | h |
      h.freeze
    }
    super.freeze
  end

  def size
    num = 0
    each_key { | k |
      num += 1
    }
    num
  end

  def stack_size
    length
  end

  def save
    push({})
  end

  alias restore pop
  alias each each_pair
end

class << DictStack
  def [](*lst)
    if ((lst.size % 2) == 1)
      raise ArgumentError, "Number of arguments shall be even."
    end
    dict = DictStack.new
    (0 .. (lst.size/2)-1).each { |idx|
      dict[lst[idx*2]] = lst[idx*2+1]
    }
    dict
  end
end

class ErrorDict < DictStack
  ErrorDicts = {}

  attr_accessor :origin_thread

  def initialize(th='main')
    @origin_thread = th
    ErrorDicts[Thread.current] = self
    super()
  end

  def ensure_copy
    if (@origin_thread)
      origin = ErrorDicts[@origin_thread]
      if (origin.type != self.type)
        raise RuntimeError, "ErrorDict is not properly initialized"
      end
      if (origin.size > 0)
        pop
        origin.each_dict { | h |
         push(h)
        }
        push({})
      end
      @origin_thread = nil
    end
  end

  def []= (key, value)
    ensure_copy
    super(key, value)
  end

  def [] (key)
    ensure_copy
    super(key)
  end
end

class << ErrorDict
  def at_thread_startup(th='main')
    dict = ErrorDict.new
    dict.push({})
    dict.origin_thread = th
    ErrorDict::ErrorDicts[Thread.current] = self
    Thread.current['error_dict'] = dict
  end
end

ErrorDict::at_thread_startup
ErrorDict::ErrorDicts['main'] = Thread.current['error_dict']

alias original_raise raise

def raise(*args)
  case args.size
    when 0; original_raise
    when 1; original_raise(args[0])
  end
  if (Thread.current['error_dict'])
    handler = Thread.current['error_dict'][args[0]]
  else
    handler = ErrorDict::ErrorDicts['main'][args[0]]
  end
  if (!handler)
    original_raise(args)
  end
  if (handler.type == String)
    raise handler
  elsif (handler.type == Proc)
    handler.call(args)
  else
    raise ArgumentError, "Invalid error handler: type = #{handler.type}"
  end
end

if __FILE__ == $0

  dict = Thread.current['error_dict']
  dict[Errno::EINVAL] = "default handler(Errno::EINVAL)"
  dict[Errno::ENOENT] = Proc.new { | *args |
    args.each { |arg|
      print "#{arg}\n"
    }
    print "no such file/directory\n";
    open ("/dev/null")
  }

  print "initial entries\n"
  Thread.current['error_dict'].each { | key, val |
    print "#{key} = #{val}\n"
  }
  th = Thread.start {
  
    ErrorDict::at_thread_startup
    child_dict = Thread.current['error_dict']
    child_dict[Errno::ENOENT] = "new handler(Errno::ENOENT)"

    print "modified entries\n"
    Thread.current['error_dict'].each { | key, val |
      print "#{key} = #{val}\n"
    }
    Thread.current.exit
  }
  th.join

  print "resumed entries\n"
  Thread.current['error_dict'].each { | key, val |
    print "#{key} = #{val}\n"
  }

  raise(Errno::ENOENT, "This is O.K.!")

  f = open ("henoheno")
  while f.gets
    print
  end
  f.close
end

--
*------*				adachi / okidata.co.jp
|人∧鷲|				沖データ 第一研究所
| <女> |				安達 淳
|牛∨獅|
*------*