rjr 0.9.0 → 0.11.7
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 +150 -0
- data/bin/rjr-client-launcher +38 -0
- data/bin/rjr-server +54 -32
- data/lib/rjr/amqp_node.rb +95 -37
- data/lib/rjr/common.rb +60 -10
- data/lib/rjr/dispatcher.rb +98 -16
- data/lib/rjr/em_adapter.rb +110 -0
- data/lib/rjr/inspect.rb +66 -0
- data/lib/rjr/local_node.rb +1 -0
- data/lib/rjr/message.rb +123 -3
- data/lib/rjr/missing_node.rb +17 -0
- data/lib/rjr/multi_node.rb +3 -0
- data/lib/rjr/node.rb +79 -67
- data/lib/rjr/tcp_node.rb +146 -53
- data/lib/rjr/tcp_node2.rb +4 -0
- data/lib/rjr/thread_pool2.rb +271 -0
- data/lib/rjr/util.rb +104 -0
- data/lib/rjr/web_node.rb +115 -23
- data/lib/rjr/ws_node.rb +162 -34
- data/lib/rjr.rb +5 -10
- data/specs/dispatcher_spec.rb +81 -0
- data/specs/em_adapter_spec.rb +85 -0
- data/specs/inspect_spec.rb +60 -0
- data/specs/message_spec.rb +58 -0
- data/specs/multi_node_spec.rb +5 -4
- data/specs/node_spec.rb +140 -4
- data/specs/tcp_node_spec.rb +1 -0
- data/specs/thread_pool_spec.rb +41 -0
- data/specs/util_spec.rb +46 -0
- data/specs/web_node_spec.rb +1 -0
- data/specs/ws_node_spec.rb +1 -1
- metadata +24 -8
- data/lib/rjr/web_socket.rb +0 -589
data/bin/rjr-client
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# RJR client
|
3
|
+
#
|
4
|
+
# Copyright (C) 2013 Mohammed Morsi <mo@morsi.org>
|
5
|
+
# Licensed under the Apache License, Version 2.0
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'optparse'
|
9
|
+
require 'rjr'
|
10
|
+
|
11
|
+
RJR::Logger.log_level = ::Logger::DEBUG
|
12
|
+
|
13
|
+
##########################################################
|
14
|
+
|
15
|
+
|
16
|
+
MAX_MESSAGES = 5 # only used when generating random number of messages
|
17
|
+
MAX_INTERVAL = 20 # only used when generating random interval
|
18
|
+
MAX_DISCONNECT = 30 # only used when generating random disconnect
|
19
|
+
|
20
|
+
config = { :mode => :msg,
|
21
|
+
:node_id => 'rjr_test_client',
|
22
|
+
:transport => :amqp,
|
23
|
+
:broker => 'localhost',
|
24
|
+
:dst => 'rjr_test_server-queue',
|
25
|
+
:num_msg => 1,
|
26
|
+
:msg_id => :rand,
|
27
|
+
:block => false,
|
28
|
+
:interval => 0,
|
29
|
+
:disconnect => false}
|
30
|
+
|
31
|
+
optparse = OptionParser.new do |opts|
|
32
|
+
opts.on('-h', '--help', 'Display this help screen') do
|
33
|
+
puts opts
|
34
|
+
exit
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on('-m', '--mode mode', 'Mode of operation, default "msg" to send message, may be "rand" to send sequences of random bytes') do |mode|
|
38
|
+
config[:mode] = mode.intern
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('-i', '--id ID', 'Node ID to assign to client') do |id|
|
42
|
+
config[:node_id] = id
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on('-t', '--transport type', 'Type of transport to connect to server') do |t|
|
46
|
+
config[:transport] = t.intern
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on('-b', '--broker broker', 'AMQP Broker (only needed if transport is amqp)') do |b|
|
50
|
+
config[:broker] = b
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on('--dst dst', 'Destination which to connect to/send messages to') do |dst|
|
54
|
+
config[:dst] = dst
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on('-r', '--[no-]return', 'Boolean indicating if client should block/wait for server messages (default false)') do |ret|
|
58
|
+
config[:block] = !ret
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.on('-n', '--num number_of_messages', 'Number of messages to send to server (may be :rand or indefinite)') do |n|
|
62
|
+
config[:num_msg] = case n.to_s.intern
|
63
|
+
when :rand then rand(MAX_MESSAGES)
|
64
|
+
when :indefinite then 1000000000 # XXX 'indefinite ;-)'
|
65
|
+
else n.to_i
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
opts.on('--message ID', 'Message to send to server (rand to select random)') do |mid|
|
70
|
+
config[:msg_id] = mid
|
71
|
+
end
|
72
|
+
|
73
|
+
opts.on('--interval seconds', 'Number of seconds after which to wait between requests (or rand)') do |s|
|
74
|
+
config[:interval] = s.to_s.intern
|
75
|
+
end
|
76
|
+
|
77
|
+
opts.on('-d', '--disconnect seconds', OptionParser::DecimalNumeric, 'Number of seconds after which to disconnect client (or rand)') do |d|
|
78
|
+
config[:disconnect] = d.to_s.intern
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
optparse.parse!
|
83
|
+
|
84
|
+
if config[:num_msg] < 1
|
85
|
+
puts "At least one message must be specified"
|
86
|
+
exit 1
|
87
|
+
end
|
88
|
+
|
89
|
+
##########################################################
|
90
|
+
|
91
|
+
NODES = {config[:transport] => {:node_id => config[:node_id],
|
92
|
+
:broker => config[:broker], # XXX only needed for amqp
|
93
|
+
:keep_alive => true} } # conditionally set keep alive?
|
94
|
+
|
95
|
+
cdir = File.dirname(__FILE__)
|
96
|
+
client_path = File.join(ENV['RJR_LOAD_PATH'] || File.join(cdir, '..', 'tests', 'harness'), "client")
|
97
|
+
|
98
|
+
require_path client_path
|
99
|
+
|
100
|
+
##########################################################
|
101
|
+
|
102
|
+
node = RJR::EasyNode.new(NODES)
|
103
|
+
|
104
|
+
if config[:disconnect]
|
105
|
+
disconnect_thread = Thread.new {
|
106
|
+
seconds = config[:disconnect] == :rand ? rand(MAX_DISCONNECT) : config[:disconnect].to_s.to_i
|
107
|
+
sleep seconds
|
108
|
+
# node.halt ?
|
109
|
+
exit 1
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
# invoke request(s)
|
114
|
+
0.upto(config[:num_msg]-1) { |i|
|
115
|
+
# TODO implement mode == :rand
|
116
|
+
|
117
|
+
# grab message (or rand message)
|
118
|
+
msg = (config[:msg_id] == :rand ? RJR::Definitions.rand_msg(config[:transport]) :
|
119
|
+
RJR::Definitions.rjr_message(config[:msg_id]))
|
120
|
+
|
121
|
+
if msg.nil?
|
122
|
+
puts "Invalid message id"
|
123
|
+
exit 1
|
124
|
+
end
|
125
|
+
|
126
|
+
# manipulate msg params
|
127
|
+
parmas = []
|
128
|
+
params = msg[:params].collect { |p|
|
129
|
+
if p.is_a?(String)
|
130
|
+
p.gsub('<CLIENT_ID>', config[:node_id]) # TODO other substitutions
|
131
|
+
elsif p.is_a?(Proc)
|
132
|
+
p.call
|
133
|
+
else
|
134
|
+
p
|
135
|
+
end
|
136
|
+
} if msg[:params]
|
137
|
+
|
138
|
+
# invoke request
|
139
|
+
res = node.invoke_request(config[:dst], msg[:method], *params)
|
140
|
+
|
141
|
+
# verify and output result
|
142
|
+
ress = (msg[:result].nil? ? "" : (msg[:result].call(res) ? "passed" : "failed"))
|
143
|
+
RJR::Logger.info "#{msg[:method]} result #{res} #{ress}"
|
144
|
+
|
145
|
+
if msg[:interval] && i < (config[:num_msg] - 1)
|
146
|
+
sleep(msg[:interval] == :rand ? rand(MAX_INTERVAL) : msg[:interval].to_s.to_i)
|
147
|
+
end
|
148
|
+
}
|
149
|
+
|
150
|
+
node.join if config[:block]
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# Launches a series of clients
|
3
|
+
#
|
4
|
+
# Copyright (C) 2013 Mohammed Morsi <mo@morsi.org>
|
5
|
+
# Licensed under the Apache License, Version 2.0
|
6
|
+
|
7
|
+
ID_OFFSET = ARGV.shift.to_i || 100
|
8
|
+
|
9
|
+
NUM_CLIENTS = 5
|
10
|
+
NUM_MSGS = 20 # per client
|
11
|
+
NODE_ID = 'rjr_test_launcher-'
|
12
|
+
MSG_IDS = ['stress', 'stress_callback']
|
13
|
+
TRANSPORTS = {#:amqp => 'rjr_test_server-queue',
|
14
|
+
#:tcp => 'jsonrpc://localhost:8181',
|
15
|
+
:ws => 'jsonrpc://localhost:8080',
|
16
|
+
#:www => 'http://localhost:8888'
|
17
|
+
}
|
18
|
+
BROKER = 'localhost' # only used for amqp
|
19
|
+
MSG_INTERVAL= 3
|
20
|
+
|
21
|
+
CLIENT = File.join(File.dirname(__FILE__), 'rjr-client')
|
22
|
+
|
23
|
+
threads = []
|
24
|
+
|
25
|
+
0.upto(NUM_CLIENTS) { |i|
|
26
|
+
transport = TRANSPORTS.keys[rand(TRANSPORTS.keys.size)]
|
27
|
+
dst = TRANSPORTS[transport]
|
28
|
+
mode = rand(2) == 0 ? :msg : :rand
|
29
|
+
node_id = NODE_ID + (i + ID_OFFSET).to_s
|
30
|
+
msg_id = MSG_IDS[rand(MSG_IDS.size)]
|
31
|
+
|
32
|
+
threads <<
|
33
|
+
Thread.new{
|
34
|
+
system("#{CLIENT} -m #{mode} -t #{transport} -i #{node_id} -b #{BROKER} --dst #{dst} -n #{NUM_MSGS} --message #{msg_id} --interval #{MSG_INTERVAL}")
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
threads.each { |t| t.join }
|
data/bin/rjr-server
CHANGED
@@ -1,52 +1,74 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
|
-
#
|
3
|
-
# Executable to launch registered rjr methods
|
2
|
+
# RJR server
|
4
3
|
#
|
5
|
-
#
|
6
|
-
# -h --help
|
7
|
-
#
|
8
|
-
# Copyright (C) 2011-2012 Mohammed Morsi <mo@morsi.org>
|
4
|
+
# Copyright (C) 2013 Mohammed Morsi <mo@morsi.org>
|
9
5
|
# Licensed under the Apache License, Version 2.0
|
10
6
|
|
11
7
|
require 'rubygems'
|
12
8
|
require 'optparse'
|
13
9
|
require 'rjr'
|
10
|
+
require 'stringio'
|
14
11
|
|
15
|
-
|
12
|
+
require 'rjr/inspect'
|
16
13
|
|
14
|
+
##########################################################
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
end
|
16
|
+
config = { :node_id => 'rjr_test_server',
|
17
|
+
:broker => 'localhost',
|
18
|
+
:host => 'localhost',
|
19
|
+
:tcp_port => 8181,
|
20
|
+
:www_port => 8888,
|
21
|
+
:ws_port => 8080 }
|
26
22
|
|
27
|
-
|
28
|
-
|
29
|
-
opts.parse!(ARGV)
|
30
|
-
rescue OptionParser::InvalidOption
|
23
|
+
optparse = OptionParser.new do |opts|
|
24
|
+
opts.on('-h', '--help', 'Display this help screen') do
|
31
25
|
puts opts
|
32
26
|
exit
|
33
27
|
end
|
34
28
|
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
opts.on('-i', '--id ID', 'Node ID to assign to server') do |id|
|
30
|
+
config[:node_id] = id
|
31
|
+
end
|
38
32
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
"world"
|
43
|
-
}
|
33
|
+
opts.on('-b', '--broker broker', 'AMQP Broker') do |b|
|
34
|
+
config[:broker] = b
|
35
|
+
end
|
44
36
|
|
45
|
-
|
37
|
+
opts.on('-h', '--host host', 'Host (or ip) which to listen on') do |h|
|
38
|
+
config[:host] = host
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('--tcp port', 'TCP Port to listen on') do |p|
|
42
|
+
config[:tcp_port] = p
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on('--www port', 'Port to listen on for www requests') do |p|
|
46
|
+
config[:www_port] = p
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on('--ws port', 'Websocket Port to listen on') do |p|
|
50
|
+
config[:ws_port] = p
|
51
|
+
end
|
46
52
|
|
47
|
-
rjr_node.listen
|
48
|
-
rjr_node.join
|
49
|
-
rjr_node.stop # TODO run in signal handler
|
50
53
|
end
|
51
54
|
|
52
|
-
|
55
|
+
optparse.parse!
|
56
|
+
|
57
|
+
##########################################################
|
58
|
+
|
59
|
+
NODES = {:amqp => {:node_id => config[:node_id], :broker => config[:broker]},
|
60
|
+
:ws => {:node_id => config[:node_id], :host => config[:host], :port => config[:ws_port]},
|
61
|
+
:www => {:node_id => config[:node_id], :host => config[:host], :port => config[:www_port]},
|
62
|
+
:tcp => {:node_id => config[:node_id], :host => config[:host], :port => config[:tcp_port]}}
|
63
|
+
|
64
|
+
cdir = File.dirname(__FILE__)
|
65
|
+
server_path = File.join(ENV['RJR_LOAD_PATH'] || File.join(cdir, '..', 'tests', 'harness'), 'server')
|
66
|
+
require_path server_path
|
67
|
+
|
68
|
+
##########################################################
|
69
|
+
|
70
|
+
$messages = StringIO.new
|
71
|
+
|
72
|
+
RJR::Logger.log_level = ::Logger::DEBUG
|
73
|
+
RJR::Logger.log_to $messages
|
74
|
+
RJR::EasyNode.new(NODES).stop_on("INT").listen.join
|
data/lib/rjr/amqp_node.rb
CHANGED
@@ -5,10 +5,23 @@
|
|
5
5
|
# Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
|
6
6
|
# Licensed under the Apache License, Version 2.0
|
7
7
|
|
8
|
+
skip_module = false
|
9
|
+
begin
|
8
10
|
require 'amqp'
|
9
|
-
|
11
|
+
rescue LoadError
|
12
|
+
skip_module = true
|
13
|
+
end
|
14
|
+
|
15
|
+
if skip_module
|
16
|
+
# TODO output: "amqp gem could not be loaded, skipping amqp node definition"
|
17
|
+
require 'rjr/missing_node'
|
18
|
+
RJR::AMQPNode = RJR::MissingNode
|
19
|
+
|
20
|
+
else
|
10
21
|
require 'rjr/node'
|
11
22
|
require 'rjr/message'
|
23
|
+
require 'rjr/dispatcher'
|
24
|
+
require 'rjr/thread_pool2'
|
12
25
|
|
13
26
|
module RJR
|
14
27
|
|
@@ -31,8 +44,8 @@ class AMQPNodeCallback
|
|
31
44
|
|
32
45
|
# Implementation of {RJR::NodeCallback#invoke}
|
33
46
|
def invoke(callback_method, *data)
|
34
|
-
msg =
|
35
|
-
@node.publish
|
47
|
+
msg = NotificationMessage.new :method => callback_method, :args => data, :headers => @message_headers
|
48
|
+
@node.publish(msg.to_s, :routing_key => @destination, :mandatory => true) { }
|
36
49
|
end
|
37
50
|
end
|
38
51
|
|
@@ -66,7 +79,11 @@ class AMQPNode < RJR::Node
|
|
66
79
|
def handle_message(metadata, msg)
|
67
80
|
if RequestMessage.is_request_message?(msg)
|
68
81
|
reply_to = metadata.reply_to
|
69
|
-
|
82
|
+
ThreadPool2Manager << ThreadPool2Job.new { handle_request(reply_to, msg, false) }
|
83
|
+
|
84
|
+
elsif NotificationMessage.is_notification_message?(msg)
|
85
|
+
reply_to = metadata.reply_to
|
86
|
+
ThreadPool2Manager << ThreadPool2Job.new { handle_request(reply_to, msg, true) }
|
70
87
|
|
71
88
|
elsif ResponseMessage.is_response_message?(msg)
|
72
89
|
handle_response(msg)
|
@@ -75,8 +92,9 @@ class AMQPNode < RJR::Node
|
|
75
92
|
end
|
76
93
|
|
77
94
|
# Internal helper, handle request message pulled off queue
|
78
|
-
def handle_request(reply_to, message)
|
79
|
-
msg =
|
95
|
+
def handle_request(reply_to, message, notification=false)
|
96
|
+
msg = notification ? NotificationMessage.new(:message => message, :headers => @message_headers) :
|
97
|
+
RequestMessage.new(:message => message, :headers => @message_headers)
|
80
98
|
headers = @message_headers.merge(msg.headers) # append request message headers
|
81
99
|
result = Dispatcher.dispatch_request(msg.jr_method,
|
82
100
|
:method_args => msg.jr_args,
|
@@ -91,8 +109,10 @@ class AMQPNode < RJR::Node
|
|
91
109
|
:exchange => @exchange,
|
92
110
|
:destination => reply_to,
|
93
111
|
:headers => headers))
|
94
|
-
|
95
|
-
|
112
|
+
unless notification
|
113
|
+
response = ResponseMessage.new(:id => msg.msg_id, :result => result, :headers => headers)
|
114
|
+
publish(response.to_s, :routing_key => reply_to) { }
|
115
|
+
end
|
96
116
|
end
|
97
117
|
|
98
118
|
# Internal helper, handle response message pulled off queue
|
@@ -102,7 +122,7 @@ class AMQPNode < RJR::Node
|
|
102
122
|
begin
|
103
123
|
res = Dispatcher.handle_response(msg.result)
|
104
124
|
rescue Exception => e
|
105
|
-
err = e
|
125
|
+
err = e.to_s
|
106
126
|
end
|
107
127
|
|
108
128
|
@response_lock.synchronize{
|
@@ -114,11 +134,20 @@ class AMQPNode < RJR::Node
|
|
114
134
|
end
|
115
135
|
|
116
136
|
# Initialize the amqp subsystem
|
117
|
-
def init_node
|
118
|
-
|
119
|
-
|
137
|
+
def init_node(&on_init)
|
138
|
+
if !@conn.nil? && @conn.connected?
|
139
|
+
on_init.call
|
140
|
+
return
|
141
|
+
end
|
142
|
+
|
143
|
+
super
|
144
|
+
@conn = AMQP.connect(:host => @broker) do |*args|
|
145
|
+
on_init.call
|
146
|
+
end
|
120
147
|
@conn.on_tcp_connection_failure { puts "OTCF #{@node_id}" }
|
121
148
|
|
149
|
+
# TODO move the rest into connect callback ?
|
150
|
+
|
122
151
|
### connect to qpid broker
|
123
152
|
@channel = AMQP::Channel.new(@conn)
|
124
153
|
|
@@ -143,18 +172,15 @@ class AMQPNode < RJR::Node
|
|
143
172
|
res = nil
|
144
173
|
while res.nil?
|
145
174
|
@response_lock.synchronize{
|
146
|
-
@response_cv.wait @response_lock
|
147
175
|
# FIXME throw err if more than 1 match found
|
148
176
|
res = @responses.select { |response| message.msg_id == response.first }.first
|
149
|
-
|
177
|
+
if !res.nil?
|
150
178
|
@responses.delete(res)
|
179
|
+
|
151
180
|
else
|
152
|
-
# we can't just go back to waiting for message here, need to give
|
153
|
-
# other nodes a chance to check it first
|
154
181
|
@response_cv.signal
|
155
|
-
@
|
182
|
+
@response_cv.wait @response_lock
|
156
183
|
end
|
157
|
-
@response_check_cv.signal
|
158
184
|
}
|
159
185
|
end
|
160
186
|
return res
|
@@ -194,7 +220,6 @@ class AMQPNode < RJR::Node
|
|
194
220
|
@connection_event_handlers = {:closed => [], :error => []}
|
195
221
|
@response_lock = Mutex.new
|
196
222
|
@response_cv = ConditionVariable.new
|
197
|
-
@response_check_cv = ConditionVariable.new
|
198
223
|
@responses = []
|
199
224
|
@amqp_lock = Mutex.new
|
200
225
|
end
|
@@ -202,10 +227,12 @@ class AMQPNode < RJR::Node
|
|
202
227
|
# Publish a message using the amqp exchange (*do* *not* *use*).
|
203
228
|
#
|
204
229
|
# XXX hack should be private, declared publically so as to be able to be used by {RJR::AMQPNodeCallback}
|
205
|
-
def publish(*args)
|
230
|
+
def publish(*args, &on_publish)
|
206
231
|
@amqp_lock.synchronize {
|
207
232
|
#raise RJR::Errors::ConnectionError.new("client unreachable") if @disconnected
|
208
|
-
@exchange.publish *args
|
233
|
+
@exchange.publish *args do |*cargs|
|
234
|
+
on_publish.call
|
235
|
+
end
|
209
236
|
}
|
210
237
|
nil
|
211
238
|
end
|
@@ -225,16 +252,20 @@ class AMQPNode < RJR::Node
|
|
225
252
|
# Implementation of {RJR::Node#listen}
|
226
253
|
def listen
|
227
254
|
em_run do
|
228
|
-
init_node
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
255
|
+
init_node {
|
256
|
+
# start receiving messages
|
257
|
+
subscribe { |metadata, msg|
|
258
|
+
handle_message(metadata, msg)
|
259
|
+
}
|
233
260
|
}
|
234
261
|
end
|
235
262
|
end
|
236
263
|
|
237
|
-
# Instructs node to send rpc request, and wait for and return response
|
264
|
+
# Instructs node to send rpc request, and wait for and return response.
|
265
|
+
#
|
266
|
+
# Do not invoke directly from em event loop or callback as will block the message
|
267
|
+
# subscription used to receive responses
|
268
|
+
#
|
238
269
|
# @param [String] routing_key destination queue to send request to
|
239
270
|
# @param [String] rpc_method json-rpc method to invoke on destination
|
240
271
|
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
@@ -245,26 +276,53 @@ class AMQPNode < RJR::Node
|
|
245
276
|
:args => args,
|
246
277
|
:headers => @message_headers
|
247
278
|
em_run do
|
248
|
-
init_node
|
279
|
+
init_node {
|
280
|
+
# begin listening for result
|
281
|
+
subscribe { |metadata, msg|
|
282
|
+
handle_message(metadata, msg)
|
283
|
+
}
|
249
284
|
|
250
|
-
|
251
|
-
subscribe { |metadata, msg|
|
252
|
-
handle_message(metadata, msg)
|
285
|
+
publish(message.to_s, :routing_key => routing_key, :reply_to => @queue_name) { }
|
253
286
|
}
|
254
|
-
|
255
|
-
publish message.to_s, :routing_key => routing_key, :reply_to => @queue_name
|
256
287
|
end
|
257
288
|
|
258
|
-
# TODO optional timeout for response
|
289
|
+
# TODO optional timeout for response
|
259
290
|
result = wait_for_result(message)
|
260
|
-
#self.stop
|
261
|
-
#self.join unless self.em_running?
|
262
291
|
|
263
292
|
if result.size > 2
|
264
|
-
raise result[2]
|
293
|
+
raise Exception, result[2]
|
265
294
|
end
|
266
295
|
return result[1]
|
267
296
|
end
|
268
297
|
|
298
|
+
# FIXME add method to instruct node to send rpc request, and immediately
|
299
|
+
# return / ignoring response & also add method to collect response
|
300
|
+
# at a later time
|
301
|
+
|
302
|
+
# Instructs node to send rpc notification (immadiately returns / no response is generated)
|
303
|
+
#
|
304
|
+
# @param [String] routing_key destination queue to send request to
|
305
|
+
# @param [String] rpc_method json-rpc method to invoke on destination
|
306
|
+
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
307
|
+
def send_notification(routing_key, rpc_method, *args)
|
308
|
+
# will block until message is published
|
309
|
+
published_l = Mutex.new
|
310
|
+
published_c = ConditionVariable.new
|
311
|
+
|
312
|
+
message = NotificationMessage.new :method => rpc_method,
|
313
|
+
:args => args,
|
314
|
+
:headers => @message_headers
|
315
|
+
em_run do
|
316
|
+
init_node {
|
317
|
+
publish(message.to_s, :routing_key => routing_key, :reply_to => @queue_name){
|
318
|
+
published_l.synchronize { published_c.signal }
|
319
|
+
}
|
320
|
+
}
|
321
|
+
end
|
322
|
+
published_l.synchronize { published_c.wait published_l }
|
323
|
+
nil
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
269
327
|
end
|
270
328
|
end
|
data/lib/rjr/common.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# RJR Utility Methods
|
1
|
+
# Low level RJR Utility Methods
|
2
2
|
#
|
3
3
|
# Assortment of helper methods and methods that don't fit elsewhere
|
4
4
|
#
|
@@ -14,17 +14,20 @@ module RJR
|
|
14
14
|
# Encapsulates the standard ruby logger in a thread safe manner. Dispatches
|
15
15
|
# class methods to an internally tracked logger to provide global access.
|
16
16
|
#
|
17
|
+
# TODO handle logging errors (log size too big, logrotate, etc)
|
18
|
+
#
|
17
19
|
# @example
|
18
20
|
# RJR::Logger.info 'my message'
|
19
21
|
# RJR::Logger.warn 'my warning'
|
20
22
|
class Logger
|
21
23
|
private
|
22
24
|
def self._instantiate_logger
|
23
|
-
|
25
|
+
if @logger.nil?
|
24
26
|
#STDOUT.sync = true
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
output = @log_to || ENV['RJR_LOG'] || STDOUT
|
28
|
+
@logger = ::Logger.new(output)
|
29
|
+
@logger.level = @log_level || ::Logger::FATAL
|
30
|
+
@logger_mutex = Mutex.new
|
28
31
|
end
|
29
32
|
end
|
30
33
|
|
@@ -32,32 +35,79 @@ class Logger
|
|
32
35
|
|
33
36
|
def self.method_missing(method_id, *args)
|
34
37
|
_instantiate_logger
|
35
|
-
|
38
|
+
@logger_mutex.synchronize {
|
36
39
|
if args.first.is_a?(Array)
|
37
40
|
args.first.each{ |a|
|
38
|
-
|
41
|
+
@logger.send(method_id, a)
|
39
42
|
}
|
40
43
|
else
|
41
|
-
|
44
|
+
@logger.send(method_id, args)
|
42
45
|
end
|
43
46
|
}
|
44
47
|
end
|
45
48
|
|
46
49
|
def self.logger
|
47
50
|
_instantiate_logger
|
48
|
-
|
51
|
+
@logger
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set log destination
|
55
|
+
# @param dst destination which to log to (file name, STDOUT, etc)
|
56
|
+
def self.log_to(dst)
|
57
|
+
@log_to = dst
|
58
|
+
@logger = nil
|
59
|
+
_instantiate_logger
|
49
60
|
end
|
50
61
|
|
51
62
|
# Set log level.
|
52
63
|
# @param level one of the standard rails log levels (default fatal)
|
53
64
|
def self.log_level=(level)
|
54
65
|
_instantiate_logger
|
55
|
-
|
66
|
+
if level.is_a?(String)
|
67
|
+
level = case level
|
68
|
+
when 'debug' then
|
69
|
+
::Logger::DEBUG
|
70
|
+
when 'info' then
|
71
|
+
::Logger::INFO
|
72
|
+
when 'warn' then
|
73
|
+
::Logger::WARN
|
74
|
+
when 'error' then
|
75
|
+
::Logger::ERROR
|
76
|
+
when 'fatal' then
|
77
|
+
::Logger::FATAL
|
78
|
+
end
|
79
|
+
end
|
80
|
+
@log_level = level
|
81
|
+
@logger.level = level
|
82
|
+
end
|
83
|
+
|
84
|
+
# Return true if log level is set to debug, else false
|
85
|
+
def self.debug?
|
86
|
+
@log_level == ::Logger::DEBUG
|
56
87
|
end
|
57
88
|
end
|
58
89
|
|
59
90
|
end # module RJR
|
60
91
|
|
92
|
+
class Object
|
93
|
+
def eigenclass
|
94
|
+
class << self
|
95
|
+
self
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module Kernel
|
101
|
+
def require_path(path)
|
102
|
+
path.split(':').all? { |dir|
|
103
|
+
# TODO also all .so files? allow user to specify suffix or omit?
|
104
|
+
Dir.glob(File.join(dir, '*.rb')).all? { |rb|
|
105
|
+
require rb
|
106
|
+
}
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
61
111
|
if RUBY_VERSION < "1.9"
|
62
112
|
# We extend object in ruby 1.9 to define 'instance_exec'
|
63
113
|
#
|