rinda2 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 13d1278aefb6ed5124732435a552d304d7fb4138
4
- data.tar.gz: 0b8a86151cb6881f59eaf7d328f686ed8f401c0b
3
+ metadata.gz: f23287f91efcdf6b8dbdd11e8b0913c6739cf133
4
+ data.tar.gz: d556c379195055cec95457e55477bb11750c229a
5
5
  SHA512:
6
- metadata.gz: 125cd4ac651251f21a637431194837d15e617a87c599b1024ab9eb76336fb211e78dad59646307e06a6516f837238aacb6cdb8306c61cc3907834b3671a448c1
7
- data.tar.gz: 78ac6e59f23304a346a99f3eeffb868e64959248ce2cc3b1705ed6ad68399a85d3bc066582037ff71425b3657bcbfadea7fa1ddd93e1f4a3d999290399551de7
6
+ metadata.gz: 41702362cfae06f7822c6849861a5c21d6652888f99cfc3b8b34651664439be6be8c2a702eb9ba6b82ee87bf52bfea26df4c4638eb0f9fd67638ee794e20426c
7
+ data.tar.gz: 64afa0d0fc633c205596b21573ee68cb222f1d345d63c2d1a3db7f94184c0f6e6974ad5febb85fb25d5a2d74d8a1ddd93b61174863fcf108fd5c346089ac5325
@@ -1,7 +1,7 @@
1
- require "rinda2/version"
2
- require "rinda2/ring"
3
- require "rinda2/rinda"
4
- require "rinda2/tuplespace"
1
+ require_relative "rinda2/version"
2
+ require_relative "rinda2/ring"
3
+ require_relative "rinda2/rinda"
4
+ require_relative "rinda2/tuplespace"
5
5
 
6
6
  module Rinda2
7
7
  # Your code goes here...
@@ -3,10 +3,13 @@
3
3
  # Note: Rinda::Ring API is unstable.
4
4
  #
5
5
  require 'drb/drb'
6
- require 'rinda/rinda'
7
6
  require 'thread'
8
7
  require 'ipaddr'
9
8
 
9
+ require_relative 'rinda'
10
+ require_relative 'ring/finger'
11
+ require_relative 'ring/server'
12
+
10
13
  module Rinda
11
14
 
12
15
  ##
@@ -14,448 +17,6 @@ module Rinda
14
17
 
15
18
  Ring_PORT = 7647
16
19
 
17
- ##
18
- # A RingServer allows a Rinda::TupleSpace to be located via UDP broadcasts.
19
- # Default service location uses the following steps:
20
- #
21
- # 1. A RingServer begins listening on the network broadcast UDP address.
22
- # 2. A RingFinger sends a UDP packet containing the DRb URI where it will
23
- # listen for a reply.
24
- # 3. The RingServer receives the UDP packet and connects back to the
25
- # provided DRb URI with the DRb service.
26
- #
27
- # A RingServer requires a TupleSpace:
28
- #
29
- # ts = Rinda::TupleSpace.new
30
- # rs = Rinda::RingServer.new
31
- #
32
- # RingServer can also listen on multicast addresses for announcements. This
33
- # allows multiple RingServers to run on the same host. To use network
34
- # broadcast and multicast:
35
- #
36
- # ts = Rinda::TupleSpace.new
37
- # rs = Rinda::RingServer.new ts, %w[Socket::INADDR_ANY, 239.0.0.1 ff02::1]
38
-
39
- class RingServer
40
-
41
- include DRbUndumped
42
-
43
- ##
44
- # Special renewer for the RingServer to allow shutdown
45
-
46
- class Renewer # :nodoc:
47
- include DRbUndumped
48
-
49
- ##
50
- # Set to false to shutdown future requests using this Renewer
51
-
52
- attr_writer :renew
53
-
54
- def initialize # :nodoc:
55
- @renew = true
56
- end
57
-
58
- def renew # :nodoc:
59
- @renew ? 1 : true
60
- end
61
- end
62
-
63
- ##
64
- # Advertises +ts+ on the given +addresses+ at +port+.
65
- #
66
- # If +addresses+ is omitted only the UDP broadcast address is used.
67
- #
68
- # +addresses+ can contain multiple addresses. If a multicast address is
69
- # given in +addresses+ then the RingServer will listen for multicast
70
- # queries.
71
- #
72
- # If you use IPv4 multicast you may need to set an address of the inbound
73
- # interface which joins a multicast group.
74
- #
75
- # ts = Rinda::TupleSpace.new
76
- # rs = Rinda::RingServer.new(ts, [['239.0.0.1', '9.5.1.1']])
77
- #
78
- # You can set addresses as an Array Object. The first element of the
79
- # Array is a multicast address and the second is an inbound interface
80
- # address. If the second is omitted then '0.0.0.0' is used.
81
- #
82
- # If you use IPv6 multicast you may need to set both the local interface
83
- # address and the inbound interface index:
84
- #
85
- # rs = Rinda::RingServer.new(ts, [['ff02::1', '::1', 1]])
86
- #
87
- # The first element is a multicast address and the second is an inbound
88
- # interface address. The third is an inbound interface index.
89
- #
90
- # At this time there is no easy way to get an interface index by name.
91
- #
92
- # If the second is omitted then '::1' is used.
93
- # If the third is omitted then 0 (default interface) is used.
94
-
95
- def initialize(ts, addresses=[Socket::INADDR_ANY], port=Ring_PORT)
96
- @port = port
97
-
98
- if Integer === addresses then
99
- addresses, @port = [Socket::INADDR_ANY], addresses
100
- end
101
-
102
- @renewer = Renewer.new
103
-
104
- @ts = ts
105
- @sockets = []
106
- addresses.each do |address|
107
- if Array === address
108
- make_socket(*address)
109
- else
110
- make_socket(address)
111
- end
112
- end
113
-
114
- @w_services = write_services
115
- @r_service = reply_service
116
- end
117
-
118
- ##
119
- # Creates a socket at +address+
120
- #
121
- # If +address+ is multicast address then +interface_address+ and
122
- # +multicast_interface+ can be set as optional.
123
- #
124
- # A created socket is bound to +interface_address+. If you use IPv4
125
- # multicast then the interface of +interface_address+ is used as the
126
- # inbound interface. If +interface_address+ is omitted or nil then
127
- # '0.0.0.0' or '::1' is used.
128
- #
129
- # If you use IPv6 multicast then +multicast_interface+ is used as the
130
- # inbound interface. +multicast_interface+ is a network interface index.
131
- # If +multicast_interface+ is omitted then 0 (default interface) is used.
132
-
133
- def make_socket(address, interface_address=nil, multicast_interface=0)
134
- addrinfo = Addrinfo.udp(address, @port)
135
-
136
- socket = Socket.new(addrinfo.pfamily, addrinfo.socktype,
137
- addrinfo.protocol)
138
- @sockets << socket
139
-
140
- if addrinfo.ipv4_multicast? or addrinfo.ipv6_multicast? then
141
- if Socket.const_defined?(:SO_REUSEPORT) then
142
- socket.setsockopt(:SOCKET, :SO_REUSEPORT, true)
143
- else
144
- socket.setsockopt(:SOCKET, :SO_REUSEADDR, true)
145
- end
146
-
147
- if addrinfo.ipv4_multicast? then
148
- interface_address = '0.0.0.0' if interface_address.nil?
149
- socket.bind(Addrinfo.udp('0.0.0.0', @port))
150
-
151
- mreq = IPAddr.new(addrinfo.ip_address).hton +
152
- IPAddr.new(interface_address).hton
153
-
154
- socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, mreq)
155
- else
156
- interface_address = '::1' if interface_address.nil?
157
- socket.bind(Addrinfo.udp(interface_address, @port))
158
-
159
- mreq = IPAddr.new(addrinfo.ip_address).hton +
160
- [multicast_interface].pack('I')
161
-
162
- socket.setsockopt(:IPPROTO_IPV6, :IPV6_JOIN_GROUP, mreq)
163
- end
164
- else
165
- socket.bind(addrinfo)
166
- end
167
-
168
- socket
169
- end
170
-
171
- ##
172
- # Creates threads that pick up UDP packets and passes them to do_write for
173
- # decoding.
174
-
175
- def write_services
176
- @sockets.map do |s|
177
- Thread.new(s) do |socket|
178
- loop do
179
- msg = socket.recv(1024)
180
- do_write(msg)
181
- end
182
- end
183
- end
184
- end
185
-
186
- ##
187
- # Extracts the response URI from +msg+ and adds it to TupleSpace where it
188
- # will be picked up by +reply_service+ for notification.
189
-
190
- def do_write(msg)
191
- Thread.new do
192
- begin
193
- tuple, sec = Marshal.load(msg)
194
- @ts.write(tuple, sec)
195
- rescue
196
- end
197
- end
198
- end
199
-
200
- ##
201
- # Creates a thread that notifies waiting clients from the TupleSpace.
202
-
203
- def reply_service
204
- Thread.new do
205
- loop do
206
- do_reply
207
- end
208
- end
209
- end
210
-
211
- ##
212
- # Pulls lookup tuples out of the TupleSpace and sends their DRb object the
213
- # address of the local TupleSpace.
214
-
215
- def do_reply
216
- tuple = @ts.take([:lookup_ring, nil], @renewer)
217
- Thread.new { tuple[1].call(@ts) rescue nil}
218
- rescue
219
- end
220
-
221
- ##
222
- # Shuts down the RingServer
223
-
224
- def shutdown
225
- @renewer.renew = false
226
-
227
- @w_services.each do |thread|
228
- thread.kill
229
- thread.join
230
- end
231
-
232
- @sockets.each do |socket|
233
- socket.close
234
- end
235
-
236
- @r_service.kill
237
- @r_service.join
238
- end
239
-
240
- end
241
-
242
- ##
243
- # RingFinger is used by RingServer clients to discover the RingServer's
244
- # TupleSpace. Typically, all a client needs to do is call
245
- # RingFinger.primary to retrieve the remote TupleSpace, which it can then
246
- # begin using.
247
- #
248
- # To find the first available remote TupleSpace:
249
- #
250
- # Rinda::RingFinger.primary
251
- #
252
- # To create a RingFinger that broadcasts to a custom list:
253
- #
254
- # rf = Rinda::RingFinger.new ['localhost', '192.0.2.1']
255
- # rf.primary
256
- #
257
- # Rinda::RingFinger also understands multicast addresses and sets them up
258
- # properly. This allows you to run multiple RingServers on the same host:
259
- #
260
- # rf = Rinda::RingFinger.new ['239.0.0.1']
261
- # rf.primary
262
- #
263
- # You can set the hop count (or TTL) for multicast searches using
264
- # #multicast_hops.
265
- #
266
- # If you use IPv6 multicast you may need to set both an address and the
267
- # outbound interface index:
268
- #
269
- # rf = Rinda::RingFinger.new ['ff02::1']
270
- # rf.multicast_interface = 1
271
- # rf.primary
272
- #
273
- # At this time there is no easy way to get an interface index by name.
274
-
275
- class RingFinger
276
-
277
- @@broadcast_list = ['<broadcast>', 'localhost']
278
-
279
- @@finger = nil
280
-
281
- ##
282
- # Creates a singleton RingFinger and looks for a RingServer. Returns the
283
- # created RingFinger.
284
-
285
- def self.finger
286
- unless @@finger
287
- @@finger = self.new
288
- @@finger.lookup_ring_any
289
- end
290
- @@finger
291
- end
292
-
293
- ##
294
- # Returns the first advertised TupleSpace.
295
-
296
- def self.primary
297
- finger.primary
298
- end
299
-
300
- ##
301
- # Contains all discovered TupleSpaces except for the primary.
302
-
303
- def self.to_a
304
- finger.to_a
305
- end
306
-
307
- ##
308
- # The list of addresses where RingFinger will send query packets.
309
-
310
- attr_accessor :broadcast_list
311
-
312
- ##
313
- # Maximum number of hops for sent multicast packets (if using a multicast
314
- # address in the broadcast list). The default is 1 (same as UDP
315
- # broadcast).
316
-
317
- attr_accessor :multicast_hops
318
-
319
- ##
320
- # The interface index to send IPv6 multicast packets from.
321
-
322
- attr_accessor :multicast_interface
323
-
324
- ##
325
- # The port that RingFinger will send query packets to.
326
-
327
- attr_accessor :port
328
-
329
- ##
330
- # Contain the first advertised TupleSpace after lookup_ring_any is called.
331
-
332
- attr_accessor :primary
333
-
334
- ##
335
- # Creates a new RingFinger that will look for RingServers at +port+ on
336
- # the addresses in +broadcast_list+.
337
- #
338
- # If +broadcast_list+ contains a multicast address then multicast queries
339
- # will be made using the given multicast_hops and multicast_interface.
340
-
341
- def initialize(broadcast_list=@@broadcast_list, port=Ring_PORT)
342
- @broadcast_list = broadcast_list || ['localhost']
343
- @port = port
344
- @primary = nil
345
- @rings = []
346
-
347
- @multicast_hops = 1
348
- @multicast_interface = 0
349
- end
350
-
351
- ##
352
- # Contains all discovered TupleSpaces except for the primary.
353
-
354
- def to_a
355
- @rings
356
- end
357
-
358
- ##
359
- # Iterates over all discovered TupleSpaces starting with the primary.
360
-
361
- def each
362
- lookup_ring_any unless @primary
363
- return unless @primary
364
- yield(@primary)
365
- @rings.each { |x| yield(x) }
366
- end
367
-
368
- ##
369
- # Looks up RingServers waiting +timeout+ seconds. RingServers will be
370
- # given +block+ as a callback, which will be called with the remote
371
- # TupleSpace.
372
-
373
- def lookup_ring(timeout=5, &block)
374
- return lookup_ring_any(timeout) unless block_given?
375
-
376
- msg = Marshal.dump([[:lookup_ring, DRbObject.new(block)], timeout])
377
- @broadcast_list.each do |it|
378
- send_message(it, msg)
379
- end
380
- sleep(timeout)
381
- end
382
-
383
- ##
384
- # Returns the first found remote TupleSpace. Any further recovered
385
- # TupleSpaces can be found by calling +to_a+.
386
-
387
- def lookup_ring_any(timeout=5)
388
- queue = Queue.new
389
-
390
- Thread.new do
391
- self.lookup_ring(timeout) do |ts|
392
- queue.push(ts)
393
- end
394
- queue.push(nil)
395
- end
396
-
397
- @primary = queue.pop
398
- raise('RingNotFound') if @primary.nil?
399
-
400
- Thread.new do
401
- while it = queue.pop
402
- @rings.push(it)
403
- end
404
- end
405
-
406
- @primary
407
- end
408
-
409
- ##
410
- # Creates a socket for +address+ with the appropriate multicast options
411
- # for multicast addresses.
412
-
413
- def make_socket(address, interface_address = nil) # :nodoc:
414
- addrinfo = Addrinfo.udp(address, @port)
415
-
416
- soc = Socket.new(addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol)
417
- begin
418
- if addrinfo.ipv4_multicast? then
419
- interface_address ||= '0.0.0.0'
420
- soc.setsockopt(Socket::Option.ipv4_multicast_loop(1))
421
- soc.setsockopt(Socket::Option.ipv4_multicast_ttl(@multicast_hops))
422
- ifreq = IPAddr.new(interface_address).hton
423
- soc.setsockopt(:IPPROTO_IP, :IP_MULTICAST_IF, ifreq)
424
- elsif addrinfo.ipv6_multicast? then
425
- soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_LOOP, true)
426
- soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_HOPS,
427
- [@multicast_hops].pack('I'))
428
- soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_IF,
429
- [@multicast_interface].pack('I'))
430
- else
431
- soc.setsockopt(:SOL_SOCKET, :SO_BROADCAST, true)
432
- end
433
-
434
- soc.connect(addrinfo)
435
- rescue Exception
436
- soc.close
437
- raise
438
- end
439
-
440
- soc
441
- end
442
-
443
- def send_message(address, message) # :nodoc:
444
- soc = if Array === address
445
- make_socket(*address)
446
- else
447
- make_socket(address)
448
- end
449
-
450
- soc.send(message, 0)
451
- rescue
452
- nil
453
- ensure
454
- soc.close if soc
455
- end
456
-
457
- end
458
-
459
20
  ##
460
21
  # RingProvider uses a RingServer advertised TupleSpace as a name service.
461
22
  # TupleSpace clients can register themselves with the remote TupleSpace and
@@ -0,0 +1,222 @@
1
+ module Rinda
2
+
3
+ ##
4
+ # RingFinger is used by RingServer clients to discover the RingServer's
5
+ # TupleSpace. Typically, all a client needs to do is call
6
+ # RingFinger.primary to retrieve the remote TupleSpace, which it can then
7
+ # begin using.
8
+ #
9
+ # To find the first available remote TupleSpace:
10
+ #
11
+ # Rinda::RingFinger.primary
12
+ #
13
+ # To create a RingFinger that broadcasts to a custom list:
14
+ #
15
+ # rf = Rinda::RingFinger.new ['localhost', '192.0.2.1']
16
+ # rf.primary
17
+ #
18
+ # Rinda::RingFinger also understands multicast addresses and sets them up
19
+ # properly. This allows you to run multiple RingServers on the same host:
20
+ #
21
+ # rf = Rinda::RingFinger.new ['239.0.0.1']
22
+ # rf.primary
23
+ #
24
+ # You can set the hop count (or TTL) for multicast searches using
25
+ # #multicast_hops.
26
+ #
27
+ # If you use IPv6 multicast you may need to set both an address and the
28
+ # outbound interface index:
29
+ #
30
+ # rf = Rinda::RingFinger.new ['ff02::1']
31
+ # rf.multicast_interface = 1
32
+ # rf.primary
33
+ #
34
+ # At this time there is no easy way to get an interface index by name.
35
+
36
+ class RingFinger
37
+
38
+ @@broadcast_list = ['<broadcast>', 'localhost']
39
+
40
+ @@finger = nil
41
+
42
+ ##
43
+ # Creates a singleton RingFinger and looks for a RingServer. Returns the
44
+ # created RingFinger.
45
+
46
+ def self.finger
47
+ unless @@finger
48
+ @@finger = self.new
49
+ @@finger.lookup_ring_any
50
+ end
51
+ @@finger
52
+ end
53
+
54
+ ##
55
+ # Returns the first advertised TupleSpace.
56
+
57
+ def self.primary
58
+ finger.primary
59
+ end
60
+
61
+ ##
62
+ # Contains all discovered TupleSpaces except for the primary.
63
+
64
+ def self.to_a
65
+ finger.to_a
66
+ end
67
+
68
+ ##
69
+ # The list of addresses where RingFinger will send query packets.
70
+
71
+ attr_accessor :broadcast_list
72
+
73
+ ##
74
+ # Maximum number of hops for sent multicast packets (if using a multicast
75
+ # address in the broadcast list). The default is 1 (same as UDP
76
+ # broadcast).
77
+
78
+ attr_accessor :multicast_hops
79
+
80
+ ##
81
+ # The interface index to send IPv6 multicast packets from.
82
+
83
+ attr_accessor :multicast_interface
84
+
85
+ ##
86
+ # The port that RingFinger will send query packets to.
87
+
88
+ attr_accessor :port
89
+
90
+ ##
91
+ # Contain the first advertised TupleSpace after lookup_ring_any is called.
92
+
93
+ attr_accessor :primary
94
+
95
+ ##
96
+ # Creates a new RingFinger that will look for RingServers at +port+ on
97
+ # the addresses in +broadcast_list+.
98
+ #
99
+ # If +broadcast_list+ contains a multicast address then multicast queries
100
+ # will be made using the given multicast_hops and multicast_interface.
101
+
102
+ def initialize(broadcast_list=@@broadcast_list, port=Ring_PORT)
103
+ @broadcast_list = broadcast_list || ['localhost']
104
+ @port = port
105
+ @primary = nil
106
+ @rings = []
107
+
108
+ @multicast_hops = 1
109
+ @multicast_interface = 0
110
+ end
111
+
112
+ ##
113
+ # Contains all discovered TupleSpaces except for the primary.
114
+
115
+ def to_a
116
+ @rings
117
+ end
118
+
119
+ ##
120
+ # Iterates over all discovered TupleSpaces starting with the primary.
121
+
122
+ def each
123
+ Enumerator.new do |y|
124
+ lookup_ring_any unless @primary
125
+ return unless @primary
126
+ y << @primary
127
+ @rings.each { |x| y << x }
128
+ end
129
+ end
130
+
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
+
136
+ def lookup_ring(timeout=5, &block)
137
+ return lookup_ring_any(timeout) unless block_given?
138
+
139
+ msg = Marshal.dump([[:lookup_ring, DRbObject.new(block)], timeout])
140
+ @broadcast_list.each do |it|
141
+ send_message(it, msg)
142
+ end
143
+ sleep(timeout)
144
+ end
145
+
146
+ ##
147
+ # Returns the first found remote TupleSpace. Any further recovered
148
+ # TupleSpaces can be found by calling +to_a+.
149
+
150
+ def lookup_ring_any(timeout=5)
151
+ queue = Queue.new
152
+
153
+ Thread.new do
154
+ self.lookup_ring(timeout) do |ts|
155
+ queue.push(ts)
156
+ end
157
+ queue.push(nil)
158
+ end
159
+
160
+ @primary = queue.pop
161
+ raise('RingNotFound') if @primary.nil?
162
+
163
+ Thread.new do
164
+ while it = queue.pop
165
+ @rings.push(it)
166
+ end
167
+ end
168
+
169
+ @primary
170
+ end
171
+
172
+ ##
173
+ # Creates a socket for +address+ with the appropriate multicast options
174
+ # for multicast addresses.
175
+
176
+ def make_socket(address, interface_address = nil) # :nodoc:
177
+ addrinfo = Addrinfo.udp(address, @port)
178
+
179
+ soc = Socket.new(addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol)
180
+ begin
181
+ if addrinfo.ipv4_multicast? then
182
+ interface_address ||= '0.0.0.0'
183
+ soc.setsockopt(Socket::Option.ipv4_multicast_loop(1))
184
+ soc.setsockopt(Socket::Option.ipv4_multicast_ttl(@multicast_hops))
185
+ ifreq = IPAddr.new(interface_address).hton
186
+ soc.setsockopt(:IPPROTO_IP, :IP_MULTICAST_IF, ifreq)
187
+ elsif addrinfo.ipv6_multicast? then
188
+ soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_LOOP, true)
189
+ soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_HOPS,
190
+ [@multicast_hops].pack('I'))
191
+ soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_IF,
192
+ [@multicast_interface].pack('I'))
193
+ else
194
+ soc.setsockopt(:SOL_SOCKET, :SO_BROADCAST, true)
195
+ end
196
+
197
+ soc.connect(addrinfo)
198
+ rescue Exception
199
+ soc.close
200
+ raise
201
+ end
202
+
203
+ soc
204
+ end
205
+
206
+ def send_message(address, message) # :nodoc:
207
+ soc = if Array === address
208
+ make_socket(*address)
209
+ else
210
+ make_socket(address)
211
+ end
212
+
213
+ soc.send(message, 0)
214
+ rescue
215
+ nil
216
+ ensure
217
+ soc.close if soc
218
+ end
219
+
220
+ end
221
+
222
+ end
@@ -0,0 +1,228 @@
1
+ module Rinda
2
+
3
+ ##
4
+ # A RingServer allows a Rinda::TupleSpace to be located via UDP broadcasts.
5
+ # Default service location uses the following steps:
6
+ #
7
+ # 1. A RingServer begins listening on the network broadcast UDP address.
8
+ # 2. A RingFinger sends a UDP packet containing the DRb URI where it will
9
+ # listen for a reply.
10
+ # 3. The RingServer receives the UDP packet and connects back to the
11
+ # provided DRb URI with the DRb service.
12
+ #
13
+ # A RingServer requires a TupleSpace:
14
+ #
15
+ # ts = Rinda::TupleSpace.new
16
+ # rs = Rinda::RingServer.new
17
+ #
18
+ # RingServer can also listen on multicast addresses for announcements. This
19
+ # allows multiple RingServers to run on the same host. To use network
20
+ # broadcast and multicast:
21
+ #
22
+ # ts = Rinda::TupleSpace.new
23
+ # rs = Rinda::RingServer.new ts, %w[Socket::INADDR_ANY, 239.0.0.1 ff02::1]
24
+
25
+ class RingServer
26
+
27
+ include DRbUndumped
28
+
29
+ ##
30
+ # Special renewer for the RingServer to allow shutdown
31
+
32
+ class Renewer # :nodoc:
33
+ include DRbUndumped
34
+
35
+ ##
36
+ # Set to false to shutdown future requests using this Renewer
37
+
38
+ attr_writer :renew
39
+
40
+ def initialize # :nodoc:
41
+ @renew = true
42
+ end
43
+
44
+ def renew # :nodoc:
45
+ @renew ? 1 : true
46
+ end
47
+ end
48
+
49
+ ##
50
+ # Advertises +ts+ on the given +addresses+ at +port+.
51
+ #
52
+ # If +addresses+ is omitted only the UDP broadcast address is used.
53
+ #
54
+ # +addresses+ can contain multiple addresses. If a multicast address is
55
+ # given in +addresses+ then the RingServer will listen for multicast
56
+ # queries.
57
+ #
58
+ # If you use IPv4 multicast you may need to set an address of the inbound
59
+ # interface which joins a multicast group.
60
+ #
61
+ # ts = Rinda::TupleSpace.new
62
+ # rs = Rinda::RingServer.new(ts, [['239.0.0.1', '9.5.1.1']])
63
+ #
64
+ # You can set addresses as an Array Object. The first element of the
65
+ # Array is a multicast address and the second is an inbound interface
66
+ # address. If the second is omitted then '0.0.0.0' is used.
67
+ #
68
+ # If you use IPv6 multicast you may need to set both the local interface
69
+ # address and the inbound interface index:
70
+ #
71
+ # rs = Rinda::RingServer.new(ts, [['ff02::1', '::1', 1]])
72
+ #
73
+ # The first element is a multicast address and the second is an inbound
74
+ # interface address. The third is an inbound interface index.
75
+ #
76
+ # At this time there is no easy way to get an interface index by name.
77
+ #
78
+ # If the second is omitted then '::1' is used.
79
+ # If the third is omitted then 0 (default interface) is used.
80
+
81
+ def initialize(ts, addresses=[Socket::INADDR_ANY], port=Ring_PORT)
82
+ @port = port
83
+
84
+ if Integer === addresses then
85
+ addresses, @port = [Socket::INADDR_ANY], addresses
86
+ end
87
+
88
+ @renewer = Renewer.new
89
+
90
+ @ts = ts
91
+ @sockets = []
92
+ addresses.each do |address|
93
+ if Array === address
94
+ make_socket(*address)
95
+ else
96
+ make_socket(address)
97
+ end
98
+ end
99
+
100
+ @w_services = write_services
101
+ @r_service = reply_service
102
+ end
103
+
104
+ ##
105
+ # Creates a socket at +address+
106
+ #
107
+ # If +address+ is multicast address then +interface_address+ and
108
+ # +multicast_interface+ can be set as optional.
109
+ #
110
+ # A created socket is bound to +interface_address+. If you use IPv4
111
+ # multicast then the interface of +interface_address+ is used as the
112
+ # inbound interface. If +interface_address+ is omitted or nil then
113
+ # '0.0.0.0' or '::1' is used.
114
+ #
115
+ # If you use IPv6 multicast then +multicast_interface+ is used as the
116
+ # inbound interface. +multicast_interface+ is a network interface index.
117
+ # If +multicast_interface+ is omitted then 0 (default interface) is used.
118
+
119
+ def make_socket(address, interface_address=nil, multicast_interface=0)
120
+ addrinfo = Addrinfo.udp(address, @port)
121
+
122
+ socket = Socket.new(addrinfo.pfamily, addrinfo.socktype,
123
+ addrinfo.protocol)
124
+ @sockets << socket
125
+
126
+ if addrinfo.ipv4_multicast? or addrinfo.ipv6_multicast? then
127
+ if Socket.const_defined?(:SO_REUSEPORT) then
128
+ socket.setsockopt(:SOCKET, :SO_REUSEPORT, true)
129
+ else
130
+ socket.setsockopt(:SOCKET, :SO_REUSEADDR, true)
131
+ end
132
+
133
+ if addrinfo.ipv4_multicast? then
134
+ interface_address = '0.0.0.0' if interface_address.nil?
135
+ socket.bind(Addrinfo.udp('0.0.0.0', @port))
136
+
137
+ mreq = IPAddr.new(addrinfo.ip_address).hton +
138
+ IPAddr.new(interface_address).hton
139
+
140
+ socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, mreq)
141
+ else
142
+ interface_address = '::1' if interface_address.nil?
143
+ socket.bind(Addrinfo.udp(interface_address, @port))
144
+
145
+ mreq = IPAddr.new(addrinfo.ip_address).hton +
146
+ [multicast_interface].pack('I')
147
+
148
+ socket.setsockopt(:IPPROTO_IPV6, :IPV6_JOIN_GROUP, mreq)
149
+ end
150
+ else
151
+ socket.bind(addrinfo)
152
+ end
153
+
154
+ socket
155
+ end
156
+
157
+ ##
158
+ # Creates threads that pick up UDP packets and passes them to do_write for
159
+ # decoding.
160
+
161
+ def write_services
162
+ @sockets.map do |s|
163
+ Thread.new(s) do |socket|
164
+ loop do
165
+ msg = socket.recv(9000)
166
+ do_write(msg)
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ ##
173
+ # Extracts the response URI from +msg+ and adds it to TupleSpace where it
174
+ # will be picked up by +reply_service+ for notification.
175
+
176
+ def do_write(msg)
177
+ Thread.new do
178
+ begin
179
+ tuple, sec = Marshal.load(msg)
180
+ @ts.write(tuple, sec)
181
+ rescue
182
+ end
183
+ end
184
+ end
185
+
186
+ ##
187
+ # Creates a thread that notifies waiting clients from the TupleSpace.
188
+
189
+ def reply_service
190
+ Thread.new do
191
+ loop do
192
+ do_reply
193
+ end
194
+ end
195
+ end
196
+
197
+ ##
198
+ # Pulls lookup tuples out of the TupleSpace and sends their DRb object the
199
+ # address of the local TupleSpace.
200
+
201
+ def do_reply
202
+ tuple = @ts.take([:lookup_ring, nil], @renewer)
203
+ Thread.new { tuple[1].call(@ts) rescue nil}
204
+ rescue
205
+ end
206
+
207
+ ##
208
+ # Shuts down the RingServer
209
+
210
+ def shutdown
211
+ @renewer.renew = false
212
+
213
+ @w_services.each do |thread|
214
+ thread.kill
215
+ thread.join
216
+ end
217
+
218
+ @sockets.each do |socket|
219
+ socket.close
220
+ end
221
+
222
+ @r_service.kill
223
+ @r_service.join
224
+ end
225
+
226
+ end
227
+
228
+ end
@@ -2,8 +2,8 @@
2
2
  require 'monitor'
3
3
  require 'thread'
4
4
  require 'drb/drb'
5
- require 'rinda/rinda'
6
5
  require 'forwardable'
6
+ require_relative 'rinda'
7
7
 
8
8
  module Rinda
9
9
 
@@ -639,4 +639,3 @@ module Rinda
639
639
  end
640
640
 
641
641
  end
642
-
@@ -1,3 +1,3 @@
1
1
  module Rinda2
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rinda2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Baum
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-12 00:00:00.000000000 Z
11
+ date: 2016-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -55,6 +55,8 @@ files:
55
55
  - lib/rinda2.rb
56
56
  - lib/rinda2/rinda.rb
57
57
  - lib/rinda2/ring.rb
58
+ - lib/rinda2/ring/finger.rb
59
+ - lib/rinda2/ring/server.rb
58
60
  - lib/rinda2/tuplespace.rb
59
61
  - lib/rinda2/version.rb
60
62
  - rinda2.gemspec