rjr 0.15.1 → 0.16.1

Sign up to get free protection for your applications and to get access to all the features.
data/bin/rjr-client CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  require 'optparse'
8
8
  require 'rjr/common'
9
- require 'rjr/messages'
9
+ require 'rjr/message'
10
10
  require 'rjr/nodes/easy'
11
11
 
12
12
  RJR::Logger.log_level = ::Logger::DEBUG
@@ -59,7 +59,7 @@ optparse = OptionParser.new do |opts|
59
59
  config[:block] = !ret
60
60
  end
61
61
 
62
- opts.on('-n', '--num number_of_messages', 'Number of messages to send to server (may be :rand or indefinite)') do |n|
62
+ opts.on('-n', '--num number_of_messages', 'Number of messages to send to server (may be a number, :rand, or :indefinite)') do |n|
63
63
  config[:num_msg] = case n.to_s.intern
64
64
  when :rand then rand(MAX_MESSAGES)
65
65
  when :indefinite then 1000000000 # XXX 'indefinite ;-)'
@@ -99,7 +99,9 @@ client_path = File.join(ENV['RJR_LOAD_PATH'] || File.join(cdir, '..', 'example
99
99
  ##########################################################
100
100
 
101
101
  node = RJR::Nodes::Easy.new(NODES)
102
- node.dispatcher.add_modules(client_path)
102
+ client_path.split(':').each { |cp|
103
+ node.dispatcher.add_modules(cp)
104
+ }
103
105
 
104
106
  if config[:disconnect]
105
107
  disconnect_thread = Thread.new {
data/lib/rjr/common.rb CHANGED
@@ -4,7 +4,6 @@
4
4
  #
5
5
  # Copyright (C) 2011-2013 Mohammed Morsi <mo@morsi.org>
6
6
  # Licensed under the Apache License, Version 2.0
7
-
8
7
  require 'logger'
9
8
 
10
9
  # Return a random uuid
@@ -34,24 +33,51 @@ class Logger
34
33
  @logger = ::Logger.new(output)
35
34
  @logger.level = @log_level || ::Logger::FATAL
36
35
  @logger_mutex = Mutex.new
36
+ @filters = []
37
+ @highlights = []
37
38
  end
38
39
  end
39
40
 
40
41
  public
41
42
 
43
+ # Add method which to call on every log message to determine
44
+ # if messages should be included/excluded
45
+ def self.add_filter(filter)
46
+ @logger_mutex.synchronize{
47
+ @filters << filter
48
+ }
49
+ end
50
+
51
+ # Add a method which to call on every log message to determine
52
+ # if message should be highlighted
53
+ def self.highlight(hlight)
54
+ @logger_mutex.synchronize{
55
+ @highlights << hlight
56
+ }
57
+ end
58
+
42
59
  def self.method_missing(method_id, *args)
43
60
  _instantiate_logger
44
61
  @logger_mutex.synchronize {
45
- if args.first.is_a?(Array)
46
- args.first.each{ |a|
47
- @logger.send(method_id, a)
48
- }
49
- else
50
- @logger.send(method_id, args)
51
- end
62
+ args = args.first if args.first.is_a?(Array)
63
+ args.each { |a|
64
+ # run highlights / filters against output before
65
+ # sending formatted output to logger
66
+ # TODO allow user to customize highlight mechanism/text
67
+ na = @highlights.any? { |h| h.call a } ?
68
+ "\e[1m\e[31m#{a}\e[0m\e[0m" : a
69
+ @logger.send(method_id, na) if @filters.all? { |f| f.call a }
70
+ }
52
71
  }
53
72
  end
54
73
 
74
+ def self.safe_exec(*args, &bl)
75
+ _instantiate_logger
76
+ @logger_mutex.synchronize {
77
+ bl.call *args
78
+ }
79
+ end
80
+
55
81
  def self.logger
56
82
  _instantiate_logger
57
83
  @logger
@@ -95,6 +121,13 @@ end
95
121
 
96
122
  end # module RJR
97
123
 
124
+ # Serialized puts, uses logger lock to serialize puts output
125
+ def sputs(*args)
126
+ ::RJR::Logger.safe_exec {
127
+ puts *args
128
+ }
129
+ end
130
+
98
131
  class Object
99
132
  def eigenclass
100
133
  class << self
@@ -135,9 +135,16 @@ class Request
135
135
  # Invoke the request by calling the registered handler with the registered
136
136
  # method parameters in the local scope
137
137
  def handle
138
- RJR::Logger.info "Dispatching '#{@rjr_method}' request with parameters (#{@rjr_method_args.join(',')}) on #{@rjr_node_type}-node(#{@rjr_node_id})"
138
+ node_sig = "#{@rjr_node_id}(#{@rjr_node_type})"
139
+ method_sig = "#{@rjr_method}(#{@rjr_method_args.join(',')})"
140
+
141
+ RJR::Logger.info "#{node_sig}->#{method_sig}"
142
+
139
143
  retval = instance_exec(*@rjr_method_args, &@rjr_handler)
140
- RJR::Logger.info "#{@rjr_method} request with parameters (#{@rjr_method_args.join(',')}) returning #{retval}"
144
+
145
+ RJR::Logger.info \
146
+ "#{node_sig}<-#{method_sig}<-#{retval.nil? ? "nil" : retval}"
147
+
141
148
  return retval
142
149
  end
143
150
 
@@ -174,12 +181,16 @@ class Dispatcher
174
181
  # Registered json-rpc request signatures and corresponding handlers
175
182
  attr_reader :handlers
176
183
 
184
+ # Registered json-rpc request signatures and environments which to execute handlers in
185
+ attr_reader :environments
186
+
177
187
  # Requests which have been dispatched
178
188
  def requests ; @requests_lock.synchronize { Array.new(@requests) } ; end
179
189
 
180
190
  # RJR::Dispatcher intializer
181
191
  def initialize
182
192
  @handlers = Hash.new()
193
+ @environments = Hash.new()
183
194
 
184
195
  @requests_lock = Mutex.new
185
196
  @requests = []
@@ -194,57 +205,88 @@ class Dispatcher
194
205
  # a file, directory, or path specification (dirs seperated with ':')
195
206
  # @return self
196
207
  def add_module(name)
197
- name.split(':').each { |p|
198
- if File.directory?(p)
199
- # TODO also .so files? allow user to specify suffix
200
- Dir.glob(File.join(p, '*.rb')).all? { |m|
201
- require m
202
- }
208
+ require name
203
209
 
204
- else
205
- require p
210
+ m = name.downcase.gsub(File::SEPARATOR, '_')
211
+ method("dispatch_#{m}".intern).call(self)
206
212
 
207
- end
208
-
209
- m = p.split(File::SEPARATOR).last
210
- method("dispatch_#{m}".intern).call(self)
211
- }
212
213
  self
213
214
  end
214
215
  alias :add_modules :add_module
215
216
 
216
217
  # Register json-rpc handler with dispatcher
217
218
  #
218
- # @param [String] signature request signature to match
219
+ # @param [String,Regex] signature request signature to match
219
220
  # @param [Callable] callable callable object which to bind to signature
220
221
  # @param [Callable] &bl block parameter will be set to callback if specified
221
222
  # @return self
222
223
  def handle(signature, callback = nil, &bl)
223
224
  if signature.is_a?(Array)
224
225
  signature.each { |s| handle(s, callback, &bl) }
225
- return
226
+ return self
226
227
  end
227
228
  @handlers[signature] = callback unless callback.nil?
228
229
  @handlers[signature] = bl unless bl.nil?
229
230
  self
230
231
  end
231
232
 
233
+ # Return boolean indicating if dispatcher can handle method
234
+ #
235
+ # @param [String] string rjr method to match
236
+ # @return [true,false] indicating if requests to specified method will be matched
237
+ def handles?(rjr_method)
238
+ !@handlers.find { |k,v|
239
+ k.is_a?(String) ?
240
+ k == rjr_method :
241
+ k =~ rjr_method
242
+ }.nil?
243
+ end
244
+
245
+ # Register environment to run json-rpc handler w/ dispatcher.
246
+ #
247
+ # Currently environments may be set to modules which requests
248
+ # will extend before executing handler
249
+ #
250
+ # @param [String,Regex] signature request signature to match
251
+ # @param [Module] module which to extend requests with
252
+ # @return self
253
+ def env(signature, environment)
254
+ if signature.is_a?(Array)
255
+ signature.each { |s| env(s, environment) }
256
+ return self
257
+ end
258
+ @environments[signature] = environment
259
+ self
260
+ end
261
+
232
262
  # Dispatch received request. (used internally by nodes).
233
263
  #
234
264
  # Arguments should include :rjr_method and other parameters
235
265
  # required to construct a valid Request instance
236
266
  def dispatch(args = {})
237
- # currently we just match method name against signature
238
- # TODO if signature if a regex, match against method name
239
- # or if callable, invoke it w/ request checking boolean return value for match
240
- # TODO not using concurrent access portection, assumes all handlers are registered
267
+ # currently we match method name string or regex against signature
268
+ # TODO not using concurrent access protection, assumes all handlers are registered
241
269
  # before first dispatch occurs
242
- handler = @handlers[args[:rjr_method]]
270
+ handler = @handlers.find { |k,v|
271
+ k.is_a?(String) ?
272
+ k == args[:rjr_method] :
273
+ k =~ args[:rjr_method] }
274
+
275
+ # TODO currently just using last environment that matches,
276
+ # allow multiple environments to be used?
277
+ environment = @environments.keys.select { |k|
278
+ k.is_a?(String) ?
279
+ k == args[:rjr_method] :
280
+ k =~ args[:rjr_method]
281
+ }.last
243
282
 
244
283
  return Result.method_not_found(args[:rjr_method]) if handler.nil?
245
284
 
246
285
  # TODO compare arity of handler to number of method_args passed in
247
- request = Request.new args.merge(:rjr_handler => handler)
286
+ request = Request.new args.merge(:rjr_handler => handler.last)
287
+
288
+ # set request environment
289
+ request.extend(@environments[environment]) unless environment.nil?
248
290
 
249
291
  begin
250
292
  retval = request.handle
@@ -3,7 +3,6 @@
3
3
  # Copyright (C) 2012-2013 Mohammed Morsi <mo@morsi.org>
4
4
  # Licensed under the Apache License, Version 2.0
5
5
 
6
- require 'singleton'
7
6
  require 'eventmachine'
8
7
 
9
8
  module RJR
@@ -11,8 +10,6 @@ module RJR
11
10
  # EventMachine adapater interface, ties reactor
12
11
  # lifecycle to an instance of this class.
13
12
  class EMAdapter
14
- include Singleton
15
-
16
13
  # Run reactor in its own interally managed thread
17
14
  attr_accessor :reactor_thread
18
15
 
@@ -8,6 +8,14 @@
8
8
  # Copyright (C) 2013 Mohammed Morsi <mo@morsi.org>
9
9
  # Licensed under the Apache License, Version 2.0
10
10
 
11
+ # TODO
12
+ # - data received/sent (on different interfaces)
13
+ # - messages received/sent (on different interfaces)
14
+ # - dispatches on a per node basis
15
+ # - unresolved/invalid dispatches/messages
16
+ # - em jobs
17
+ # - thread pool jobs (started / completed / etc)
18
+
11
19
  require 'eventmachine'
12
20
 
13
21
  # Helper method to process user params / select stats
@@ -37,34 +45,51 @@ def select_stats(dispatcher, *filter)
37
45
  dispatcher.requests.select { |ds| lf.all? { |lfi| lfi.call(ds) } }
38
46
  end
39
47
 
40
- # Add stats methods to specified dispatcher
41
- def dispatch_stats(dispatcher)
48
+ # Add inspection methods to specified dispatcher
49
+ def dispatch_rjr_inspect(dispatcher)
42
50
  # Retrieve all the dispatches this node served matching the specified criteri
43
51
  dispatcher.handle "rjr::dispatches" do |filter|
44
- select_stats(*filter)
52
+ select_stats(dispatcher, *filter)
45
53
  end
46
54
 
47
55
  # Retrieve the number of dispatches this node served matching the specified criteria
48
56
  dispatcher.handle "rjr::num_dispatches" do |filter|
49
- select_stats(*filter).size
57
+ select_stats(dispatcher, *filter).size
50
58
  end
51
59
 
52
60
  # Retrieve the internal status of this node
53
61
  dispatcher.handle "rjr::status" do
62
+ nodes = []
63
+ ObjectSpace.each_object RJR::Node do |node|
64
+ nodes << node.to_s
65
+ end
66
+
54
67
  {
68
+ # nodes
69
+ :nodes => nodes,
70
+
71
+ # dispatcher
72
+ :dispatcher => {
73
+ :requests => dispatcher.requests.size,
74
+ :handlers =>
75
+ dispatcher.handlers.keys,
76
+ #dispatcher.handlers.collect { |k,v|
77
+ # [k, v.source_location] },
78
+ :environments => dispatcher.environments
79
+ },
80
+
55
81
  # event machine
56
82
  :event_machine => { :running => EventMachine.reactor_running?,
57
- :thread_status => RJR::EMAdapter.instance.rector_thread ?
58
- RJR::EMAdapter.instance.reactor_thread.status :
59
- nil,
83
+ :thread_status =>
84
+ (RJR::Node.em && RJR::Node.em.reactor_thread) ?
85
+ RJR::Node.em.reactor_thread.status : nil,
60
86
  :connections => EventMachine.connection_count },
61
87
 
62
88
  # thread pool
63
- :thread_pool => { :running => RJR::ThreadPool.instance.running? }
89
+ :thread_pool => { :running => RJR::Node.tp ? RJR::Node.tp.running? : nil }
64
90
  }
65
91
  end
66
92
 
67
93
  #:log =>
68
94
  # lambda {},
69
95
  end
70
-
data/lib/rjr/message.rb CHANGED
@@ -5,8 +5,9 @@
5
5
  # Copyright (C) 2012-2013 Mohammed Morsi <mo@morsi.org>
6
6
  # Licensed under the Apache License, Version 2.0
7
7
 
8
- # establish client connection w/ specified args and invoke block w/
9
- # newly created client, returning it after block terminates
8
+ # FIXME https://github.com/flori/json/issues/179
9
+ # if pull request doesn't get accepted implement
10
+ # one of the workarounds in rjr
10
11
 
11
12
  require 'json'
12
13
  require 'rjr/common'
data/lib/rjr/node.rb CHANGED
@@ -44,6 +44,15 @@ class Node
44
44
  # Dispatcher to use to satisfy requests
45
45
  attr_accessor :dispatcher
46
46
 
47
+ # alias of RJR_NODE_TYPE
48
+ def node_type
49
+ self.class::RJR_NODE_TYPE
50
+ end
51
+
52
+ # XXX used by debugging / stats interface
53
+ def self.em ; defined?(@@em) ? @@em : nil end
54
+ def self.tp ; defined?(@@tp) ? @@tp : nil end
55
+
47
56
  # RJR::Node initializer
48
57
  #
49
58
  # @param [Hash] args options to set on request
@@ -60,16 +69,20 @@ class Node
60
69
  @dispatcher = args[:dispatcher] || RJR::Dispatcher.new
61
70
  @message_headers = args.has_key?(:headers) ? {}.merge(args[:headers]) : {}
62
71
 
63
- @tp = ThreadPool.instance.start
64
- @em = EMAdapter.instance.start
72
+ @@tp ||= ThreadPool.new
73
+ @@em ||= EMAdapter.new
74
+
75
+ # will do nothing if already started
76
+ @@tp.start
77
+ @@em.start
65
78
  end
66
79
 
67
80
  # Block until the eventmachine reactor and thread pool have both completed running
68
81
  #
69
82
  # @return self
70
83
  def join
71
- @tp.join
72
- @em.join
84
+ @@tp.join
85
+ @@em.join
73
86
  self
74
87
  end
75
88
 
@@ -80,8 +93,8 @@ class Node
80
93
  #
81
94
  # @return self
82
95
  def halt
83
- @em.stop_event_loop
84
- @tp.stop
96
+ @@em.stop_event_loop
97
+ @@tp.stop
85
98
  self
86
99
  end
87
100
 
@@ -113,10 +126,10 @@ class Node
113
126
  # Internal helper, handle message received
114
127
  def handle_message(msg, connection = {})
115
128
  if RequestMessage.is_request_message?(msg)
116
- @tp << ThreadPoolJob.new(msg) { |m| handle_request(m, false, connection) }
129
+ @@tp << ThreadPoolJob.new(msg) { |m| handle_request(m, false, connection) }
117
130
 
118
131
  elsif NotificationMessage.is_notification_message?(msg)
119
- @tp << ThreadPoolJob.new(msg) { |m| handle_request(m, true, connection) }
132
+ @@tp << ThreadPoolJob.new(msg) { |m| handle_request(m, true, connection) }
120
133
 
121
134
  elsif ResponseMessage.is_response_message?(msg)
122
135
  handle_response(msg)
@@ -142,12 +155,12 @@ class Node
142
155
  NotificationMessage.new(:message => data,
143
156
  :headers => @message_headers) :
144
157
  RequestMessage.new(:message => data,
145
- :headers => @message_headers)
158
+ :headers => @message_headers)
146
159
 
147
160
  result =
148
161
  @dispatcher.dispatch(:rjr_method => msg.jr_method,
149
162
  :rjr_method_args => msg.jr_args,
150
- :headers => msg.headers,
163
+ :rjr_headers => msg.headers,
151
164
  :rjr_client_ip => client_ip,
152
165
  :rjr_client_port => client_port,
153
166
  :rjr_node => self,
@@ -164,6 +177,8 @@ class Node
164
177
  self.send_msg(response.to_s, connection)
165
178
  return response
166
179
  end
180
+
181
+ nil
167
182
  end
168
183
 
169
184
  # Internal helper, handle response message received
@@ -61,30 +61,33 @@ class AMQP < RJR::Node
61
61
  return
62
62
  end
63
63
 
64
- @conn = ::AMQP.connect(:host => @broker) do |*args|
65
- on_init.call
66
- end
67
- @conn.on_tcp_connection_failure { puts "OTCF #{@node_id}" }
64
+ @conn = ::AMQP.connect(:host => @broker) do |conn|
65
+ ::AMQP.connection = conn # XXX not sure why this is needed but the amqp
66
+ # em interface won't shut down cleanly otherwise
67
+
68
+ conn.on_tcp_connection_failure { puts "OTCF #{@node_id}" }
68
69
 
69
- # TODO move the rest into connect callback ?
70
+ ### connect to qpid broker
71
+ @channel = ::AMQP::Channel.new(conn)
70
72
 
71
- ### connect to qpid broker
72
- @channel = ::AMQP::Channel.new(@conn)
73
+ # qpid constructs that will be created for node
74
+ @queue_name = "#{@node_id.to_s}-queue"
75
+ @queue = @channel.queue(@queue_name, :auto_delete => true)
76
+ @exchange = @channel.default_exchange
73
77
 
74
- # qpid constructs that will be created for node
75
- @queue_name = "#{@node_id.to_s}-queue"
76
- @queue = @channel.queue(@queue_name, :auto_delete => true)
77
- @exchange = @channel.default_exchange
78
+ @listening = false
79
+ #@disconnected = false
78
80
 
79
- @listening = false
80
- #@disconnected = false
81
+ @exchange.on_return do |basic_return, metadata, payload|
82
+ puts "#{payload} was returned! reply_code = #{basic_return.reply_code}, reply_text = #{basic_return.reply_text}"
83
+ #@disconnected = true # FIXME member will be set on wrong class
84
+ # TODO these are only run when we fail to send message to queue,
85
+ # need to detect when that queue is shutdown & other events
86
+ connection_event(:error)
87
+ connection_event(:closed)
88
+ end
81
89
 
82
- @exchange.on_return do |basic_return, metadata, payload|
83
- puts "#{payload} was returned! reply_code = #{basic_return.reply_code}, reply_text = #{basic_return.reply_text}"
84
- #@disconnected = true # FIXME member will be set on wrong class
85
- # TODO these are only run when we fail to send message to queue, need to detect when that queue is shutdown & other events
86
- connection_event(:error)
87
- connection_event(:closed)
90
+ on_init.call
88
91
  end
89
92
  end
90
93
 
@@ -116,6 +119,10 @@ class AMQP < RJR::Node
116
119
  @amqp_lock = Mutex.new
117
120
  end
118
121
 
122
+ def to_s
123
+ "RJR::Nodes::AMQP<#{@node_id},#{@broker},#{@queue_name}>"
124
+ end
125
+
119
126
  # Publish a message using the amqp exchange
120
127
  #
121
128
  # Implementation of {RJR::Node#send_msg}
@@ -137,7 +144,7 @@ class AMQP < RJR::Node
137
144
  #
138
145
  # Implementation of {RJR::Node#listen}
139
146
  def listen
140
- @em.schedule do
147
+ @@em.schedule do
141
148
  init_node {
142
149
  subscribe # start receiving messages
143
150
  }
@@ -161,7 +168,7 @@ class AMQP < RJR::Node
161
168
  message = RequestMessage.new :method => rpc_method,
162
169
  :args => args,
163
170
  :headers => @message_headers
164
- @em.schedule do
171
+ @@em.schedule do
165
172
  init_node {
166
173
  subscribe # begin listening for result
167
174
  send_msg(message.to_s, :routing_key => routing_key, :reply_to => @queue_name)
@@ -197,7 +204,7 @@ class AMQP < RJR::Node
197
204
  message = NotificationMessage.new :method => rpc_method,
198
205
  :args => args,
199
206
  :headers => @message_headers
200
- @em.schedule do
207
+ @@em.schedule do
201
208
  init_node {
202
209
  send_msg(message.to_s, :routing_key => routing_key, :reply_to => @queue_name){
203
210
  published_l.synchronize { invoked = true ; published_c.signal }
@@ -26,7 +26,7 @@ module Nodes
26
26
  #
27
27
  # @example Listening for and dispatching json-rpc requests locally
28
28
  # # initialize node
29
- # node = RJR::LocalNode.new :node_id => 'node'
29
+ # node = RJR::Nodes::Local.new :node_id => 'node'
30
30
  #
31
31
  # node.dispatcher.handle('hello') do |name|
32
32
  # @rjr_node_type == :local ? "Hello superuser #{name}" : "Hello #{name}!"
@@ -41,13 +41,17 @@ class Local < RJR::Node
41
41
  # allows clients to override the node type for the local node
42
42
  attr_accessor :node_type
43
43
 
44
- # LocalNode initializer
44
+ # Nodes::Local initializer
45
45
  # @param [Hash] args the options to create the local node with
46
46
  def initialize(args = {})
47
47
  super(args)
48
48
  @node_type = RJR_NODE_TYPE
49
49
  end
50
50
 
51
+ def to_s
52
+ "RJR::Nodes::Local<#{@node_id}>"
53
+ end
54
+
51
55
  # Send data using specified connection.
52
56
  #
53
57
  # Simply dispatch local notification.
@@ -55,8 +59,8 @@ class Local < RJR::Node
55
59
  # Implementation of {RJR::Node#send_msg}
56
60
  def send_msg(msg, connection)
57
61
  # ignore response message
58
- unless msg.is_a?(ResponseMessage)
59
- handle_request(msg, true, nil)
62
+ unless ResponseMessage.is_response_message?(msg)
63
+ launch_request(msg, true) # .join?
60
64
  end
61
65
  end
62
66
 
@@ -68,6 +72,18 @@ class Local < RJR::Node
68
72
  self
69
73
  end
70
74
 
75
+ # Helper to launch request in new thread
76
+ #
77
+ # This needs to happen so that each request runs in its own context
78
+ # (or close to it, globals will still be available, but locks will
79
+ # not be locally held, etc)
80
+ def launch_request(req, notification)
81
+ Thread.new(req,notification) { |req,notification|
82
+ res = handle_request(req, notification, nil)
83
+ handle_response(res.to_s) unless res.nil?
84
+ }
85
+ end
86
+
71
87
  # Instructs node to send rpc request, and wait for and return response
72
88
  #
73
89
  # Implementation of {RJR::Node#invoke}
@@ -85,9 +101,16 @@ class Local < RJR::Node
85
101
  0.upto(args.size).each { |i| args[i] = args[i].to_s if args[i].is_a?(Symbol) }
86
102
  message = RequestMessage.new(:method => rpc_method,
87
103
  :args => args,
88
- :headers => @message_headers).to_s
89
- res = handle_request(message, false, nil)
90
- return @dispatcher.handle_response(res.result)
104
+ :headers => @message_headers)
105
+ launch_request(message.to_s, false)
106
+
107
+ # TODO optional timeout for response ?
108
+ res = wait_for_result(message)
109
+
110
+ if res.size > 2
111
+ raise Exception, res[2]
112
+ end
113
+ return res[1]
91
114
  end
92
115
 
93
116
  # Instructs node to send rpc notification (immediately returns / no response is generated)
@@ -99,15 +122,11 @@ class Local < RJR::Node
99
122
  # @param [String] rpc_method json-rpc method to invoke on destination
100
123
  # @param [Array] args array of arguments to convert to json and invoke remote method wtih
101
124
  def notify(rpc_method, *args)
102
- # TODO run in thread & immediately return?
103
- begin
104
- 0.upto(args.size).each { |i| args[i] = args[i].to_s if args[i].is_a?(Symbol) }
105
- message = NotificationMessage.new(:method => rpc_method,
106
- :args => args,
107
- :headers => @message_headers).to_s
108
- handle_request(message, true, nil)
109
- rescue
110
- end
125
+ 0.upto(args.size).each { |i| args[i] = args[i].to_s if args[i].is_a?(Symbol) }
126
+ message = NotificationMessage.new(:method => rpc_method,
127
+ :args => args,
128
+ :headers => @message_headers)
129
+ launch_request(message.to_s, true) #.join ?
111
130
  nil
112
131
  end
113
132
 
@@ -56,6 +56,10 @@ class Multi < RJR::Node
56
56
  } if args[:nodes]
57
57
  end
58
58
 
59
+ def to_s
60
+ "RJR::Nodes::Multi<#{@node_id}>"
61
+ end
62
+
59
63
  # Add node to multinode
60
64
  # @param [RJR::Node] node the node to add
61
65
  def <<(node)
@@ -0,0 +1 @@
1
+ # TODO
data/lib/rjr/nodes/tcp.rb CHANGED
@@ -116,6 +116,10 @@ class TCP < RJR::Node
116
116
  @connections_lock = Mutex.new
117
117
  end
118
118
 
119
+ def to_s
120
+ "RJR::Nodes::TCP<#{@node_id},#{@host},#{@port}>"
121
+ end
122
+
119
123
  # Send data using specified connection
120
124
  #
121
125
  # Implementation of {RJR::Node#send_msg}
@@ -127,8 +131,8 @@ class TCP < RJR::Node
127
131
  #
128
132
  # Implementation of {RJR::Node#listen}
129
133
  def listen
130
- @em.schedule {
131
- @em.start_server @host, @port, TCPConnection, { :rjr_node => self }
134
+ @@em.schedule {
135
+ @@em.start_server @host, @port, TCPConnection, { :rjr_node => self }
132
136
  }
133
137
  self
134
138
  end
@@ -152,7 +156,7 @@ class TCP < RJR::Node
152
156
  :args => args,
153
157
  :headers => @message_headers
154
158
  connection = nil
155
- @em.schedule {
159
+ @@em.schedule {
156
160
  init_client(:host => host, :port => port,
157
161
  :rjr_node => self) { |c|
158
162
  connection = c
@@ -190,7 +194,7 @@ class TCP < RJR::Node
190
194
  message = NotificationMessage.new :method => rpc_method,
191
195
  :args => args,
192
196
  :headers => @message_headers
193
- @em.schedule {
197
+ @@em.schedule {
194
198
  init_client(:host => host, :port => port,
195
199
  :rjr_node => self) { |c|
196
200
  conn = c
data/lib/rjr/nodes/web.rb CHANGED
@@ -103,6 +103,10 @@ class Web < RJR::Node
103
103
  @port = args[:port]
104
104
  end
105
105
 
106
+ def to_s
107
+ "RJR::Nodes::Web<#{@node_id},#{@host},#{@port}>"
108
+ end
109
+
106
110
  # Send data using specified http connection
107
111
  #
108
112
  # Implementation of {RJR::Node#send_msg}
@@ -123,7 +127,7 @@ class Web < RJR::Node
123
127
  #
124
128
  # Implementation of {RJR::Node#listen}
125
129
  def listen
126
- @em.schedule do
130
+ @@em.schedule do
127
131
  EventMachine::start_server(@host, @port, WebConnection, :rjr_node => self)
128
132
  end
129
133
  self
@@ -149,7 +153,7 @@ class Web < RJR::Node
149
153
  handle_message(http.response, http)
150
154
  }
151
155
 
152
- @em.schedule do
156
+ @@em.schedule do
153
157
  http = EventMachine::HttpRequest.new(uri).post :body => message.to_s
154
158
  http.errback &cb
155
159
  http.callback &cb
@@ -182,7 +186,7 @@ class Web < RJR::Node
182
186
  :args => args,
183
187
  :headers => @message_headers
184
188
  cb = lambda { |arg| published_l.synchronize { invoked = true ; published_c.signal }}
185
- @em.schedule do
189
+ @@em.schedule do
186
190
  http = EventMachine::HttpRequest.new(uri).post :body => message.to_s
187
191
  http.errback &cb
188
192
  http.callback &cb
data/lib/rjr/nodes/ws.rb CHANGED
@@ -93,6 +93,10 @@ class WS < RJR::Node
93
93
  @connections_lock = Mutex.new
94
94
  end
95
95
 
96
+ def to_s
97
+ "RJR::Nodes::WS<#{@node_id},#{@host},#{@port}>"
98
+ end
99
+
96
100
  # Send data using specified websocket safely
97
101
  #
98
102
  # Implementation of {RJR::Node#send_msg}
@@ -104,8 +108,8 @@ class WS < RJR::Node
104
108
  #
105
109
  # Implementation of {RJR::Node#listen}
106
110
  def listen
107
- @em.schedule do
108
- EventMachine::WebSocket.start(:host => @host, :port => @port) do |ws|
111
+ @@em.schedule do
112
+ EventMachine::WebSocket.run(:host => @host, :port => @port) do |ws|
109
113
  ws.onopen { }
110
114
  ws.onclose { @connection_event_handlers[:closed].each { |h| h.call self } }
111
115
  ws.onerror { |e| @connection_event_handlers[:error].each { |h| h.call self } }
@@ -131,7 +135,7 @@ class WS < RJR::Node
131
135
  :args => args,
132
136
  :headers => @message_headers
133
137
 
134
- @em.schedule {
138
+ @@em.schedule {
135
139
  init_client(uri) do |c|
136
140
  c.stream { |msg| handle_message(msg, c) }
137
141
 
@@ -165,7 +169,7 @@ class WS < RJR::Node
165
169
  message = NotificationMessage.new :method => rpc_method,
166
170
  :args => args,
167
171
  :headers => @message_headers
168
- @em.schedule {
172
+ @@em.schedule {
169
173
  init_client(uri) do |c|
170
174
  c.stream { |msg| handle_message(msg, c) }
171
175
 
@@ -3,8 +3,6 @@
3
3
  # Copyright (C) 2010-2013 Mohammed Morsi <mo@morsi.org>
4
4
  # Licensed under the Apache License, Version 2.0
5
5
 
6
- require 'singleton'
7
-
8
6
  module RJR
9
7
 
10
8
  # Work item to be executed in a thread launched by {ThreadPool}.
@@ -81,8 +79,6 @@ end
81
79
  # Supports optional timeout which allows the user to kill and restart
82
80
  # threads if a job is taking too long to run.
83
81
  class ThreadPool
84
- include Singleton
85
-
86
82
  class << self
87
83
  # @!group Config options (must be set before first node is instantiated)
88
84
 
data/site/jrw.js CHANGED
@@ -120,7 +120,7 @@ function JRObject (type, value, ignore_properties){
120
120
  this.type = type;
121
121
  this.value = value;
122
122
  this.ignore_properties = (typeof(ignore_properties) != 'undefined') ?
123
- ignore_properties : ["toJSON"];
123
+ ignore_properties : ["toJSON", "json_class"];
124
124
  this.toJSON = function(){
125
125
  var data = {};
126
126
  for(p in this.value)
@@ -169,6 +169,7 @@ JRObject.from_json_array = function(json){
169
169
  // Main json-rpc client websocket interface
170
170
  function WSNode (host, port){
171
171
  var node = this;
172
+ this.opening = false;
172
173
  this.opened = false;
173
174
  this.node_id = null;
174
175
  this.headers = {};
@@ -176,6 +177,8 @@ function WSNode (host, port){
176
177
 
177
178
  // Open socket connection
178
179
  this.open = function(){
180
+ if(this.opening) return;
181
+ this.opening = true;
179
182
  node.socket = new WebSocket("ws://" + host + ":" + port);
180
183
 
181
184
  node.socket.onclose = function (){
@@ -197,14 +200,14 @@ function WSNode (host, port){
197
200
  if(node.onerror)
198
201
  node.onerror(msg)
199
202
 
200
- }else{
201
- // relying on clients to handle notifications via message_received
202
- // TODO add notification (and request?) handler support here
203
- //node.invoke_method(msg.rpc_method, msg.params)
204
- if(node.message_received)
205
- node.message_received(msg);
206
-
207
203
  }
204
+
205
+ // relying on clients to handle notifications via message_received
206
+ // TODO add notification (and request?) handler support here
207
+ // clients may user this to register additional handlers to be invoked
208
+ // upon request responses
209
+ if(node.message_received)
210
+ node.message_received(msg);
208
211
  };
209
212
 
210
213
  node.socket.onerror = function(e){
@@ -213,12 +216,13 @@ function WSNode (host, port){
213
216
  }
214
217
 
215
218
  node.socket.onopen = function (){
219
+ node.opened = true;
220
+ node.opening = false;
221
+
216
222
  // send queued messages
217
223
  for(var m in node.messages)
218
224
  node.socket.send(node.messages[m].to_json());
219
225
 
220
- node.opened = true;
221
-
222
226
  // invoke client callback
223
227
  if(node.onopen)
224
228
  node.onopen();
@@ -270,9 +274,10 @@ function WebNode (uri){
270
274
 
271
275
  success: function(data){
272
276
  var msg = JRMessage.from_msg(data);
273
- // js web client doesn't support notifications
274
- //if(node.message_received)
275
- //node.message_received(msg);
277
+ // clients may register additional callbacks
278
+ // to handle web node responses
279
+ if(node.message_received)
280
+ node.message_received(msg);
276
281
 
277
282
  req.handle_response(msg)
278
283
 
@@ -145,6 +145,7 @@ module RJR
145
145
  h = proc {}
146
146
  d.handle('foobar', h)
147
147
  d.handlers['foobar'].should == h
148
+ d.handles?('foobar').should be_true
148
149
  end
149
150
 
150
151
  it "should set handler from block param" do
@@ -152,6 +153,7 @@ module RJR
152
153
  h = proc {}
153
154
  d.handle('foobar', &h)
154
155
  d.handlers['foobar'].should == h
156
+ d.handles?('foobar').should be_true
155
157
  end
156
158
 
157
159
  it "should register handler for multiple methods" do
@@ -160,6 +162,8 @@ module RJR
160
162
  d.handle(['foobar', 'barfoo'], &h)
161
163
  d.handlers['foobar'].should == h
162
164
  d.handlers['barfoo'].should == h
165
+ d.handles?('foobar').should be_true
166
+ d.handles?('barfoo').should be_true
163
167
  end
164
168
  end
165
169
 
@@ -183,6 +187,10 @@ module RJR
183
187
  invoked.should be_true
184
188
  end
185
189
 
190
+ context "handler is regex" do
191
+ it "should match method"
192
+ end
193
+
186
194
  it "should pass params to handler" do
187
195
  param = nil
188
196
  d = Dispatcher.new
@@ -3,49 +3,43 @@ require 'rjr/em_adapter'
3
3
 
4
4
  module RJR
5
5
  describe EMAdapter do
6
- after(:each) do
7
- EMAdapter.instance.halt
8
- EMAdapter.instance.join
6
+ before(:each) do
7
+ @em = EMAdapter.new
9
8
  end
10
9
 
11
- it "should be a singleton" do
12
- em = EMAdapter.instance
13
- EMAdapter.instance.should == em
10
+ after(:each) do
11
+ @em.halt.join
14
12
  end
15
13
 
16
14
  it "should start the reactor" do
17
- em = EMAdapter.instance
18
- em.start
19
- em.reactor_running?.should be_true
20
- ['sleep', 'run'].should include(em.reactor_thread.status)
15
+ @em.start
16
+ @em.reactor_running?.should be_true
17
+ ['sleep', 'run'].should include(@em.reactor_thread.status)
21
18
  end
22
19
 
23
20
  it "should only start the reactor once" do
24
- em = EMAdapter.instance
25
- em.start
21
+ @em.start
26
22
 
27
- ot = em.reactor_thread
28
- em.start
29
- em.reactor_thread.should == ot
23
+ ot = @em.reactor_thread
24
+ @em.start
25
+ @em.reactor_thread.should == ot
30
26
  end
31
27
 
32
28
  it "should halt the reactor" do
33
- em = EMAdapter.instance
34
- em.start
29
+ @em.start
35
30
 
36
- em.halt
37
- em.join
38
- em.reactor_running?.should be_false
39
- em.reactor_thread.should be_nil
31
+ @em.halt
32
+ @em.join
33
+ @em.reactor_running?.should be_false
34
+ @em.reactor_thread.should be_nil
40
35
  end
41
36
 
42
37
  it "should dispatch all requests to eventmachine" do
43
- em = EMAdapter.instance
44
- em.start
38
+ @em.start
45
39
 
46
40
  invoked = false
47
41
  m,c = Mutex.new, ConditionVariable.new
48
- em.schedule {
42
+ @em.schedule {
49
43
  invoked = true
50
44
  m.synchronize { c.signal }
51
45
  }
@@ -1,5 +1,5 @@
1
1
  require 'rjr/dispatcher'
2
- require 'rjr/stats'
2
+ require 'rjr/inspect'
3
3
 
4
4
  describe "#select_stats" do
5
5
  before(:each) do
data/specs/node_spec.rb CHANGED
@@ -21,27 +21,26 @@ module RJR
21
21
  end
22
22
 
23
23
  it "should start the thread pool" do
24
- ThreadPool.instance.stop.join
25
24
  node = Node.new
26
- ThreadPool.instance.should be_running
25
+ node.class.class_variable_get(:@@tp).should be_running
27
26
  end
28
27
 
29
28
  it "should start event machine" do
30
- EMAdapter.instance.halt.join
29
+ EventMachine.stop_event_loop
31
30
  node = Node.new
32
- EMAdapter.instance.reactor_running?.should be_true
31
+ EventMachine.reactor_running?.should be_true
33
32
  end
34
33
 
35
34
  it "should halt the thread pool" do
36
35
  node = Node.new
37
36
  node.halt.join
38
- ThreadPool.instance.should_not be_running
37
+ node.class.class_variable_get(:@@tp).should_not be_running
39
38
  end
40
39
 
41
40
  it "should halt event machine" do
42
41
  node = Node.new
43
42
  node.halt.join
44
- EMAdapter.instance.reactor_running?.should be_false
43
+ EventMachine.reactor_running?.should be_false
45
44
  end
46
45
 
47
46
  it "should handle connection events" do
@@ -32,16 +32,22 @@ module RJR::Nodes
32
32
 
33
33
  describe "#notify" do
34
34
  it "should dispatch local notification" do
35
+ # notify will most likely return before
36
+ # handler is executed (in seperate thread), wait
37
+ m,c = Mutex.new, ConditionVariable.new
38
+
35
39
  invoked = nil
36
40
  node = Local.new :node_id => 'aaa'
37
41
  node.dispatcher.handle('foobar') { |param|
38
42
  invoked = true
43
+ m.synchronize { c.signal }
39
44
  'retval'
40
45
  }
41
46
 
42
47
  res = node.notify 'foobar', 'myparam'
43
- invoked.should == true
44
48
  res.should == nil
49
+ m.synchronize { c.wait m, 0.1 } unless invoked
50
+ invoked.should == true
45
51
  end
46
52
  end
47
53
 
@@ -5,40 +5,35 @@ require 'rjr/thread_pool'
5
5
 
6
6
  module RJR
7
7
  describe ThreadPool do
8
- after(:each) do
9
- ThreadPool.instance.stop
10
- ThreadPool.instance.join
8
+ before(:each) do
9
+ @tp = ThreadPool.new
11
10
  end
12
11
 
13
- it "should be a singleton" do
14
- tp = ThreadPool.instance
15
- ThreadPool.instance.should == tp
12
+ after(:each) do
13
+ @tp.stop.join
16
14
  end
17
15
 
18
16
  it "should start the thread pool" do
19
- tp = ThreadPool.instance
20
- tp.start
21
- tp.instance_variable_get(:@worker_threads).size.should == ThreadPool.num_threads
22
- tp.should be_running
17
+ @tp.start
18
+ @tp.instance_variable_get(:@worker_threads).size.should == ThreadPool.num_threads
19
+ @tp.should be_running
23
20
  end
24
21
 
25
22
  it "should stop the thread pool" do
26
- tp = ThreadPool.instance
27
- tp.start
28
- tp.stop
29
- tp.join
30
- tp.instance_variable_get(:@worker_threads).size.should == 0
31
- tp.should_not be_running
23
+ @tp.start
24
+ @tp.stop
25
+ @tp.join
26
+ @tp.instance_variable_get(:@worker_threads).size.should == 0
27
+ @tp.should_not be_running
32
28
  end
33
29
 
34
30
  it "should run work" do
35
- tp = ThreadPool.instance
36
- tp.start
31
+ @tp.start
37
32
 
38
33
  jobs_executed = []
39
34
  m,c = Mutex.new, ConditionVariable.new
40
- tp << ThreadPoolJob.new { jobs_executed << 1 ; m.synchronize { c.signal } }
41
- tp << ThreadPoolJob.new { jobs_executed << 2 ; m.synchronize { c.signal } }
35
+ @tp << ThreadPoolJob.new { jobs_executed << 1 ; m.synchronize { c.signal } }
36
+ @tp << ThreadPoolJob.new { jobs_executed << 2 ; m.synchronize { c.signal } }
42
37
 
43
38
  m.synchronize { c.wait m, 0.1 } unless jobs_executed.include?(1)
44
39
  m.synchronize { c.wait m, 0.1 } unless jobs_executed.include?(2)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rjr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.1
4
+ version: 0.16.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-19 00:00:00.000000000 Z
12
+ date: 2013-09-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -79,14 +79,15 @@ files:
79
79
  - lib/rjr/semaphore.rb
80
80
  - lib/rjr/em_adapter.rb
81
81
  - lib/rjr/errors.rb
82
+ - lib/rjr/inspect.rb
82
83
  - lib/rjr/node.rb
83
84
  - lib/rjr/common.rb
84
85
  - lib/rjr/thread_pool.rb
85
86
  - lib/rjr/message.rb
86
- - lib/rjr/stats.rb
87
87
  - lib/rjr/nodes/web.rb
88
88
  - lib/rjr/nodes/udp.rb
89
89
  - lib/rjr/nodes/tcp.rb
90
+ - lib/rjr/nodes/ssh.rb
90
91
  - lib/rjr/nodes/ws.rb
91
92
  - lib/rjr/nodes/tcp2.rb
92
93
  - lib/rjr/nodes/local.rb
@@ -97,7 +98,6 @@ files:
97
98
  - lib/rjr/dispatcher.rb
98
99
  - specs/dispatcher_spec.rb
99
100
  - specs/node_spec.rb
100
- - specs/stats_spec.rb
101
101
  - specs/nodes/multi_spec.rb
102
102
  - specs/nodes/tcp_spec.rb
103
103
  - specs/nodes/web_spec.rb
@@ -105,6 +105,7 @@ files:
105
105
  - specs/nodes/amqp_spec.rb
106
106
  - specs/nodes/ws_spec.rb
107
107
  - specs/nodes/easy_spec.rb
108
+ - specs/inspect_spec.rb
108
109
  - specs/thread_pool_spec.rb
109
110
  - specs/message_spec.rb
110
111
  - specs/em_adapter_spec.rb