class Rinda::RingFinger

RingFinger is used by RingServer clients to discover the RingServer's TupleSpace. Typically, all a client needs to do is call ::primary to retrieve the remote TupleSpace, which it can then begin using.

To find the first available remote TupleSpace:

Rinda::RingFinger.primary

To create a RingFinger that broadcasts to a custom list:

rf = Rinda::RingFinger.new  ['localhost', '192.0.2.1']
rf.primary

Rinda::RingFinger also understands multicast addresses and sets them up properly. This allows you to run multiple RingServers on the same host:

rf = Rinda::RingFinger.new ['239.0.0.1']
rf.primary

You can set the hop count (or TTL) for multicast searches using multicast_hops.

If you use IPv6 multicast you may need to set both an address and the outbound interface index:

rf = Rinda::RingFinger.new ['ff02::1']
rf.multicast_interface = 1
rf.primary

At this time there is no easy way to get an interface index by name.

Attributes

broadcast_list[RW]

The list of addresses where RingFinger will send query packets.

multicast_hops[RW]

Maximum number of hops for sent multicast packets (if using a multicast address in the broadcast list). The default is 1 (same as UDP broadcast).

multicast_interface[RW]

The interface index to send IPv6 multicast packets from.

port[RW]

The port that RingFinger will send query packets to.

primary[RW]

Contain the first advertised TupleSpace after #lookup_ring_any is called.

Public Class Methods

finger() click to toggle source

Creates a singleton RingFinger and looks for a RingServer. Returns the created RingFinger.

# File lib/rinda/ring.rb, line 285
def self.finger
  unless @@finger
    @@finger = self.new
    @@finger.lookup_ring_any
  end
  @@finger
end
new(broadcast_list=@@broadcast_list, port=Ring_PORT) click to toggle source

Creates a new RingFinger that will look for RingServers at port on the addresses in broadcast_list.

If broadcast_list contains a multicast address then multicast queries will be made using the given #multicast_hops and multicast_interface.

# File lib/rinda/ring.rb, line 341
def initialize(broadcast_list=@@broadcast_list, port=Ring_PORT)
  @broadcast_list = broadcast_list || ['localhost']
  @port = port
  @primary = nil
  @rings = []

  @multicast_hops = 1
  @multicast_interface = 0
end
primary() click to toggle source

Returns the first advertised TupleSpace.

# File lib/rinda/ring.rb, line 296
def self.primary
  finger.primary
end
to_a() click to toggle source

Contains all discovered TupleSpaces except for the primary.

# File lib/rinda/ring.rb, line 303
def self.to_a
  finger.to_a
end

Public Instance Methods

each() { |primary| ... } click to toggle source

Iterates over all discovered TupleSpaces starting with the primary.

# File lib/rinda/ring.rb, line 361
  def each
    lookup_ring_any unless @primary
    return unless @primary
    yield(@primary)
    @rings.each { |x| yield(x) }
  end

  ##
  # Looks up RingServers waiting +timeout+ seconds.  RingServers will be
  # given +block+ as a callback, which will be called with the remote
  # TupleSpace.

  def lookup_ring(timeout=5, &block)
    return lookup_ring_any(timeout) unless block_given?

    msg = Marshal.dump([[:lookup_ring, DRbObject.new(block)], timeout])
    @broadcast_list.each do |it|
      send_message(it, msg)
    end
    sleep(timeout)
  end

  ##
  # Returns the first found remote TupleSpace.  Any further recovered
  # TupleSpaces can be found by calling +to_a+.

  def lookup_ring_any(timeout=5)
    queue = Thread::Queue.new

    Thread.new do
      self.lookup_ring(timeout) do |ts|
        queue.push(ts)
      end
      queue.push(nil)
    end

    @primary = queue.pop
    raise('RingNotFound') if @primary.nil?

    Thread.new do
      while it = queue.pop
        @rings.push(it)
      end
    end

    @primary
  end

  ##
  # Creates a socket for +address+ with the appropriate multicast options
  # for multicast addresses.

  def make_socket(address) # :nodoc:
    addrinfo = Addrinfo.udp(address, @port)

    soc = Socket.new(addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol)
    begin
      if addrinfo.ipv4_multicast? then
        soc.setsockopt(Socket::Option.ipv4_multicast_loop(1))
        soc.setsockopt(Socket::Option.ipv4_multicast_ttl(@multicast_hops))
      elsif addrinfo.ipv6_multicast? then
        soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_LOOP, true)
        soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_HOPS,
                       [@multicast_hops].pack('I'))
        soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_IF,
                       [@multicast_interface].pack('I'))
      else
        soc.setsockopt(:SOL_SOCKET, :SO_BROADCAST, true)
      end

      soc.connect(addrinfo)
    rescue Exception
      soc.close
      raise
    end

    soc
  end

  def send_message(address, message) # :nodoc:
    soc = make_socket(address)

    soc.send(message, 0)
  rescue
    nil
  ensure
    soc.close if soc
  end

end
lookup_ring(timeout=5, &block) click to toggle source

Looks up RingServers waiting timeout seconds. RingServers will be given block as a callback, which will be called with the remote TupleSpace.

# File lib/rinda/ring.rb, line 373
def lookup_ring(timeout=5, &block)
  return lookup_ring_any(timeout) unless block_given?

  msg = Marshal.dump([[:lookup_ring, DRbObject.new(block)], timeout])
  @broadcast_list.each do |it|
    send_message(it, msg)
  end
  sleep(timeout)
end
lookup_ring_any(timeout=5) click to toggle source

Returns the first found remote TupleSpace. Any further recovered TupleSpaces can be found by calling to_a.

# File lib/rinda/ring.rb, line 387
def lookup_ring_any(timeout=5)
  queue = Thread::Queue.new

  Thread.new do
    self.lookup_ring(timeout) do |ts|
      queue.push(ts)
    end
    queue.push(nil)
  end

  @primary = queue.pop
  raise('RingNotFound') if @primary.nil?

  Thread.new do
    while it = queue.pop
      @rings.push(it)
    end
  end

  @primary
end
to_a() click to toggle source

Contains all discovered TupleSpaces except for the primary.

# File lib/rinda/ring.rb, line 354
def to_a
  @rings
end