Rubyでfinalizerの順番を制御する

投稿者 nanki 2010-09-21 00:55:00 GMT

SWIGでLLVMのRuby bindingを書いていて、やっぱりGC周りではまってしまった。

具体的には、オブジェクトの開放の順番を指定しないといけないのだが、 Rubyではfinalizerの実行順は不定だし、先に消えてほしいオブジェクトからリファレンスを持ったりしてみたのだが、 rootからの参照を一度に消されるとどうしようもない。

そこで試しに以下のようなコードを書いてみたところうまくいった。

module OrderedFinalizer
  @@queue = []

  def self.add(*v)
    @@queue << weaken_reference(v)
  end

  def self.weaken_reference(v)
    v.last.map!(&:object_id)
    v
  end

  def self.step
    @@queue.each do |group|
      unless group.last.any?{|e|ObjectSpace._id2ref(e) rescue nil}
        group.pop
        weaken_reference(group) unless group.empty?
      end
    end
    @@queue.reject!(&:empty?)
    @@queue.empty?
  end

  at_exit do
    GC.start until self.step
  end
end


# obj2, obj3 ☞ obj1 という順番で解放したい
OrderedFinalizer.add [obj1], [obj2, obj3]
OrderedFinalizer.add [obj1], [obj4, obj5]

以下軽く解説。

  1. OrderedFinalizerに解放順序を指定したいオブジェクトへのリファレンスを保持させる。
  2. 一番先に解放してほしいオブジェクトだけはweak reference(つまりobject_id)で持つ。
  3. weak referenceなオブジェクトが解放されていたら、次のオブジェクトの参照をweakにする。

OrderedFinalizerはat_exitで参照されているので、Ruby界では最後まで生き残る。

stepを各GC毎に呼ぶのが理想なのだが、そんなイベントはないよなぁ。 今回の用途には足るんだけど、今のままだと、最後までいくらかのオブジェクトを保持してしまう。

当然、obj1からobj2へのリファレンスがあったりすると無限ループになるし、[true]とか渡してやるとかも困る。

あるいは別の解法求む。

This entry was posted on 2010-09-21 00:55:00 GMT and カテゴリ , . You can follow any response to this entry through the Atom feed. or a trackback from your own site.

タグ


トラックバック

トラックバックリンク:
http://blog.netswitch.jp/trackbacks?article_id=8669