@m_seki の

I like ruby tooから引っ越し

rinda_evalその後

TupleSpaceやTupleSpaceProxyのメソッドにすると、リモートでrinda_evalするミスがおきやすい(自分ではまった)のでmodule_functionにすることにした。

require 'drb/drb'
require 'rinda/rinda'

module Rinda
  module_function
  def rinda_eval(ts)
    ts = DRbObject.new(ts) unless DRbObject === ts
    pid = fork do
      Thread.current['DRb'] = nil
      DRb.stop_service
      DRb.start_service
      place = TupleSpaceProxy.new(ts)
      tuple = yield(place)
      place.write(tuple) rescue nil
    end
    Process.detach(pid)
  end
end

rinda_evalをつかって、大きくなったRBTreeをサブプロセスに分割していく遊びをしてみる。大きな集合を1つのプロセスで管理するなんて不安だ!という心配性な人にはうれしいかもしれない。

MultiRBTreeにsplit(名前に問題があるけど他に思いつかなかった)メソッドを追加。指定された要素数をローカルに残して、あまりをサブプロセスに管理してもらう。となりあったノードはそれぞれ@leftと@rightで参照できる。

require 'rbtree'
require 'rinda/tuplespace'
require 'rinda_eval'

class MultiRBTree
  def split(size, place=nil)
    key = Time.now.to_f
    pid = Process.pid
    place = Rinda::TupleSpace.new unless place
    prev = DRbObject.new(self)
    Rinda::rinda_eval(place) do |ts|
      @left = prev
      size.times do
        self.shift
      end
      ts.write([:rbtree, key, pid, DRbObject.new(self)])
      ts.read([:shutdown]) rescue exit
      [:done]
    end
    (self.size - size).times do
      self.pop
    end
    tuple = place.take([:rbtree, key, pid, nil])
    @right.left = tuple[3] if @right
    @right = tuple[3]
  end

  attr_accessor :left, :right
end

if __FILE__ == $0
  ts = Rinda::TupleSpace.new
  DRb.start_service
  rbt = MultiRBTree.new
  5000.times do |n|
    rbt[(n % 49).to_s] = n
  end
  ary = [rbt]
  ary << ary.last.split(1000, ts)
  ary << ary.last.split(1000, ts)
  ary << ary.last.split(1000, ts)
  ary << ary[1].split(500)
  ary.each do |x|
    p [x.first, x.size, (x.__drburi rescue [:local, DRb.uri]), 
       (x.left.last rescue nil), (x.right.first rescue nil)]
  end

  cur = ary[0]
  while cur
    p [cur.first, cur.last]
    cur = cur.right
  end

  ts.write([:shutdown])
end

この例だと自分と子供、孫、ひ孫で分担してMultiRBTreeを管理します。起動よりも終了が難しいな。