ruby_skynet 0.4.0.pre2 → 0.4.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: 0a4505056e7cd42bd2218494f4ef40c5c46b68b8
4
- data.tar.gz: a2c44053bd1e42ac9bf3f67797e0363d5002b6fa
3
+ metadata.gz: 16525cda69fa5554ce2851afcea16d4d379e1c3e
4
+ data.tar.gz: b02c07c9d88035fc5018e18548dc0e868f640716
5
5
  SHA512:
6
- metadata.gz: df66afd5b487c3f1014064c39687404681f1c0cb5528e888af262daf7b2535474b10029a8d278d7e133b1ccb89f223b9621a5bd944ee19b7f413462cc4c8602f
7
- data.tar.gz: 47ed383fa62a90510cb2788d520a9416e6f82610db9300fcb7a6370285fc298cfd96348c99ecbc1bcbd5bb03c708a3456283b52f524960843aeb0f0eb930d5d1
6
+ metadata.gz: 461650dbc05cd7febf853c2f7416e22f089cec61fa4e5aea188cef90573452e6ea1f8cc43443132b94071420e9727cacd605662a2d35423baf3a2acecbc2132e
7
+ data.tar.gz: 9bfa3b9751bd03753a849e34c1c4a3eb9c9f9de04af1efe34131aa7fdcd3c59aa1f8e3fb885ecbe61e79abd2aea810804a9c169c9a26ed0d553ecf0e5210be23
data/Gemfile CHANGED
@@ -2,6 +2,7 @@ source :rubygems
2
2
 
3
3
  group :test do
4
4
  gem "shoulda"
5
+ gem "mocha", :require => false
5
6
  end
6
7
 
7
8
  gem "rake"
@@ -16,6 +17,3 @@ gem "ruby_protobuf"
16
17
  # Wire format when communicating with services
17
18
  gem "bson"
18
19
  gem "bson_ext", :platform => :ruby
19
- # Celluloid::IO is used to create SkyNet services in Ruby
20
- # multi_json?
21
- gem "celluloid-io", '0.13.0.pre2'
data/Gemfile.lock CHANGED
@@ -1,27 +1,23 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- activesupport (3.2.12)
5
- i18n (~> 0.6)
4
+ activesupport (3.2.13)
5
+ i18n (= 0.6.1)
6
6
  multi_json (~> 1.0)
7
7
  atomic (1.0.1)
8
8
  atomic (1.0.1-java)
9
- bourne (1.1.2)
10
- mocha (= 0.10.5)
9
+ bourne (1.4.0)
10
+ mocha (~> 0.13.2)
11
11
  bson (1.8.3)
12
12
  bson (1.8.3-java)
13
- celluloid (0.13.0.pre2)
14
- timers (>= 1.0.0)
15
- celluloid-io (0.13.0.pre2)
16
- celluloid (>= 0.13.0.pre)
17
- nio4r (>= 0.4.0)
13
+ bson_ext (1.8.3)
14
+ bson (~> 1.8.3)
18
15
  gene_pool (1.3.0)
19
- i18n (0.6.4)
16
+ i18n (0.6.1)
20
17
  metaclass (0.0.1)
21
- mocha (0.10.5)
18
+ mocha (0.13.3)
22
19
  metaclass (~> 0.0.1)
23
- multi_json (1.7.0)
24
- nio4r (0.4.3-java)
20
+ multi_json (1.7.1)
25
21
  rake (10.0.3)
26
22
  resilient_socket (0.4.0)
27
23
  semantic_logger
@@ -29,26 +25,26 @@ GEM
29
25
  semantic_logger (2.0.0)
30
26
  sync_attr
31
27
  thread_safe
32
- shoulda (3.3.2)
33
- shoulda-context (~> 1.0.1)
34
- shoulda-matchers (~> 1.4.1)
28
+ shoulda (3.4.0)
29
+ shoulda-context (~> 1.0, >= 1.0.1)
30
+ shoulda-matchers (~> 1.0, >= 1.4.1)
35
31
  shoulda-context (1.0.2)
36
- shoulda-matchers (1.4.2)
32
+ shoulda-matchers (1.5.4)
37
33
  activesupport (>= 3.0.0)
38
- bourne (~> 1.1.2)
34
+ bourne (~> 1.3)
39
35
  sync_attr (0.1.1)
40
36
  thread_safe (0.1.0)
41
37
  atomic
42
- timers (1.1.0)
43
38
 
44
39
  PLATFORMS
45
40
  java
41
+ ruby
46
42
 
47
43
  DEPENDENCIES
48
44
  bson
49
45
  bson_ext
50
- celluloid-io (= 0.13.0.pre2)
51
46
  gene_pool
47
+ mocha
52
48
  multi_json
53
49
  rake
54
50
  resilient_socket
data/Rakefile CHANGED
@@ -29,7 +29,6 @@ task :gem do |t|
29
29
  spec.add_dependency 'multi_json', '>= 1.6.1'
30
30
  spec.add_dependency 'bson'
31
31
  spec.add_dependency 'ruby_protobuf'
32
- spec.add_dependency 'celluloid-io', '>= 0.13.0.pre2'
33
32
  end
34
33
  Gem::Package.build gemspec
35
34
  end
data/examples/e.rb CHANGED
@@ -1,14 +0,0 @@
1
- $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
- require 'rubygems'
3
- require 'ruby_skynet'
4
- require 'active_support/core_ext/hash/conversions'
5
-
6
- SemanticLogger::Logger.default_level = :trace
7
- SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('skynet.log')
8
-
9
- client = RubySkynet::Client.new('InquiriesService')
10
- #h = {}; 1000.times{|i| h["hello#{i}"] = i}
11
- #p client.call('echo', h)
12
- data = Hash.from_xml(File.read("../../claritybase/test/load/inquiry.xml"))['inquiry']
13
- p data
14
- p client.call('create', data)
@@ -54,7 +54,7 @@ module RubySkynet
54
54
  # Raises RubySkynet::SkynetException
55
55
  def call(method_name, parameters, connection_params={})
56
56
  # Skynet requires BSON RPC Calls to have the following format:
57
- # https://github.com/bketelsen/skynet/blob/protocol/protocol.md
57
+ # https://github.com/skynetservices/skynet/blob/master/protocol.md
58
58
  request_id = BSON::ObjectId.new.to_s
59
59
  @logger.tagged request_id do
60
60
  @logger.benchmark_info "Called Skynet Service: #{@service_name}.#{method_name}" do
@@ -1,3 +1,5 @@
1
+ require 'socket'
2
+
1
3
  module RubySkynet
2
4
  module Common
3
5
 
@@ -18,9 +20,15 @@ module RubySkynet
18
20
 
19
21
  bytebuf.append!(bytes)
20
22
  bytebuf.append!(socket.read(sz - 4))
21
- raise "Celluloid is not returning #{sz} requested bytes. #{bytebuf.length} bytes returned" unless sz == bytebuf.length
23
+ raise "Socket is not returning #{sz} requested bytes. #{bytebuf.length} bytes returned" unless sz == bytebuf.length
22
24
  return BSON.deserialize(bytebuf)
23
25
  end
24
26
 
27
+ # Returns the local ip address being used by this machine to talk to the
28
+ # internet. By default connects to Google and determines what IP Address is used locally
29
+ def self.local_ip_address(remote_ip = '64.233.187.99')
30
+ @@local_ip_address ||= ::UDPSocket.open {|s| s.connect(remote_ip, 1); s.addr.last }
31
+ end
32
+
25
33
  end
26
34
  end
@@ -25,7 +25,7 @@ module RubySkynet
25
25
  # Disable buffering the send since it is a RPC call
26
26
  params[:buffered] = false
27
27
 
28
- @logger.trace "Socket Connection parameters", params
28
+ @logger.trace "Socket Connection parameters", params.dup
29
29
 
30
30
  # For each new connection
31
31
  params[:on_connect] = Proc.new do |socket|
@@ -2,6 +2,7 @@ module RubySkynet
2
2
  class Exception < ::RuntimeError; end
3
3
  class ProtocolError < Exception; end
4
4
  class ServiceException < Exception; end
5
+ class InvalidServiceException < ServiceException; end
5
6
  class SkynetException < Exception; end
6
7
  class ServiceUnavailable < SkynetException; end
7
8
  end
@@ -2,6 +2,7 @@ require 'sync_attr'
2
2
  require 'multi_json'
3
3
  require 'thread_safe'
4
4
  require 'gene_pool'
5
+ require 'resolv'
5
6
 
6
7
  #
7
8
  # RubySkynet Registry Client
@@ -17,8 +18,8 @@ module RubySkynet
17
18
  # Service Registry has the following format
18
19
  # Key: [String] 'service_name/version/region'
19
20
  # Value: [Array<String>] 'host:port', 'host:port'
20
- sync_cattr_accessor :service_registry do
21
- start_monitoring
21
+ sync_cattr_reader :service_registry do
22
+ start
22
23
  end
23
24
 
24
25
  @@on_server_removed_callbacks = ThreadSafe::Hash.new
@@ -61,24 +62,32 @@ module RubySkynet
61
62
  }
62
63
  end
63
64
 
64
- # Lazy initialize Doozer Client Connection pool
65
- sync_cattr_reader :doozer_pool do
66
- GenePool.new(
67
- :name =>"Doozer Connection Pool",
68
- :pool_size => 5,
69
- :timeout => 30,
70
- :warn_timeout => 5,
71
- :idle_timeout => 600,
72
- :logger => logger,
73
- :close_proc => :close
74
- ) do
75
- Doozer::Client.new(doozer_config)
65
+ # Register the supplied service at this Skynet Server host and Port
66
+ def self.register_service(name, version, region, hostname, port)
67
+ config = {
68
+ "Config" => {
69
+ "UUID" => "#{hostname}:#{port}-#{$$}-#{name}-#{version}",
70
+ "Name" => name,
71
+ "Version" => version.to_s,
72
+ "Region" => region,
73
+ "ServiceAddr" => {
74
+ "IPAddress" => hostname,
75
+ "Port" => port,
76
+ "MaxPort" => port + 999
77
+ },
78
+ },
79
+ "Registered" => true
80
+ }
81
+ doozer_pool.with_connection do |doozer|
82
+ doozer["/services/#{name}/#{version}/#{region}/#{hostname}/#{port}"] = MultiJson.encode(config)
76
83
  end
77
84
  end
78
85
 
79
- # Logging instance for this class
80
- sync_cattr_reader :logger do
81
- SemanticLogger::Logger.new(self, :debug)
86
+ # Deregister the supplied service from the Registry
87
+ def self.deregister_service(name, version, region, hostname, port)
88
+ doozer_pool.with_connection do |doozer|
89
+ doozer.delete("/services/#{name}/#{version}/#{region}/#{hostname}/#{port}") rescue nil
90
+ end
82
91
  end
83
92
 
84
93
  # Return a server that implements the specified service
@@ -115,35 +124,19 @@ module RubySkynet
115
124
  end
116
125
 
117
126
  # Returns [Array<String>] a list of servers implementing the requested service
118
- def self.servers_for(service_name, version='*', region='Development', remote = false)
119
- if remote
120
- if version != '*'
121
- registered_implementers(service_name, version, region).map do |host|
122
- service = host['Config']['ServiceAddr']
123
- "#{service['IPAddress']}:#{service['Port']}"
124
- end
125
- else
126
- # Find the highest version of any particular service
127
- versions = {}
128
- registered_implementers(service_name, version, region).each do |host|
129
- service = host['Config']['ServiceAddr']
130
- (versions[version.to_i] ||= []) << "#{service['IPAddress']}:#{service['Port']}"
131
- end
132
- # Return the servers implementing the highest version number
133
- versions.sort.last.last
134
- end
135
- else
136
- if version == '*'
137
- # Find the highest version for the named service in this region
138
- version = -1
139
- service_registry.keys.each do |key|
140
- if match = key.match(/#{service_name}\/(\d+)\/#{region}/)
141
- ver = match[1].to_i
142
- version = ver if ver > version
143
- end
127
+ def self.servers_for(service_name, version='*', region='Development')
128
+ if version == '*'
129
+ # Find the highest version for the named service in this region
130
+ version = -1
131
+ service_registry.keys.each do |key|
132
+ if match = key.match(/#{service_name}\/(\d+)\/#{region}/)
133
+ ver = match[1].to_i
134
+ version = ver if ver > version
144
135
  end
145
136
  end
146
- service_registry["#{service_name}/#{version}/#{region}"]
137
+ end
138
+ if server_infos = service_registry["#{service_name}/#{version}/#{region}"]
139
+ server_infos.first.servers
147
140
  end
148
141
  end
149
142
 
@@ -155,83 +148,124 @@ module RubySkynet
155
148
  (@@on_server_removed_callbacks[server] ||= ThreadSafe::Array.new) << block
156
149
  end
157
150
 
151
+ IPV4_REG_EXP = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
152
+
153
+ # Returns [Integer] the score for the supplied ip_address
154
+ # Score currently ranges from 0 to 4 with 4 being the best score
155
+ # If the IP address does not match an IP v4 address a DNS lookup will
156
+ # be performed
157
+ def self.score_for_server(ip_address)
158
+ score = 0
159
+ # Each matching element adds 1 to the score
160
+ # 192.168. 0. 0
161
+ # 1
162
+ # 1
163
+ # 1
164
+ # 1
165
+ server_match = IPV4_REG_EXP.match(ip_address) || IPV4_REG_EXP.match(Resolv::DNS.new.getaddress(ip_address).to_s)
166
+ if server_match
167
+ @@local_match ||= IPV4_REG_EXP.match(Common.local_ip_address)
168
+ score = 0
169
+ (1..4).each do |i|
170
+ break if @@local_match[i].to_i != server_match[i].to_i
171
+ score += 1
172
+ end
173
+ end
174
+ score
175
+ end
176
+
158
177
  ############################
159
- #protected
178
+ protected
179
+
180
+ # Logging instance for this class
181
+ sync_cattr_reader :logger do
182
+ SemanticLogger::Logger.new(self, :debug)
183
+ end
160
184
 
161
- # Fetch the all registry information from Doozer and set the internal registry
185
+ # Lazy initialize Doozer Client Connection pool
186
+ sync_cattr_reader :doozer_pool do
187
+ GenePool.new(
188
+ :name =>"Doozer Connection Pool",
189
+ :pool_size => 5,
190
+ :timeout => 30,
191
+ :warn_timeout => 5,
192
+ :idle_timeout => 600,
193
+ :logger => logger,
194
+ :close_proc => :close
195
+ ) do
196
+ Doozer::Client.new(doozer_config)
197
+ end
198
+ end
199
+
200
+ # Fetch the all registry information from Doozer and sets the internal registry
162
201
  # Also starts the monitoring thread to keep the registry up to date
163
- def self.start_monitoring
202
+ def self.start
203
+ # Populate internal registry from doozer server
204
+ # Holds a lock in this process on the service_registry so that only
205
+ # this thread will pre-populate the local copy of the registry
164
206
  registry = ThreadSafe::Hash.new
165
207
  revision = nil
166
208
  doozer_pool.with_connection do |doozer|
167
209
  revision = doozer.current_revision
168
210
  doozer.walk(DOOZER_SERVICES_PATH, revision).each do |node|
169
- # path: "/services/TutorialService/1/Development/127.0.0.1/9000"
170
- e = node.path.split('/')
171
-
172
- # Key: [String] 'service_name/version/region'
173
- key = "#{e[2]}/#{e[3]}/#{e[4]}"
174
- server = "#{e[5]}:#{e[6]}"
175
-
176
- if node.value.strip.size > 0
177
- entry = MultiJson.load(node.value)
178
- if entry['Registered']
179
- # Value: [Array<String>] 'host:port', 'host:port'
180
- servers = (registry[key] ||= ThreadSafe::Array.new)
181
- servers << server unless servers.include?(server)
182
- logger.debug "#start_monitoring Add Service: #{key} => #{server}"
183
- end
184
- end
211
+ service_info_change(registry, node.path, node.value)
185
212
  end
186
213
  end
187
214
  # Start monitoring thread to keep the registry up to date
188
- @@monitor_thread = Thread.new { self.watch(revision + 1) }
215
+ @@monitor_thread = Thread.new { watch_registry(revision + 1) }
216
+
217
+ # Cleanup when process exits
218
+ at_exit do
219
+ if @@monitor_thread
220
+ @@monitor_thread.kill
221
+ @@monitor_thread.join
222
+ @@monitor_thread = nil
223
+ end
224
+ doozer_pool.close
225
+ end
189
226
  registry
190
227
  end
191
228
 
192
229
  # Waits for any updates from Doozer and updates the internal service registry
193
- def self.watch(revision)
230
+ def self.watch_registry(revision)
194
231
  logger.info "Start monitoring #{DOOZER_SERVICES_PATH}"
195
232
  # This thread must use its own dedicated doozer connection
196
233
  doozer = Doozer::Client.new(doozer_config)
197
- doozer.watch(DOOZER_SERVICES_PATH, revision) do |node|
198
- # path: "/services/TutorialService/1/Development/127.0.0.1/9000"
199
- e = node.path.split('/')
200
234
 
201
- # Key: [String] 'service_name/version/region'
202
- key = "#{e[2]}/#{e[3]}/#{e[4]}"
203
- server = "#{e[5]}:#{e[6]}"
204
-
205
- if node.value.strip.size > 0
206
- entry = MultiJson.load(node.value)
207
- if entry['Registered']
208
- # Value: [Array<String>] 'host:port', 'host:port'
209
- servers = (@@service_registry[key] ||= ThreadSafe::Array.new)
210
- servers << server unless servers.include?(server)
211
- logger.debug "#monitor Add/Update Service: #{key} => #{server}"
212
- else
213
- logger.debug "#monitor Service deregistered, remove: #{key} => #{server}"
214
- if @@service_registry[key]
215
- @@service_registry[key].delete(server)
216
- @@service_registry.delete(key) if @@service_registry[key].size == 0
217
- end
218
- end
219
- else
220
- # Service has stopped and needs to be removed
221
- logger.debug "#monitor Service stopped, remove: #{key} => #{server}"
222
- if @@service_registry[key]
223
- @@service_registry[key].delete(server)
224
- @@service_registry.delete(key) if @@service_registry[key].size == 0
225
- server_removed(server)
226
- end
227
- end
228
- logger.debug "Updated registry", @@service_registry
235
+ # Watch for any changes
236
+ doozer.watch(DOOZER_SERVICES_PATH, revision) do |node|
237
+ service_info_change(service_registry, node.path, node.value)
238
+ logger.trace "Updated registry", service_registry
229
239
  end
230
240
  logger.info "Stopping monitoring thread normally"
231
241
  rescue Exception => exc
232
242
  logger.error "Exception in monitoring thread", exc
233
243
  ensure
234
- logger.info "Stopped monitoring"
244
+ doozer.close if doozer
245
+ logger.info "Stopped monitoring for changes in the doozer registry"
246
+ end
247
+
248
+ # Service information changed in doozer, so update internal registry
249
+ def self.service_info_change(registry, path, value)
250
+ # path from doozer: "/services/TutorialService/1/Development/127.0.0.1/9000"
251
+ e = path.split('/')
252
+
253
+ # Key: [String] 'service_name/version/region'
254
+ key = "#{e[2]}/#{e[3]}/#{e[4]}"
255
+ hostname, port = e[5], e[6]
256
+
257
+ if value.strip.size > 0
258
+ entry = MultiJson.load(value)
259
+ if entry['Registered']
260
+ add_server(registry, key, hostname, port)
261
+ else
262
+ # Service just de-registered
263
+ remove_server(registry, key, hostname, port, false)
264
+ end
265
+ else
266
+ # Service has stopped and needs to be removed
267
+ remove_server(registry, key, hostname, port, true)
268
+ end
235
269
  end
236
270
 
237
271
  # Invoke any registered callbacks for the specific server
@@ -248,5 +282,94 @@ module RubySkynet
248
282
  end
249
283
  end
250
284
 
285
+ # :score: [Integer] Score
286
+ # :servers: [Array<String>] 'host:port', 'host:port'
287
+ ServerInfo = Struct.new(:score, :servers )
288
+
289
+ # Format of the internal services registry
290
+ # key: [String] "<service_name>/<version>/<region>"
291
+ # value: [ServiceInfo, ServiceInfo]
292
+ # Sorted by highest score first
293
+
294
+ # Add the host to the registry based on it's score
295
+ def self.add_server(registry, key, hostname, port)
296
+ server = "#{hostname}:#{port}"
297
+ logger.debug "#monitor Add/Update Service: #{key} => #{server.inspect}"
298
+
299
+ server_infos = (registry[key] ||= ThreadSafe::Array.new)
300
+
301
+ # If already present, then nothing to do
302
+ server_info = server_infos.find{|si| si.server == server}
303
+ return server_info if server_info
304
+
305
+ # Look for the same score with a different server
306
+ score = score_for_server(hostname)
307
+ if server_info = server_infos.find{|si| si.score == score}
308
+ server_info.servers << server
309
+ return server_info
310
+ end
311
+
312
+ # New score
313
+ servers = ThreadSafe::Array.new
314
+ servers << server
315
+ server_info = ServerInfo.new(score, servers)
316
+
317
+ # Insert into Array in order of score
318
+ if index = server_infos.find_index {|si| si.score <= score}
319
+ server_infos.insert(index, server_info)
320
+ else
321
+ server_infos << server_info
322
+ end
323
+ server_info
324
+ end
325
+
326
+ # Remove the host from the registry based
327
+ # Returns the server instance if it was removed
328
+ def self.remove_server(registry, key, hostname, port, notify)
329
+ server = "#{hostname}:#{port}"
330
+ logger.debug "Remove Service: #{key} => #{server.inspect}"
331
+ server_info = nil
332
+ if server_infos = registry[key]
333
+ server_infos.each do |si|
334
+ if si.servers.delete(server)
335
+ server_info = si
336
+ break
337
+ end
338
+ end
339
+
340
+ # Found server
341
+ if server_info
342
+ # Cleanup if no more servers in server list
343
+ server_infos.delete(server_info) if server_info.servers.size == 0
344
+
345
+ # Cleanup if no more server infos
346
+ registry.delete(key) if server_infos.size == 0
347
+
348
+ server_removed(server) if notify
349
+ end
350
+ end
351
+ server_info
352
+ end
353
+
354
+ # Check doozer for servers matching supplied criteria
355
+ # Code unused, consider deleting
356
+ def self.remote_servers_for(service_name, version='*', region='Development')
357
+ if version != '*'
358
+ registered_implementers(service_name, version, region).map do |host|
359
+ service = host['Config']['ServiceAddr']
360
+ "#{service['IPAddress']}:#{service['Port']}"
361
+ end
362
+ else
363
+ # Find the highest version of any particular service
364
+ versions = {}
365
+ registered_implementers(service_name, version, region).each do |host|
366
+ service = host['Config']['ServiceAddr']
367
+ (versions[version.to_i] ||= []) << "#{service['IPAddress']}:#{service['Port']}"
368
+ end
369
+ # Return the servers implementing the highest version number
370
+ versions.sort.last.last
371
+ end
372
+ end
373
+
251
374
  end
252
375
  end
@@ -1,8 +1,4 @@
1
1
  require 'bson'
2
- require 'celluloid/io'
3
-
4
- # Replace Celluloid logger immediately upon loading the Server Instance
5
- Celluloid.logger = SemanticLogger::Logger.new('Celluloid')
6
2
 
7
3
  #
8
4
  # RubySkynet Server
@@ -11,92 +7,80 @@ Celluloid.logger = SemanticLogger::Logger.new('Celluloid')
11
7
  #
12
8
  module RubySkynet
13
9
  class Server
14
- include Celluloid::IO
15
10
  include SemanticLogger::Loggable
16
11
 
17
- # TODO Make Server instance based rather than class based. Then make instance global
18
- @@hostname = nil
19
- @@port = 2000
20
- @@region = 'Development'
21
12
  @@server = nil
13
+ @@services = ThreadSafe::Hash.new
22
14
 
23
- def self.start
24
- @@server ||= supervise(hostname, port)
15
+ # Start a single instance of the server
16
+ def self.start(region = 'Development', start_port = 2000, hostname = nil)
17
+ @@server ||= new(region, start_port, hostname)
25
18
  end
26
19
 
20
+ # Stop the single instance of the server
27
21
  def self.stop
28
- @@server.terminate if @@server
22
+ @@server.finalize if @@server
29
23
  @@server = nil
30
24
  end
31
25
 
26
+ # Is the single instance of the server running
32
27
  def self.running?
33
28
  (@@server != nil) && @@server.running?
34
29
  end
35
30
 
36
- # Region under which to register Skynet services
37
- # Default: 'Development'
38
- def self.region
39
- @@region
40
- end
41
-
42
- def self.region=(region)
43
- @@region = region
44
- end
45
-
46
- # Port to listen to requests on
47
- # Default: 2000
48
- def self.port
49
- @@port
50
- end
51
-
52
- def self.port=(port)
53
- @@port = port
54
- end
55
-
56
- # Override the hostname at which this server is running
57
- # Useful when the service is behind a firewall or NAT device
58
- def self.hostname=(hostname)
59
- @@hostname = hostname
60
- end
61
-
62
- # Returns [String] hostname of the current server
63
- def self.hostname
64
- @@hostname ||= Socket.gethostname
65
- end
66
-
67
- @@services = ThreadSafe::Hash.new
68
-
69
31
  # Services currently loaded and available at this server when running
70
32
  def self.services
71
33
  @@services
72
34
  end
73
35
 
74
- # Registers a Service Class as being available at this port
36
+ # Registers a Service Class as being available at this host and port
75
37
  def self.register_service(klass)
76
- logger.debug "Registering Service: #{klass.name} with name: #{klass.service_name}"
38
+ raise InvalidServiceException.new("#{klass.inspect} is not a RubySkynet::Service") unless klass.respond_to?(:service_name) && klass.respond_to?(:service_version)
39
+
40
+ if previous_klass = @@services[klass.service_name] && (previous_klass.name != klass.name)
41
+ logger.warn("Service with name: #{klass.service_name} is already registered to a different implementation:#{previous_klass.name}")
42
+ end
77
43
  @@services[klass.service_name] = klass
78
- register_service_in_doozer(klass) if running?
44
+ @@server.register_service(klass) if @@server
79
45
  end
80
46
 
81
- # De-register service in doozer
47
+ # De-register service
82
48
  def self.deregister_service(klass)
83
- RubySkynet::Registry.doozer_pool.with_connection do |doozer|
84
- doozer.delete(klass.service_key) rescue nil
85
- end
49
+ raise InvalidServiceException.new("#{klass.inspect} is not a RubySkynet::Service") unless klass.respond_to?(:service_name) && klass.respond_to?(:service_version)
50
+
51
+ @@server.deregister_service(klass) if @@server
86
52
  @@services.delete(klass.service_name)
87
53
  end
88
54
 
55
+ # The actual port the server is running at which will be different
56
+ # from Server.port if that port was already in use at startup
57
+ attr_reader :hostname, :port, :region
58
+
89
59
  # Start the server so that it can start taking RPC calls
90
60
  # Returns false if the server is already running
91
- def initialize(host, port)
92
- # Since we included Celluloid::IO, we're actually making a
93
- # Celluloid::IO::TCPServer here
94
- # TODO If port is in use, try the next port in sequence
95
- @server = TCPServer.new(host, port)
96
- async.run
61
+ def initialize(region = 'Development', start_port = 2000, hostname = nil)
62
+ hostname ||= Common.local_ip_address
63
+
64
+ # If port is in use, try the next port in sequence
65
+ port_count = 0
66
+ begin
67
+ @server = ::TCPServer.new(hostname, start_port + port_count)
68
+ @hostname = hostname
69
+ @port = start_port + port_count
70
+ @region = region
71
+ rescue Errno::EADDRINUSE => exc
72
+ if port_count < 999
73
+ port_count += 1
74
+ retry
75
+ end
76
+ raise exc
77
+ end
78
+
79
+ # Start Server listener thread
80
+ Thread.new { run }
97
81
 
98
82
  # Register services hosted by this server
99
- self.class.services.each_pair {|key, klass| self.class.register_service_in_doozer(klass)}
83
+ self.class.services.each_value {|klass| register_service(klass)}
100
84
  end
101
85
 
102
86
  def finalize
@@ -104,21 +88,25 @@ module RubySkynet
104
88
  logger.info "Skynet Server Stopped"
105
89
 
106
90
  # Deregister services hosted by this server
107
- RubySkynet::Registry.doozer_pool.with_connection do |doozer|
108
- self.class.services.each_value do |klass|
109
- doozer.delete(klass.service_key) rescue nil
110
- end
91
+ self.class.services.each_value do |klass|
92
+ deregister_service(klass) rescue nil
111
93
  end
112
- logger.info "Skynet Services De-registered in Doozer"
94
+ logger.info "Skynet Services De-registered"
113
95
  end
114
96
 
115
97
  def run
116
- logger.info("Starting listener on #{self.class.hostname}:#{self.class.port}")
98
+ logger.info("Starting listener on #{hostname}:#{port}")
117
99
  loop do
118
100
  logger.debug "Waiting for a client to connect"
119
101
  begin
120
- async.handle_connection(@server.accept)
121
- rescue Exception => exc
102
+ client = @server.accept
103
+ # We could use a thread pool here, but JRuby already does that
104
+ # and MRI threads are very light weight
105
+ Thread.new { handle_connection(client) }
106
+ rescue Errno::EBADF, IOError => exc
107
+ logger.info "TCPServer listener thread shutting down. #{exc.class}: #{exc.message}"
108
+ return
109
+ rescue ScriptError, NameError, StandardError, Exception => exc
122
110
  logger.error "Exception while processing connection request", exc
123
111
  end
124
112
  end
@@ -152,11 +140,11 @@ module RubySkynet
152
140
  break unless request
153
141
  params = BSON.deserialize(request['in'])
154
142
  logger.trace 'Parameters', params
143
+
155
144
  reply = begin
156
145
  on_message(service_name, request['method'].to_sym, params)
157
- rescue Exception => exc
146
+ rescue ScriptError, NameError, StandardError, Exception => exc
158
147
  logger.error "Exception while calling service: #{service_name}", exc
159
- # TODO Return exception in header
160
148
  { :exception => {:message => exc.message, :class => exc.class.name} }
161
149
  end
162
150
 
@@ -173,6 +161,9 @@ module RubySkynet
173
161
  break
174
162
  end
175
163
  end
164
+ rescue ScriptError, NameError, StandardError, Exception => exc
165
+ logger.error "#handle_connection Exception", exc
166
+ ensure
176
167
  # Disconnect from the client
177
168
  client.close
178
169
  logger.debug "Disconnected from the client"
@@ -186,37 +177,28 @@ module RubySkynet
186
177
  ############################################################################
187
178
  protected
188
179
 
189
- # Register the supplied service in doozer
190
- def self.register_service_in_doozer(klass)
191
- config = {
192
- "Config" => {
193
- "UUID" => "#{Server.hostname}:#{Server.port}-#{$$}-#{klass.name}-#{klass.object_id}",
194
- "Name" => klass.service_name,
195
- "Version" => klass.service_version.to_s,
196
- "Region" => Server.region,
197
- "ServiceAddr" => {
198
- "IPAddress" => Server.hostname,
199
- "Port" => Server.port,
200
- "MaxPort" => Server.port + 999
201
- },
202
- },
203
- "Registered" => true
204
- }
205
- RubySkynet::Registry.doozer_pool.with_connection do |doozer|
206
- doozer[klass.service_key] = MultiJson.encode(config)
207
- end
180
+ # Registers a Service Class as being available at this server
181
+ def register_service(klass)
182
+ logger.debug "Registering Service: #{klass.name} with name: #{klass.service_name}"
183
+ Registry.register_service(klass.service_name, klass.service_version, @region, @hostname, @port)
184
+ end
185
+
186
+ # De-register service from this server
187
+ def deregister_service(klass)
188
+ logger.debug "De-registering Service: #{klass.name} with name: #{klass.service_name}"
189
+ Registry.deregister_service(klass.service_name, klass.service_version, @region, @hostname, @port)
208
190
  end
209
191
 
210
192
  # Called for each message received from the client
211
193
  # Returns a Hash that is sent back to the caller
212
194
  def on_message(service_name, method, params)
213
- logger.benchmark_debug "Called: #{service_name}##{method}" do
195
+ logger.benchmark_info("Skynet Call: #{service_name}##{method}") do
214
196
  logger.trace "Method Call: #{method} with parameters:", params
215
197
  klass = Server.services[service_name]
216
198
  raise "Invalid Skynet RPC call, service: #{service_name} is not available at this server" unless klass
199
+ # TODO Use pool of services
217
200
  service = klass.new
218
201
  raise "Invalid Skynet RPC call, method: #{method} does not exist for service: #{service_name}" unless service.respond_to?(method)
219
- # TODO Use pool of services, or Celluloid here
220
202
  service.send(method, params)
221
203
  end
222
204
  end
@@ -29,27 +29,23 @@ module RubySkynet
29
29
  # Name of this service to Register with Skynet
30
30
  # Default: class name
31
31
  def service_name
32
- @@service_name ||= name.gsub('::', '.')
32
+ @service_name ||= name.gsub('::', '.')
33
33
  end
34
34
 
35
35
  def service_name=(service_name)
36
- @@service_name = service_name
36
+ @service_name = service_name
37
37
  end
38
38
 
39
39
  # Version of this service to register with Skynet, defaults to 1
40
40
  # Default: 1
41
41
  def service_version
42
- @@service_version ||= 1
42
+ @service_version ||= 1
43
43
  end
44
44
 
45
45
  def service_version=(service_version)
46
- @@service_version = service_version
46
+ @service_version = service_version
47
47
  end
48
48
 
49
- # Key by which this service is known in the doozer registry
50
- def service_key
51
- "/services/#{service_name}/#{service_version}/#{Server.region}/#{Server.hostname}/#{Server.port}"
52
- end
53
49
  end
54
50
 
55
51
  end
@@ -1,3 +1,3 @@
1
1
  module RubySkynet #:nodoc
2
- VERSION = "0.4.0.pre2"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -6,8 +6,6 @@ require 'rubygems'
6
6
  require 'test/unit'
7
7
  require 'shoulda'
8
8
  require 'ruby_skynet'
9
- require 'simple_server'
10
- require 'multi_json'
11
9
 
12
10
  # Register an appender if one is not already registered
13
11
  if SemanticLogger::Logger.appenders.size == 0
@@ -15,8 +13,33 @@ if SemanticLogger::Logger.appenders.size == 0
15
13
  SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('test.log')
16
14
  end
17
15
 
16
+ class ClientTestService
17
+ include RubySkynet::Service
18
+
19
+ # Methods implemented by this service
20
+ # Must take a Hash as input
21
+ # Must Return a Hash response or nil for no response
22
+ def test1(params)
23
+ { 'result' => 'test1' }
24
+ end
25
+
26
+ def sleep(params)
27
+ Kernel.sleep params['duration'] || 1
28
+ { 'result' => 'sleep' }
29
+ end
30
+
31
+ def fail(params)
32
+ if params['attempt'].to_i >= 2
33
+ { 'result' => 'fail' }
34
+ else
35
+ nil
36
+ end
37
+ end
38
+
39
+ end
40
+
18
41
  # Unit Test for ResilientSocket::TCPClient
19
- class RubySkynetClientTest < Test::Unit::TestCase
42
+ class ClientTest < Test::Unit::TestCase
20
43
  context RubySkynet::Client do
21
44
 
22
45
  context "without server" do
@@ -32,41 +55,17 @@ class RubySkynetClientTest < Test::Unit::TestCase
32
55
 
33
56
  context "with server" do
34
57
  setup do
35
- @port = 2000
36
- @read_timeout = 3.0
37
- @server = SimpleServer.new(@port)
38
- @server_name = "localhost:#{@port}"
58
+ @region = 'ClientTest'
59
+ RubySkynet::Server.start(@region)
39
60
 
40
- # Register service in doozer
41
- @service_name = "TestService"
61
+ @service_name = 'ClientTestService'
42
62
  @version = 1
43
- @region = 'Test'
44
- @ip_address = "127.0.0.1"
45
- config = {
46
- "Config" => {
47
- "UUID" => "3978b371-15e9-40f8-9b7b-59ae88d8c7ec",
48
- "Name" => @service_name,
49
- "Version" => @version.to_s,
50
- "Region" => @region,
51
- "ServiceAddr" => {
52
- "IPAddress" => @ip_address,
53
- "Port" => @port,
54
- "MaxPort" => @port + 999
55
- },
56
- },
57
- "Registered" => true
58
- }
59
- RubySkynet::Registry.doozer_pool.with_connection do |doozer|
60
- doozer["/services/#{@service_name}/#{@version}/#{@region}/#{@ip_address}/#{@port}"] = MultiJson.encode(config)
61
- end
63
+
64
+ @read_timeout = 3.0
62
65
  end
63
66
 
64
67
  teardown do
65
- @server.terminate if @server
66
- # De-register server in doozer
67
- RubySkynet::Registry.doozer_pool.with_connection do |doozer|
68
- doozer.delete("/services/#{@service_name}/#{@version}/#{@region}/#{@ip_address}/#{@port}") rescue nil
69
- end
68
+ RubySkynet::Server.stop
70
69
  end
71
70
 
72
71
  context "with client connection" do
@@ -0,0 +1,99 @@
1
+ # Allow test to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
6
+ require 'shoulda'
7
+ require 'mocha/setup'
8
+ require 'ruby_skynet'
9
+
10
+ # Register an appender if one is not already registered
11
+ if SemanticLogger::Logger.appenders.size == 0
12
+ SemanticLogger::Logger.default_level = :trace
13
+ SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('test.log')
14
+ end
15
+
16
+ # Unit Test
17
+ class RegistryTest < Test::Unit::TestCase
18
+ context 'RubySkynet::Service' do
19
+
20
+ setup do
21
+ @service_name = 'MyRegistryService'
22
+ @version = 5
23
+ @region = 'RegistryTest'
24
+ @hostname = '127.0.0.1'
25
+ @port = 2100
26
+ @service_key = "/services/#{@service_name}/#{@version}/#{@region}/#{@hostname}/#{@port}"
27
+ end
28
+
29
+ context "without a registered service" do
30
+ should "not be in doozer" do
31
+ RubySkynet::Registry.send(:doozer_pool).with_connection do |doozer|
32
+ assert_equal '', doozer[@service_key]
33
+ end
34
+ end
35
+ end
36
+
37
+ context "with a registered service" do
38
+ setup do
39
+ RubySkynet::Registry.register_service(@service_name, @version, @region, @hostname, @port)
40
+ # Allow time for doozer callback that service was registered
41
+ sleep 0.1
42
+ end
43
+
44
+ teardown do
45
+ RubySkynet::Registry.deregister_service(@service_name, @version, @region, @hostname, @port)
46
+ # Allow time for doozer callback that service was deregistered
47
+ sleep 0.1
48
+ # No servers should be in the local registry
49
+ assert_equal nil, RubySkynet::Registry.servers_for(@service_name, @version, @region)
50
+ end
51
+
52
+ should "find server using exact match" do
53
+ assert servers = RubySkynet::Registry.servers_for(@service_name, @version, @region)
54
+ assert_equal 1, servers.size
55
+ assert_equal "#{@hostname}:#{@port}", servers.first
56
+ end
57
+
58
+ should "find server using * version match" do
59
+ assert servers = RubySkynet::Registry.servers_for(@service_name, '*', @region)
60
+ assert_equal 1, servers.size
61
+ assert_equal "#{@hostname}:#{@port}", servers.first
62
+ end
63
+
64
+ should "return nil when service not found" do
65
+ assert_equal nil, RubySkynet::Registry.servers_for('MissingService', @version, @region)
66
+ end
67
+
68
+ should "return nil when version not found" do
69
+ assert_equal nil, RubySkynet::Registry.servers_for(@service_name, @version+1, @region)
70
+ end
71
+
72
+ should "return nil when region not found" do
73
+ assert_equal nil, RubySkynet::Registry.servers_for(@service_name, @version, 'OtherRegion')
74
+ end
75
+
76
+ should "be in doozer" do
77
+ RubySkynet::Registry.send(:doozer_pool).with_connection do |doozer|
78
+ assert_equal true, doozer[@service_key].length > 20
79
+ end
80
+ end
81
+ end
82
+
83
+ context "scoring" do
84
+ [
85
+ ['192.168.11.0', 4 ],
86
+ ['192.168.11.10', 3 ],
87
+ ['192.168.10.0', 2 ],
88
+ ['192.5.10.0', 1 ],
89
+ ['10.0.11.0', 0 ],
90
+ ].each do |test|
91
+ should "handle score #{test[1]}" do
92
+ RubySkynet::Common.stubs(:local_ip_address).returns("192.168.11.0")
93
+ assert_equal test[1], RubySkynet::Registry.score_for_server(test[0]), "Local: #{RubySkynet::Common.local_ip_address} Server: #{test[0]} Score: #{test[1]}"
94
+ end
95
+ end
96
+ end
97
+
98
+ end
99
+ end
@@ -12,10 +12,6 @@ if SemanticLogger::Logger.appenders.size == 0
12
12
  SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('test.log')
13
13
  end
14
14
 
15
- RubySkynet::Server.port = 2100
16
- RubySkynet::Server.region = 'Test'
17
- RubySkynet::Server.hostname = '127.0.0.1'
18
-
19
15
  class TestService
20
16
  include RubySkynet::Service
21
17
 
@@ -25,38 +21,34 @@ class TestService
25
21
  def echo(params)
26
22
  params
27
23
  end
24
+
25
+ def exception_test(params)
26
+ raise "Exception message"
27
+ end
28
28
  end
29
29
 
30
30
  # Unit Test for ResilientSocket::TCPClient
31
- class RubySkynetServiceTest < Test::Unit::TestCase
31
+ class ServiceTest < Test::Unit::TestCase
32
32
  context 'RubySkynet::Service' do
33
33
  context "with server" do
34
34
  setup do
35
- RubySkynet::Server.start
35
+ @region = 'Test'
36
36
  @service_name = 'TestService'
37
37
  @version = 1
38
- @region = 'Test'
39
- @doozer_key = "/services/#{@service_name}/#{@version}/#{@region}/127.0.0.1/2100"
38
+ RubySkynet::Server.start(@region)
39
+ sleep 0.2
40
40
  end
41
41
 
42
42
  teardown do
43
- begin
44
- RubySkynet::Server.stop
45
- rescue Celluloid::DeadActorError
46
- end
43
+ RubySkynet::Server.stop
44
+ SemanticLogger::Logger.flush
47
45
  end
48
46
 
49
- should "have correct service key" do
50
- assert_equal @doozer_key, TestService.service_key
47
+ should "be running" do
48
+ assert_equal true, RubySkynet::Server.running?
51
49
  end
52
50
 
53
- should "register service" do
54
- RubySkynet::Registry.doozer_pool.with_connection do |doozer|
55
- assert_equal true, doozer[@doozer_key].length > 20
56
- end
57
- end
58
-
59
- context "calling with a client" do
51
+ context "using a client" do
60
52
  setup do
61
53
  @client = RubySkynet::Client.new(@service_name, @version, @region)
62
54
  end
@@ -66,6 +58,13 @@ class RubySkynetServiceTest < Test::Unit::TestCase
66
58
  assert_equal 'some', reply.keys.first
67
59
  assert_equal 'parameters', reply.values.first
68
60
  end
61
+
62
+ # Cellulloid 0.13.0.pre2 is doing something weird here and preventing the
63
+ # Server from receiving the exception
64
+ should "handle service exceptions" do
65
+ reply = @client.call(:exception_test, 'some' => 'parameters')
66
+ assert_equal "Exception message", reply['exception']['message']
67
+ end
69
68
  end
70
69
 
71
70
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_skynet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0.pre2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-03-19 00:00:00.000000000 Z
11
+ date: 2013-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: semantic_logger
@@ -80,20 +80,6 @@ dependencies:
80
80
  - - '>='
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: celluloid-io
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - '>='
88
- - !ruby/object:Gem::Version
89
- version: 0.13.0.pre2
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - '>='
95
- - !ruby/object:Gem::Version
96
- version: 0.13.0.pre2
97
83
  description: Ruby Client for invoking Skynet services
98
84
  email:
99
85
  - reidmo@gmail.com
@@ -122,10 +108,10 @@ files:
122
108
  - lib/ruby_skynet/service.rb
123
109
  - lib/ruby_skynet/version.rb
124
110
  - test.sh
111
+ - test/client_test.rb
125
112
  - test/doozer_client_test.rb
126
- - test/ruby_skynet_client_test.rb
127
- - test/ruby_skynet_service_test.rb
128
- - test/simple_server.rb
113
+ - test/registry_test.rb
114
+ - test/service_test.rb
129
115
  homepage: https://github.com/ClarityServices/ruby_skynet
130
116
  licenses:
131
117
  - Apache License V2.0
@@ -141,9 +127,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
127
  version: '0'
142
128
  required_rubygems_version: !ruby/object:Gem::Requirement
143
129
  requirements:
144
- - - '>'
130
+ - - '>='
145
131
  - !ruby/object:Gem::Version
146
- version: 1.3.1
132
+ version: '0'
147
133
  requirements: []
148
134
  rubyforge_project:
149
135
  rubygems_version: 2.0.2
@@ -1,122 +0,0 @@
1
- require 'rubygems'
2
- require 'socket'
3
- require 'bson'
4
- require 'semantic_logger'
5
- require 'celluloid/io'
6
-
7
- # This a simple stand-alone server that does not use the Skynet code so that
8
- # the Skynet code can be tested
9
-
10
- # Simple single threaded server for testing purposes using a local socket
11
- # Sends and receives BSON Messages
12
- class SimpleServer
13
- include Celluloid::IO
14
-
15
- def initialize(port)
16
- # Since we included Celluloid::IO, we're actually making a
17
- # Celluloid::IO::TCPServer here
18
- @server = TCPServer.new('127.0.0.1', port)
19
- @logger = SemanticLogger::Logger.new(self.class)
20
- async.run
21
- end
22
-
23
- def run
24
- loop do
25
- @logger.debug "Waiting for a client to connect"
26
- async.handle_connection(@server.accept)
27
- end
28
- end
29
-
30
- def finalize
31
- @server.close if @server
32
- end
33
-
34
- # Called for each message received from the client
35
- # Returns a Hash that is sent back to the caller
36
- def on_message(method, params)
37
- case method
38
- when 'test1'
39
- { 'result' => 'test1' }
40
- when 'sleep'
41
- sleep params['duration'] || 1
42
- { 'result' => 'sleep' }
43
- when 'fail'
44
- if params['attempt'].to_i >= 2
45
- { 'result' => 'fail' }
46
- else
47
- nil
48
- end
49
- else
50
- { 'result' => "Unknown method: #{method}" }
51
- end
52
- end
53
-
54
- # Called for each client connection
55
- def handle_connection(client)
56
- @logger.debug "Client connected, waiting for data from client"
57
-
58
- # Process handshake
59
- handshake = {
60
- 'registered' => true,
61
- 'clientid' => '123'
62
- }
63
- client.write(BSON.serialize(handshake).to_s)
64
- read_bson_document(client)
65
-
66
- while(header = read_bson_document(client)) do
67
- @logger.debug "\n******************"
68
- @logger.debug "Received Request"
69
- @logger.trace 'Header', header
70
-
71
- request = read_bson_document(client)
72
- @logger.trace 'Request', request
73
- break unless request
74
-
75
- if reply = on_message(request['method'], BSON.deserialize(request['in']))
76
- @logger.debug "Sending Header"
77
- # For this test we just send back the received header
78
- client.write(BSON.serialize(header).to_s)
79
-
80
- @logger.debug "Sending Reply"
81
- @logger.trace 'Reply', reply
82
- client.write(BSON.serialize({'out' => BSON.serialize(reply).to_s}).to_s)
83
- else
84
- @logger.debug "Closing client since no reply is being sent back"
85
- @server.close
86
- client.close
87
- @logger.debug "Server closed"
88
- async.run
89
- @logger.debug "Server Restarted"
90
- break
91
- end
92
- end
93
- # Disconnect from the client
94
- client.close
95
- @logger.debug "Disconnected from the client"
96
- end
97
-
98
- # Read the bson document, returning nil if the IO is closed
99
- # before receiving any data or a complete BSON document
100
- def read_bson_document(io)
101
- bytebuf = BSON::ByteBuffer.new
102
- # Read 4 byte size of following BSON document
103
- bytes = io.read(4)
104
- return unless bytes
105
- # Read BSON document
106
- sz = bytes.unpack("V")[0]
107
- bytebuf.append!(bytes)
108
- bytes = io.read(sz-4)
109
- return unless bytes
110
- bytebuf.append!(bytes)
111
- return BSON.deserialize(bytebuf)
112
- end
113
-
114
- end
115
-
116
- if $0 == __FILE__
117
- SemanticLogger::Logger.default_level = :trace
118
- SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new(STDOUT)
119
- Celluloid.logger = SemanticLogger::Logger.new('Celluloid')
120
- server = SimpleServer.new(2000)
121
- server.thread.join
122
- end