rjr 0.15.1 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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