ruby_skynet 0.4.0.pre2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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