archipelago 0.1.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.
- data/GPL-2 +339 -0
- data/README +39 -0
- data/TODO +3 -0
- data/lib/archipelago.rb +24 -0
- data/lib/current.rb +128 -0
- data/lib/disco.rb +548 -0
- data/lib/hashish.rb +199 -0
- data/lib/pirate.rb +205 -0
- data/lib/tranny.rb +650 -0
- data/lib/treasure.rb +679 -0
- data/profiles/1000xChest#join!-prepare!-commit!.rb +19 -0
- data/profiles/1000xDubloon#[]=(t).rb +19 -0
- data/profiles/1000xDubloon#method_missing(t).rb +21 -0
- data/profiles/README +3 -0
- data/profiles/profile_helper.rb +25 -0
- data/scripts/chest.rb +20 -0
- data/scripts/console +3 -0
- data/scripts/pirate.rb +6 -0
- data/scripts/tranny.rb +20 -0
- data/tests/current_test.rb +28 -0
- data/tests/disco_test.rb +179 -0
- data/tests/pirate_test.rb +75 -0
- data/tests/test_helper.rb +60 -0
- data/tests/tranny_test.rb +70 -0
- data/tests/treasure_test.rb +257 -0
- metadata +69 -0
data/lib/disco.rb
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
# Archipelago - a distributed computing toolkit for ruby
|
|
2
|
+
# Copyright (C) 2006 Martin Kihlgren <zond at troja dot ath dot cx>
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or
|
|
5
|
+
# modify it under the terms of the GNU General Public License
|
|
6
|
+
# as published by the Free Software Foundation; either version 2
|
|
7
|
+
# of the License, or (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software
|
|
16
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
17
|
+
|
|
18
|
+
require 'socket'
|
|
19
|
+
require 'thread'
|
|
20
|
+
require 'ipaddr'
|
|
21
|
+
require 'pp'
|
|
22
|
+
require 'current'
|
|
23
|
+
require 'drb'
|
|
24
|
+
require 'set'
|
|
25
|
+
|
|
26
|
+
module Archipelago
|
|
27
|
+
|
|
28
|
+
module Disco
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# Default address to use.
|
|
32
|
+
#
|
|
33
|
+
ADDRESS = "234.2.4.2"
|
|
34
|
+
#
|
|
35
|
+
# Default port to use.
|
|
36
|
+
#
|
|
37
|
+
PORT = 25242
|
|
38
|
+
#
|
|
39
|
+
# Default port range to use for unicast.
|
|
40
|
+
#
|
|
41
|
+
UNIPORTS = 25243..26243
|
|
42
|
+
#
|
|
43
|
+
# Default lookup timeout.
|
|
44
|
+
#
|
|
45
|
+
LOOKUP_TIMEOUT = 10
|
|
46
|
+
#
|
|
47
|
+
# Default initial pause between resending lookup queries.
|
|
48
|
+
# Will be doubled for each resend.
|
|
49
|
+
#
|
|
50
|
+
INITIAL_LOOKUP_STANDOFF = 0.1
|
|
51
|
+
#
|
|
52
|
+
# Default pause between trying to validate all services we
|
|
53
|
+
# know about.
|
|
54
|
+
#
|
|
55
|
+
VALIDATION_INTERVAL = 60
|
|
56
|
+
#
|
|
57
|
+
# Only save stuff that we KNOW we want.
|
|
58
|
+
#
|
|
59
|
+
THRIFTY_CACHING = true
|
|
60
|
+
#
|
|
61
|
+
# Only reply to the one actually asking about a service.
|
|
62
|
+
#
|
|
63
|
+
THRIFTY_REPLYING = true
|
|
64
|
+
#
|
|
65
|
+
# Dont send on publish, only on query.
|
|
66
|
+
#
|
|
67
|
+
THRIFTY_PUBLISHING = false
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# A module to simplify publishing services.
|
|
71
|
+
#
|
|
72
|
+
# If you include it you can use the publish! method
|
|
73
|
+
# at your convenience.
|
|
74
|
+
#
|
|
75
|
+
# If you want to customize the publishing related behaviour you can
|
|
76
|
+
# call <b>initialize_publishable</b> with a Hash of options.
|
|
77
|
+
#
|
|
78
|
+
# See Archipelago::Treasure::Chest or Archipelago::Tranny::Manager for examples.
|
|
79
|
+
#
|
|
80
|
+
# It will store the service_id of this service in a directory beside this
|
|
81
|
+
# file (publishable.rb) named as the class you include into unless you
|
|
82
|
+
# define <b>@persistence_provider</b> before you call <b>initialize_publishable</b>.
|
|
83
|
+
#
|
|
84
|
+
module Publishable
|
|
85
|
+
|
|
86
|
+
#
|
|
87
|
+
# Will initialize this instance with @service_description and @jockey_options
|
|
88
|
+
# and merge these with the optionally given <i>:service_description</i> and
|
|
89
|
+
# <i>:jockey_options</i>.
|
|
90
|
+
#
|
|
91
|
+
def initialize_publishable(options = {})
|
|
92
|
+
@service_description = {
|
|
93
|
+
:service_id => service_id,
|
|
94
|
+
:validator => DRbObject.new(self),
|
|
95
|
+
:service => DRbObject.new(self),
|
|
96
|
+
:class => self.class.name
|
|
97
|
+
}.merge(options[:service_description] || {})
|
|
98
|
+
@jockey_options = options[:jockey_options] || {}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
#
|
|
102
|
+
# Create an Archipelago::Disco::Jockey for this instance using @jockey_options
|
|
103
|
+
# or optionally given <i>:jockey_options</i>.
|
|
104
|
+
#
|
|
105
|
+
# Will publish this service using @service_description or optionally given
|
|
106
|
+
# <i>:service_description</i>.
|
|
107
|
+
#
|
|
108
|
+
def publish!(options = {})
|
|
109
|
+
@jockey ||= Archipelago::Disco::Jockey.new(@jockey_options.merge(options[:jockey_options] || {}))
|
|
110
|
+
@jockey.publish(Archipelago::Disco::Record.new(@service_description.merge(options[:service_description] || {})))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
#
|
|
114
|
+
# We are always valid if we are able to reply.
|
|
115
|
+
#
|
|
116
|
+
def valid?
|
|
117
|
+
true
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
#
|
|
121
|
+
# Returns our semi-unique id so that we can be found again.
|
|
122
|
+
#
|
|
123
|
+
def service_id
|
|
124
|
+
#
|
|
125
|
+
# The provider of happy magic persistent hashes of different kinds.
|
|
126
|
+
#
|
|
127
|
+
@persistence_provider ||= Archipelago::Hashish::BerkeleyHashishProvider.new(Pathname.new(File.expand_path(__FILE__)).parent.join(self.class.name + ".db"))
|
|
128
|
+
#
|
|
129
|
+
# Stuff that didnt fit in any of the other databases.
|
|
130
|
+
#
|
|
131
|
+
@metadata ||= @persistence_provider.get_hashish("metadata")
|
|
132
|
+
service_id = @metadata["service_id"]
|
|
133
|
+
unless service_id
|
|
134
|
+
host = "#{Socket::gethostbyname(Socket::gethostname)[0]}" rescue "localhost"
|
|
135
|
+
service_id = @metadata["service_id"] ||= Digest::SHA1.hexdigest("#{host}:#{Time.new.to_f}:#{self.object_id}:#{rand(1 << 32)}")
|
|
136
|
+
end
|
|
137
|
+
return service_id
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
#
|
|
143
|
+
# A mock validator to be used for dumb systems that dont want
|
|
144
|
+
# to validate.
|
|
145
|
+
#
|
|
146
|
+
class MockValidator
|
|
147
|
+
def valid?
|
|
148
|
+
true
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
#
|
|
153
|
+
# A Hash-like description of a service.
|
|
154
|
+
#
|
|
155
|
+
class ServiceDescription
|
|
156
|
+
IGNORABLE_ATTRIBUTES = Set[:unicast_reply]
|
|
157
|
+
attr_reader :attributes
|
|
158
|
+
#
|
|
159
|
+
# Initialize this service description with a hash
|
|
160
|
+
# that describes its attributes.
|
|
161
|
+
#
|
|
162
|
+
def initialize(hash = {})
|
|
163
|
+
@attributes = hash
|
|
164
|
+
end
|
|
165
|
+
#
|
|
166
|
+
# Forwards as much as possible to our Hash.
|
|
167
|
+
#
|
|
168
|
+
def method_missing(meth, *args, &block)
|
|
169
|
+
if @attributes.respond_to?(meth)
|
|
170
|
+
if block
|
|
171
|
+
@attributes.send(meth, *args, &block)
|
|
172
|
+
else
|
|
173
|
+
@attributes.send(meth, *args)
|
|
174
|
+
end
|
|
175
|
+
else
|
|
176
|
+
super(*args)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
#
|
|
180
|
+
# Returns whether this ServiceDescription matches the given +match+.
|
|
181
|
+
#
|
|
182
|
+
def matches?(match)
|
|
183
|
+
match.each do |key, value|
|
|
184
|
+
unless IGNORABLE_ATTRIBUTES.include?(key)
|
|
185
|
+
return false unless @attributes.include?(key) && (value.nil? || @attributes[key] == value)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
true
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
#
|
|
193
|
+
# A class used to query the Disco network for services.
|
|
194
|
+
#
|
|
195
|
+
class Query < ServiceDescription
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
#
|
|
199
|
+
# A class used to define an existing service.
|
|
200
|
+
#
|
|
201
|
+
class Record < ServiceDescription
|
|
202
|
+
#
|
|
203
|
+
# Initialize this Record with a hash that must contain an <i>:service_id</i> and a <i>:validator</i>.
|
|
204
|
+
#
|
|
205
|
+
def initialize(hash)
|
|
206
|
+
raise "Record must have an :service_id" unless hash.include?(:service_id)
|
|
207
|
+
raise "Record must have a :validator" unless hash.include?(:validator)
|
|
208
|
+
super(hash)
|
|
209
|
+
end
|
|
210
|
+
#
|
|
211
|
+
# Returns whether this service is still valid.
|
|
212
|
+
#
|
|
213
|
+
def valid?
|
|
214
|
+
begin
|
|
215
|
+
self[:validator].valid?
|
|
216
|
+
rescue DRb::DRbError => e
|
|
217
|
+
false
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
#
|
|
223
|
+
# A container of services.
|
|
224
|
+
#
|
|
225
|
+
class ServiceLocker
|
|
226
|
+
attr_reader :hash
|
|
227
|
+
include Archipelago::Current::Synchronized
|
|
228
|
+
def initialize(hash = nil)
|
|
229
|
+
super
|
|
230
|
+
@hash = hash || {}
|
|
231
|
+
end
|
|
232
|
+
#
|
|
233
|
+
# Merge this locker with another.
|
|
234
|
+
#
|
|
235
|
+
def merge(sd)
|
|
236
|
+
rval = @hash.clone
|
|
237
|
+
rval.merge!(sd.hash)
|
|
238
|
+
ServiceLocker.new(rval)
|
|
239
|
+
end
|
|
240
|
+
#
|
|
241
|
+
# Forwards as much as possible to our Hash.
|
|
242
|
+
#
|
|
243
|
+
def method_missing(meth, *args, &block)
|
|
244
|
+
if @hash.respond_to?(meth)
|
|
245
|
+
synchronize do
|
|
246
|
+
if block
|
|
247
|
+
@hash.send(meth, *args, &block)
|
|
248
|
+
else
|
|
249
|
+
@hash.send(meth, *args)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
else
|
|
253
|
+
super(*args)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
#
|
|
257
|
+
# Find all containing services matching +match+.
|
|
258
|
+
#
|
|
259
|
+
def get_services(match)
|
|
260
|
+
rval = ServiceLocker.new
|
|
261
|
+
self.each do |service_id, service_data|
|
|
262
|
+
rval[service_id] = service_data if service_data.matches?(match) && service_data.valid?
|
|
263
|
+
end
|
|
264
|
+
return rval
|
|
265
|
+
end
|
|
266
|
+
#
|
|
267
|
+
# Remove all non-valid services.
|
|
268
|
+
#
|
|
269
|
+
def validate!
|
|
270
|
+
self.clone.each do |service_id, service_data|
|
|
271
|
+
self.delete(service_id) unless service_data.valid?
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
#
|
|
277
|
+
# The main discovery class used to both publish and lookup services.
|
|
278
|
+
#
|
|
279
|
+
class Jockey
|
|
280
|
+
|
|
281
|
+
attr_reader :new_service_semaphore
|
|
282
|
+
|
|
283
|
+
#
|
|
284
|
+
# Will create a Jockey service running on <i>:address</i> and <i>:port</i> or
|
|
285
|
+
# ADDRESS and PORT if none are given.
|
|
286
|
+
#
|
|
287
|
+
# Will the first available unicast port within <i>:uniports</i> or if not given UNIPORTS for receiving unicast messages.
|
|
288
|
+
#
|
|
289
|
+
# Will have a default <i>:lookup_timeout</i> of LOOKUP_TIMEOUT, a default
|
|
290
|
+
# <i>:initial_lookup_standoff</i> of INITIAL_LOOKUP_STANDOFF and a default
|
|
291
|
+
# <i>:validation_interval</i> of VALIDATION_INTERVAL.
|
|
292
|
+
#
|
|
293
|
+
# Will only cache (and validate, which saves network traffic) stuff
|
|
294
|
+
# that has been looked up before if <i>:thrifty_caching</i>, or THRIFTY_CACHING if not given.
|
|
295
|
+
#
|
|
296
|
+
# Will only reply to the one that sent out the query (and therefore save lots of network traffic)
|
|
297
|
+
# if <i>:thrifty_replying</i>, or THRIFTY_REPLYING if not given.
|
|
298
|
+
#
|
|
299
|
+
# Will send out a multicast when a new service is published unless <i>:thrifty_publishing</i>, or
|
|
300
|
+
# THRIFTY_PUBLISHING if not given.
|
|
301
|
+
#
|
|
302
|
+
# Will reply to all queries to which it has matching local services with a unicast message if <i>:thrifty_replying</i>,
|
|
303
|
+
# or if not given THRIFTY_REPLYING. Otherwise will reply with multicasts.
|
|
304
|
+
#
|
|
305
|
+
def initialize(options = {})
|
|
306
|
+
@thrifty_caching = options.include?(:thrifty_caching) ? options[:thrifty_caching] : THRIFTY_CACHING
|
|
307
|
+
@thrifty_replying = options.include?(:thrifty_replying) ? options[:thrifty_replying] : THRIFTY_REPLYING
|
|
308
|
+
@thrifty_publishing = options.include?(:thrifty_publishing) ? options[:thrifty_publishing] : THRIFTY_PUBLISHING
|
|
309
|
+
@lookup_timeout = options[:lookup_timeout] || LOOKUP_TIMEOUT
|
|
310
|
+
@initial_lookup_standoff = options[:initial_lookup_standoff] || INITIAL_LOOKUP_STANDOFF
|
|
311
|
+
|
|
312
|
+
@remote_services = ServiceLocker.new
|
|
313
|
+
@local_services = ServiceLocker.new
|
|
314
|
+
@subscribed_services = Set.new
|
|
315
|
+
|
|
316
|
+
@incoming = Queue.new
|
|
317
|
+
@outgoing = Queue.new
|
|
318
|
+
|
|
319
|
+
@new_service_semaphore = MonitorMixin::ConditionVariable.new(Archipelago::Current::Lock.new)
|
|
320
|
+
|
|
321
|
+
@listener = UDPSocket.new
|
|
322
|
+
@unilistener = UDPSocket.new
|
|
323
|
+
|
|
324
|
+
@listener.setsockopt(Socket::IPPROTO_IP,
|
|
325
|
+
Socket::IP_ADD_MEMBERSHIP,
|
|
326
|
+
IPAddr.new(options[:address] || ADDRESS).hton + Socket.gethostbyname("0.0.0.0")[3])
|
|
327
|
+
|
|
328
|
+
@listener.setsockopt(Socket::SOL_SOCKET,
|
|
329
|
+
Socket::SO_REUSEADDR,
|
|
330
|
+
true)
|
|
331
|
+
begin
|
|
332
|
+
@listener.setsockopt(Socket::SOL_SOCKET,
|
|
333
|
+
Socket::SO_REUSEPORT,
|
|
334
|
+
true)
|
|
335
|
+
rescue
|
|
336
|
+
# /moo
|
|
337
|
+
end
|
|
338
|
+
@listener.bind('', options[:port] || PORT)
|
|
339
|
+
|
|
340
|
+
uniports = options[:uniports] || UNIPORTS
|
|
341
|
+
this_port = uniports.min
|
|
342
|
+
begin
|
|
343
|
+
@unilistener.bind('', this_port)
|
|
344
|
+
rescue Errno::EADDRINUSE => e
|
|
345
|
+
if this_port < uniports.max
|
|
346
|
+
this_port += 1
|
|
347
|
+
retry
|
|
348
|
+
else
|
|
349
|
+
raise e
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
@unicast_address = "#{Socket::gethostbyname(Socket::gethostname)[0]}:#{this_port}" rescue "localhost:#{this_port}"
|
|
353
|
+
|
|
354
|
+
@sender = UDPSocket.new
|
|
355
|
+
@sender.connect(options[:address] || ADDRESS, options[:port] || PORT)
|
|
356
|
+
|
|
357
|
+
@unisender = UDPSocket.new
|
|
358
|
+
|
|
359
|
+
start_listener
|
|
360
|
+
start_unilistener
|
|
361
|
+
start_shouter
|
|
362
|
+
start_picker
|
|
363
|
+
start_validator(options[:validation_interval] || VALIDATION_INTERVAL)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
#
|
|
367
|
+
# Stops all the threads in this instance.
|
|
368
|
+
#
|
|
369
|
+
def stop
|
|
370
|
+
@listener_thread.kill
|
|
371
|
+
@unilistener_thread.kill
|
|
372
|
+
@shouter_thread.kill
|
|
373
|
+
@picker_thread.kill
|
|
374
|
+
@validator_thread.kill
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
#
|
|
378
|
+
# Lookup any services matching +match+, optionally with a +timeout+.
|
|
379
|
+
#
|
|
380
|
+
# Will immediately return if we know of matching and valid services,
|
|
381
|
+
# will otherwise send out regular Queries and return as soon as
|
|
382
|
+
# matching services are found, or when the +timeout+ runs out.
|
|
383
|
+
#
|
|
384
|
+
def lookup(match, timeout = @lookup_timeout)
|
|
385
|
+
match[:unicast_reply] = @unicast_address
|
|
386
|
+
@subscribed_services << match if @thrifty_caching
|
|
387
|
+
standoff = @initial_lookup_standoff
|
|
388
|
+
|
|
389
|
+
@outgoing << [nil, match]
|
|
390
|
+
known_services = @remote_services.get_services(match).merge(@local_services.get_services(match))
|
|
391
|
+
return known_services unless known_services.empty?
|
|
392
|
+
|
|
393
|
+
@new_service_semaphore.wait(standoff)
|
|
394
|
+
standoff *= 2
|
|
395
|
+
|
|
396
|
+
t = Time.new
|
|
397
|
+
while Time.new < t + timeout
|
|
398
|
+
known_services = @remote_services.get_services(match).merge(@local_services.get_services(match))
|
|
399
|
+
return known_services unless known_services.empty?
|
|
400
|
+
|
|
401
|
+
@new_service_semaphore.wait(standoff)
|
|
402
|
+
standoff *= 2
|
|
403
|
+
|
|
404
|
+
@outgoing << [nil, match]
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
ServiceLocker.new
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
#
|
|
411
|
+
# Record the given +service+ and broadcast about it.
|
|
412
|
+
#
|
|
413
|
+
def publish(service)
|
|
414
|
+
if service.valid?
|
|
415
|
+
@local_services[service[:service_id]] = service
|
|
416
|
+
@new_service_semaphore.broadcast
|
|
417
|
+
unless @thrifty_publishing
|
|
418
|
+
@outgoing << [nil, service]
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
private
|
|
424
|
+
|
|
425
|
+
#
|
|
426
|
+
# Start the validating thread.
|
|
427
|
+
#
|
|
428
|
+
def start_validator(validation_interval)
|
|
429
|
+
@validator_thread = Thread.new do
|
|
430
|
+
loop do
|
|
431
|
+
begin
|
|
432
|
+
@local_services.validate!
|
|
433
|
+
@remote_services.validate!
|
|
434
|
+
sleep(validation_interval)
|
|
435
|
+
rescue Exception => e
|
|
436
|
+
puts e
|
|
437
|
+
pp e.backtrace
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
#
|
|
444
|
+
# Start the thread sending Records and Queries
|
|
445
|
+
#
|
|
446
|
+
def start_shouter
|
|
447
|
+
@shouter_thread = Thread.new do
|
|
448
|
+
loop do
|
|
449
|
+
begin
|
|
450
|
+
recipient, data = @outgoing.pop
|
|
451
|
+
if recipient
|
|
452
|
+
address, port = recipient.split(/:/)
|
|
453
|
+
@unisender.send(Marshal.dump(data), 0, address, port.to_i)
|
|
454
|
+
else
|
|
455
|
+
begin
|
|
456
|
+
@sender.write(Marshal.dump(data))
|
|
457
|
+
rescue Errno::ECONNREFUSED => e
|
|
458
|
+
retry
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
rescue Exception => e
|
|
462
|
+
puts e
|
|
463
|
+
pp e.backtrace
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
#
|
|
470
|
+
# Start the thread receiving Records and Queries
|
|
471
|
+
#
|
|
472
|
+
def start_listener
|
|
473
|
+
@listener_thread = Thread.new do
|
|
474
|
+
loop do
|
|
475
|
+
begin
|
|
476
|
+
@incoming << Marshal.load(@listener.recv(1024))
|
|
477
|
+
rescue Exception => e
|
|
478
|
+
puts e
|
|
479
|
+
pp e.backtrace
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
#
|
|
486
|
+
# Start the thread receiving Records and Queries
|
|
487
|
+
# on unicast.
|
|
488
|
+
#
|
|
489
|
+
def start_unilistener
|
|
490
|
+
@unilistener_thread = Thread.new do
|
|
491
|
+
loop do
|
|
492
|
+
begin
|
|
493
|
+
@incoming << Marshal.load(@unilistener.recv(1024))
|
|
494
|
+
rescue Exception => e
|
|
495
|
+
puts e
|
|
496
|
+
pp e.backtrace
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
#
|
|
503
|
+
# Start the thread picking incoming Records and Queries and
|
|
504
|
+
# handling them properly
|
|
505
|
+
#
|
|
506
|
+
def start_picker
|
|
507
|
+
@picker_thread = Thread.new do
|
|
508
|
+
loop do
|
|
509
|
+
begin
|
|
510
|
+
data = @incoming.pop
|
|
511
|
+
if Archipelago::Disco::Query === data
|
|
512
|
+
@local_services.get_services(data).each do |service_id, service_data|
|
|
513
|
+
if @thrifty_replying
|
|
514
|
+
@outgoing << [data[:unicast_reply], service_data]
|
|
515
|
+
else
|
|
516
|
+
@outgoing << [nil, service_data]
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
elsif Archipelago::Disco::Record === data
|
|
520
|
+
if interesting?(data) && data.valid?
|
|
521
|
+
@remote_services[data[:service_id]] = data
|
|
522
|
+
@new_service_semaphore.broadcast
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
rescue Exception => e
|
|
526
|
+
puts e
|
|
527
|
+
pp e.backtrace
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
#
|
|
534
|
+
# Are we generous in our caching, or have we been
|
|
535
|
+
# asked about this type of +publish+ before?
|
|
536
|
+
#
|
|
537
|
+
def interesting?(publish)
|
|
538
|
+
@subscribed_services.each do |subscribed|
|
|
539
|
+
return true if publish.matches?(subscribed)
|
|
540
|
+
end
|
|
541
|
+
return !@thrifty_caching
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
end
|