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