/tools/Ruby/lib/ruby/1.8/rinda/ring.rb

http://github.com/agross/netopenspace · Ruby · 271 lines · 143 code · 57 blank · 71 comment · 6 complexity · ed8c60f79cef9eb1f2019173fe3e7e0c MD5 · raw file

  1. #
  2. # Note: Rinda::Ring API is unstable.
  3. #
  4. require 'drb/drb'
  5. require 'rinda/rinda'
  6. require 'thread'
  7. module Rinda
  8. ##
  9. # The default port Ring discovery will use.
  10. Ring_PORT = 7647
  11. ##
  12. # A RingServer allows a Rinda::TupleSpace to be located via UDP broadcasts.
  13. # Service location uses the following steps:
  14. #
  15. # 1. A RingServer begins listening on the broadcast UDP address.
  16. # 2. A RingFinger sends a UDP packet containing the DRb URI where it will
  17. # listen for a reply.
  18. # 3. The RingServer receives the UDP packet and connects back to the
  19. # provided DRb URI with the DRb service.
  20. class RingServer
  21. include DRbUndumped
  22. ##
  23. # Advertises +ts+ on the UDP broadcast address at +port+.
  24. def initialize(ts, port=Ring_PORT)
  25. @ts = ts
  26. @soc = UDPSocket.open
  27. @soc.bind('', port)
  28. @w_service = write_service
  29. @r_service = reply_service
  30. end
  31. ##
  32. # Creates a thread that picks up UDP packets and passes them to do_write
  33. # for decoding.
  34. def write_service
  35. Thread.new do
  36. loop do
  37. msg = @soc.recv(1024)
  38. do_write(msg)
  39. end
  40. end
  41. end
  42. ##
  43. # Extracts the response URI from +msg+ and adds it to TupleSpace where it
  44. # will be picked up by +reply_service+ for notification.
  45. def do_write(msg)
  46. Thread.new do
  47. begin
  48. tuple, sec = Marshal.load(msg)
  49. @ts.write(tuple, sec)
  50. rescue
  51. end
  52. end
  53. end
  54. ##
  55. # Creates a thread that notifies waiting clients from the TupleSpace.
  56. def reply_service
  57. Thread.new do
  58. loop do
  59. do_reply
  60. end
  61. end
  62. end
  63. ##
  64. # Pulls lookup tuples out of the TupleSpace and sends their DRb object the
  65. # address of the local TupleSpace.
  66. def do_reply
  67. tuple = @ts.take([:lookup_ring, nil])
  68. Thread.new { tuple[1].call(@ts) rescue nil}
  69. rescue
  70. end
  71. end
  72. ##
  73. # RingFinger is used by RingServer clients to discover the RingServer's
  74. # TupleSpace. Typically, all a client needs to do is call
  75. # RingFinger.primary to retrieve the remote TupleSpace, which it can then
  76. # begin using.
  77. class RingFinger
  78. @@broadcast_list = ['<broadcast>', 'localhost']
  79. @@finger = nil
  80. ##
  81. # Creates a singleton RingFinger and looks for a RingServer. Returns the
  82. # created RingFinger.
  83. def self.finger
  84. unless @@finger
  85. @@finger = self.new
  86. @@finger.lookup_ring_any
  87. end
  88. @@finger
  89. end
  90. ##
  91. # Returns the first advertised TupleSpace.
  92. def self.primary
  93. finger.primary
  94. end
  95. ##
  96. # Contains all discovered TupleSpaces except for the primary.
  97. def self.to_a
  98. finger.to_a
  99. end
  100. ##
  101. # The list of addresses where RingFinger will send query packets.
  102. attr_accessor :broadcast_list
  103. ##
  104. # The port that RingFinger will send query packets to.
  105. attr_accessor :port
  106. ##
  107. # Contain the first advertised TupleSpace after lookup_ring_any is called.
  108. attr_accessor :primary
  109. ##
  110. # Creates a new RingFinger that will look for RingServers at +port+ on
  111. # the addresses in +broadcast_list+.
  112. def initialize(broadcast_list=@@broadcast_list, port=Ring_PORT)
  113. @broadcast_list = broadcast_list || ['localhost']
  114. @port = port
  115. @primary = nil
  116. @rings = []
  117. end
  118. ##
  119. # Contains all discovered TupleSpaces except for the primary.
  120. def to_a
  121. @rings
  122. end
  123. ##
  124. # Iterates over all discovered TupleSpaces starting with the primary.
  125. def each
  126. lookup_ring_any unless @primary
  127. return unless @primary
  128. yield(@primary)
  129. @rings.each { |x| yield(x) }
  130. end
  131. ##
  132. # Looks up RingServers waiting +timeout+ seconds. RingServers will be
  133. # given +block+ as a callback, which will be called with the remote
  134. # TupleSpace.
  135. def lookup_ring(timeout=5, &block)
  136. return lookup_ring_any(timeout) unless block_given?
  137. msg = Marshal.dump([[:lookup_ring, DRbObject.new(block)], timeout])
  138. @broadcast_list.each do |it|
  139. soc = UDPSocket.open
  140. begin
  141. soc.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
  142. soc.send(msg, 0, it, @port)
  143. rescue
  144. nil
  145. ensure
  146. soc.close
  147. end
  148. end
  149. sleep(timeout)
  150. end
  151. ##
  152. # Returns the first found remote TupleSpace. Any further recovered
  153. # TupleSpaces can be found by calling +to_a+.
  154. def lookup_ring_any(timeout=5)
  155. queue = Queue.new
  156. th = Thread.new do
  157. self.lookup_ring(timeout) do |ts|
  158. queue.push(ts)
  159. end
  160. queue.push(nil)
  161. while it = queue.pop
  162. @rings.push(it)
  163. end
  164. end
  165. @primary = queue.pop
  166. raise('RingNotFound') if @primary.nil?
  167. @primary
  168. end
  169. end
  170. ##
  171. # RingProvider uses a RingServer advertised TupleSpace as a name service.
  172. # TupleSpace clients can register themselves with the remote TupleSpace and
  173. # look up other provided services via the remote TupleSpace.
  174. #
  175. # Services are registered with a tuple of the format [:name, klass,
  176. # DRbObject, description].
  177. class RingProvider
  178. ##
  179. # Creates a RingProvider that will provide a +klass+ service running on
  180. # +front+, with a +description+. +renewer+ is optional.
  181. def initialize(klass, front, desc, renewer = nil)
  182. @tuple = [:name, klass, front, desc]
  183. @renewer = renewer || Rinda::SimpleRenewer.new
  184. end
  185. ##
  186. # Advertises this service on the primary remote TupleSpace.
  187. def provide
  188. ts = Rinda::RingFinger.primary
  189. ts.write(@tuple, @renewer)
  190. end
  191. end
  192. end
  193. if __FILE__ == $0
  194. DRb.start_service
  195. case ARGV.shift
  196. when 's'
  197. require 'rinda/tuplespace'
  198. ts = Rinda::TupleSpace.new
  199. place = Rinda::RingServer.new(ts)
  200. $stdin.gets
  201. when 'w'
  202. finger = Rinda::RingFinger.new(nil)
  203. finger.lookup_ring do |ts|
  204. p ts
  205. ts.write([:hello, :world])
  206. end
  207. when 'r'
  208. finger = Rinda::RingFinger.new(nil)
  209. finger.lookup_ring do |ts|
  210. p ts
  211. p ts.take([nil, nil])
  212. end
  213. end
  214. end