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
@@ -0,0 +1,271 @@
|
|
1
|
+
# Thread Pool (second implementation)
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010-2012 Mohammed Morsi <mo@morsi.org>
|
4
|
+
# Licensed under the Apache License, Version 2.0
|
5
|
+
|
6
|
+
require 'singleton'
|
7
|
+
|
8
|
+
# Work item to be executed in a thread launched by {ThreadPool2}.
|
9
|
+
#
|
10
|
+
# The end user just need to initialize this class with the handle
|
11
|
+
# to the job to be executed and the params to pass to it, before
|
12
|
+
# handing it off to the thread pool that will take care of the rest.
|
13
|
+
class ThreadPool2Job
|
14
|
+
attr_accessor :handler
|
15
|
+
attr_accessor :params
|
16
|
+
|
17
|
+
# used internally by the thread pool system, these shouldn't
|
18
|
+
# be set or used by the end user
|
19
|
+
attr_accessor :timestamp
|
20
|
+
attr_accessor :thread
|
21
|
+
attr_accessor :pool_lock
|
22
|
+
attr_reader :being_executed
|
23
|
+
|
24
|
+
# ThreadPoolJob initializer
|
25
|
+
# @param [Array] params arguments to pass to the job when it is invoked
|
26
|
+
# @param [Callable] block handle to callable object corresponding to job to invoke
|
27
|
+
def initialize(*params, &block)
|
28
|
+
@params = params
|
29
|
+
@handler = block
|
30
|
+
@being_executed = false
|
31
|
+
@timestamp = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
# Return string representation of thread pool job
|
35
|
+
def to_s
|
36
|
+
"thread_pool2_job-#{@handler.source_location}-#{@params}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def being_executed?
|
40
|
+
@being_executed
|
41
|
+
end
|
42
|
+
|
43
|
+
def completed?
|
44
|
+
!@timestamp.nil? && !@being_executed
|
45
|
+
end
|
46
|
+
|
47
|
+
# Set job metadata and execute job with specified params
|
48
|
+
def exec
|
49
|
+
# synchronized so that both timestamp is set and being_executed
|
50
|
+
# set to true before the possiblity of a timeout management
|
51
|
+
# check (see handle_timeout! below)
|
52
|
+
@pool_lock.synchronize{
|
53
|
+
@thread = Thread.current
|
54
|
+
@being_executed = true
|
55
|
+
@timestamp = Time.now
|
56
|
+
}
|
57
|
+
|
58
|
+
@handler.call *@params
|
59
|
+
|
60
|
+
# synchronized so as to ensure that a timeout check does not
|
61
|
+
# occur until before (in which case thread is killed during
|
62
|
+
# the check as one atomic operation) or after (in which case
|
63
|
+
# job is marked as completed, and thread is not killed / goes
|
64
|
+
# onto pull anther job)
|
65
|
+
@pool_lock.synchronize{
|
66
|
+
@being_executed = false
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
# Check timeout and kill thread if it exceeded.
|
71
|
+
def handle_timeout!(timeout)
|
72
|
+
# Synchronized so that check and kill operation occur as an
|
73
|
+
# atomic operation, see exec above
|
74
|
+
@pool_lock.synchronize {
|
75
|
+
if @being_executed && (Time.now - @timestamp) > timeout
|
76
|
+
RJR::Logger.debug "timeout detected on thread #{@thread} started at #{@timestamp}"
|
77
|
+
@thread.kill
|
78
|
+
return true
|
79
|
+
end
|
80
|
+
return false
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Utility to launches a specified number of threads on instantiation,
|
86
|
+
# assigning work to them in order as it arrives.
|
87
|
+
#
|
88
|
+
# Supports optional timeout which allows the developer to kill and restart
|
89
|
+
# threads if a job is taking too long to run.
|
90
|
+
#
|
91
|
+
# Second (and hopefully better) thread pool implementation.
|
92
|
+
#
|
93
|
+
# TODO move to the RJR namespace
|
94
|
+
class ThreadPool2
|
95
|
+
private
|
96
|
+
|
97
|
+
# Internal helper, launch worker thread
|
98
|
+
#
|
99
|
+
# Should only be launched from within the pool_lock
|
100
|
+
def launch_worker
|
101
|
+
@worker_threads << Thread.new {
|
102
|
+
while work = @work_queue.pop
|
103
|
+
begin
|
104
|
+
#RJR::Logger.debug "launch thread pool job #{work}"
|
105
|
+
work.pool_lock = @pool_lock
|
106
|
+
@running_queue << work
|
107
|
+
work.exec
|
108
|
+
# TODO cleaner / more immediate way to pop item off running_queue
|
109
|
+
#RJR::Logger.debug "finished thread pool job #{work}"
|
110
|
+
rescue Exception => e
|
111
|
+
# FIXME also send to rjr logger at a critical level
|
112
|
+
puts "Thread raised Fatal Exception #{e}"
|
113
|
+
puts "\n#{e.backtrace.join("\n")}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
} unless @worker_threads.size == @num_threads
|
117
|
+
end
|
118
|
+
|
119
|
+
# Internal helper, performs checks on workers
|
120
|
+
def check_workers
|
121
|
+
if @terminate
|
122
|
+
@pool_lock.synchronize {
|
123
|
+
@worker_threads.each { |t|
|
124
|
+
t.kill
|
125
|
+
}
|
126
|
+
@worker_threads = []
|
127
|
+
}
|
128
|
+
|
129
|
+
elsif @timeout
|
130
|
+
readd = []
|
131
|
+
while @running_queue.size > 0 && work = @running_queue.pop
|
132
|
+
if @timeout && work.handle_timeout!(@timeout)
|
133
|
+
@pool_lock.synchronize {
|
134
|
+
@worker_threads.delete(work.thread)
|
135
|
+
launch_worker
|
136
|
+
}
|
137
|
+
elsif !work.completed?
|
138
|
+
readd << work
|
139
|
+
end
|
140
|
+
end
|
141
|
+
readd.each { |work| @running_queue << work }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Internal helper, launch management thread
|
146
|
+
#
|
147
|
+
# Should only be launched from within the pool_lock
|
148
|
+
def launch_manager
|
149
|
+
@manager_thread = Thread.new {
|
150
|
+
until @terminate
|
151
|
+
# sleep needs to occur b4 check workers so
|
152
|
+
# workers are guaranteed to be terminated on @terminate
|
153
|
+
# !FIXME! this enforces a mandatory setting of @timeout which was never intended:
|
154
|
+
sleep @timeout
|
155
|
+
check_workers
|
156
|
+
end
|
157
|
+
check_workers
|
158
|
+
@pool_lock.synchronize { @manager_thread = nil }
|
159
|
+
} unless @manager_thread
|
160
|
+
end
|
161
|
+
|
162
|
+
public
|
163
|
+
# Create a thread pool with a specified number of threads
|
164
|
+
# @param [Integer] num_threads the number of worker threads to create
|
165
|
+
# @param [Hash] args optional arguments to initialize thread pool with
|
166
|
+
# @option args [Integer] :timeout optional timeout to use to kill long running worker jobs
|
167
|
+
def initialize(num_threads, args = {})
|
168
|
+
@work_queue = Queue.new
|
169
|
+
@running_queue = Queue.new
|
170
|
+
|
171
|
+
@num_threads = num_threads
|
172
|
+
@pool_lock = Mutex.new
|
173
|
+
@worker_threads = []
|
174
|
+
|
175
|
+
@timeout = args[:timeout]
|
176
|
+
|
177
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(self))
|
178
|
+
end
|
179
|
+
|
180
|
+
# Return internal thread pool state in string
|
181
|
+
def inspect
|
182
|
+
"wq#{@work_queue.size}/\
|
183
|
+
rq#{@running_queue.size}/\
|
184
|
+
nt#{@num_threads.size}/\
|
185
|
+
wt#{@worker_threads.select { |wt| ['sleep', 'run'].include?(wt.status) }.size}ok-\
|
186
|
+
#{@worker_threads.select { |wt| ['aborting', false, nil].include?(wt.status) }.size}nok/\
|
187
|
+
to#{@timeout}"
|
188
|
+
end
|
189
|
+
|
190
|
+
# Start the thread pool
|
191
|
+
def start
|
192
|
+
# clear work and timeout queues?
|
193
|
+
@pool_lock.synchronize {
|
194
|
+
@terminate = false
|
195
|
+
launch_manager
|
196
|
+
0.upto(@num_threads) { |i| launch_worker }
|
197
|
+
}
|
198
|
+
end
|
199
|
+
|
200
|
+
# Ruby ObjectSpace finalizer to ensure that thread pool terminates all
|
201
|
+
# threads when object is destroyed
|
202
|
+
def self.finalize(thread_pool)
|
203
|
+
proc { thread_pool.stop ; thread_pool.join }
|
204
|
+
end
|
205
|
+
|
206
|
+
# Return boolean indicating if thread pool is running.
|
207
|
+
#
|
208
|
+
# If at least one worker thread isn't terminated, the pool is still considered running
|
209
|
+
def running?
|
210
|
+
@pool_lock.synchronize { @worker_threads.size != 0 && @worker_threads.all? { |t| t.status } }
|
211
|
+
end
|
212
|
+
|
213
|
+
# Add work to the pool
|
214
|
+
# @param [ThreadPool2Job] work job to execute in first available thread
|
215
|
+
def <<(work)
|
216
|
+
@work_queue.push work
|
217
|
+
end
|
218
|
+
|
219
|
+
# Terminate the thread pool, stopping all worker threads
|
220
|
+
def stop
|
221
|
+
@pool_lock.synchronize {
|
222
|
+
@terminate = true
|
223
|
+
|
224
|
+
# wakeup management thread so it can kill workers
|
225
|
+
# before terminating on its own
|
226
|
+
begin
|
227
|
+
@manager_thread.wakeup
|
228
|
+
|
229
|
+
# incase thread wakes up / terminates on its own
|
230
|
+
rescue ThreadError
|
231
|
+
|
232
|
+
end
|
233
|
+
}
|
234
|
+
join
|
235
|
+
end
|
236
|
+
|
237
|
+
# Block until all worker threads have finished executing
|
238
|
+
def join
|
239
|
+
#@pool_lock.synchronize { @worker_threads.each { |t| t.join unless @terminate } }
|
240
|
+
th = nil
|
241
|
+
@pool_lock.synchronize { th = @manager_thread if @manager_thread }
|
242
|
+
th.join if th
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Providers an interface to access a shared thread pool.
|
247
|
+
#
|
248
|
+
# Thread pool operations may be invoked on this class after
|
249
|
+
# the 'init' method is called
|
250
|
+
#
|
251
|
+
# ThreadPool2Manager.init
|
252
|
+
# ThreadPool2Manager << ThreadPool2Job(:foo) { "do something" }
|
253
|
+
class ThreadPool2Manager
|
254
|
+
# Initialize thread pool if it doesn't exist
|
255
|
+
def self.init(num_threads, params = {})
|
256
|
+
if @thread_pool.nil?
|
257
|
+
@thread_pool = ThreadPool2.new(num_threads, params)
|
258
|
+
end
|
259
|
+
@thread_pool.start
|
260
|
+
end
|
261
|
+
|
262
|
+
# Return shared thread pool
|
263
|
+
def self.thread_pool
|
264
|
+
@thread_pool
|
265
|
+
end
|
266
|
+
|
267
|
+
# Delegates all methods invoked on calls to thread pool
|
268
|
+
def self.method_missing(method_id, *args, &bl)
|
269
|
+
@thread_pool.send method_id, *args, &bl
|
270
|
+
end
|
271
|
+
end
|
data/lib/rjr/util.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# High level rjr utility mechanisms
|
2
|
+
#
|
3
|
+
# Copyright (C) 2013 Mohammed Morsi <mo@morsi.org>
|
4
|
+
# Licensed under the Apache License, Version 2.0
|
5
|
+
|
6
|
+
require 'rjr/dispatcher'
|
7
|
+
|
8
|
+
module RJR
|
9
|
+
|
10
|
+
# Mixin providing utility methods to define rjr methods and messages
|
11
|
+
module Definitions
|
12
|
+
# Define one or more rjr methods, parameters should be in the form
|
13
|
+
# :id => Callable
|
14
|
+
#
|
15
|
+
# id may be a single id or an array of them
|
16
|
+
def rjr_method(args = {})
|
17
|
+
args.each { |k, v|
|
18
|
+
RJR::Dispatcher.add_handler(k.to_s, &v)
|
19
|
+
}
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
# Define/retrieve rjr messages. When defining pass a hash
|
24
|
+
# of mesasge ids to options to use in the defintions, eg
|
25
|
+
# :id => { :foo => :bar }
|
26
|
+
#
|
27
|
+
# When retrieving simply specify the id
|
28
|
+
def rjr_message(args={})
|
29
|
+
if args.is_a?(Hash)
|
30
|
+
args.each { |k,v|
|
31
|
+
RJR::Definitions.message(k.to_s, v)
|
32
|
+
}
|
33
|
+
nil
|
34
|
+
else
|
35
|
+
RJR::Definitions.message(args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Helper providing access to messages
|
40
|
+
def self.message(k, v=nil)
|
41
|
+
@rjr_messages ||= {}
|
42
|
+
@rjr_messages[k] = v unless v.nil?
|
43
|
+
@rjr_messages[k]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Reset message registry
|
47
|
+
def self.reset
|
48
|
+
# TODO also invoke 'Dispatcher.init_handlers' ?
|
49
|
+
@rjr_messages = {}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Generate / return random message. Optionally specify the transport which
|
53
|
+
# the message must accept
|
54
|
+
def self.rand_msg(transport = nil)
|
55
|
+
@rjr_messages ||= {}
|
56
|
+
messages = @rjr_messages.select { |mid,m| m[:transports].nil? || transport.nil? ||
|
57
|
+
m[:transports].include?(transport) }
|
58
|
+
messages[messages.keys[rand(messages.keys.size)]]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Class to encapsulate any number of rjr nodes
|
63
|
+
class EasyNode
|
64
|
+
def initialize(node_args = {})
|
65
|
+
nodes = node_args.keys.collect { |n|
|
66
|
+
case n
|
67
|
+
when :amqp then
|
68
|
+
RJR::AMQPNode.new node_args[:amqp]
|
69
|
+
when :ws then
|
70
|
+
RJR::WSNode.new node_args[:ws]
|
71
|
+
when :tcp then
|
72
|
+
RJR::TCPNode.new node_args[:tcp]
|
73
|
+
when :www then
|
74
|
+
RJR::WebNode.new node_args[:www]
|
75
|
+
end
|
76
|
+
}
|
77
|
+
@multi_node = RJR::MultiNode.new :nodes => nodes
|
78
|
+
end
|
79
|
+
|
80
|
+
def invoke_request(dst, method, *params)
|
81
|
+
# TODO allow selection of node, eg automatically deduce which node type to use from 'dst'
|
82
|
+
@multi_node.nodes.first.invoke_request(dst, method, *params)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Stop node on the specified signal
|
86
|
+
def stop_on(signal)
|
87
|
+
Signal.trap(signal) {
|
88
|
+
@multi_node.stop
|
89
|
+
}
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
def listen
|
94
|
+
@multi_node.listen
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
def join
|
99
|
+
@multi_node.join
|
100
|
+
self
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end # module RJR
|
data/lib/rjr/web_node.rb
CHANGED
@@ -8,16 +8,27 @@
|
|
8
8
|
# Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
|
9
9
|
# Licensed under the Apache License, Version 2.0
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
skip_module = false
|
12
|
+
begin
|
13
|
+
require 'evma_httpserver'
|
14
|
+
require 'em-http-request'
|
15
|
+
# TODO also support fallback clients ? (curb / net/http / etc)
|
16
|
+
rescue LoadError
|
17
|
+
skip_module = true
|
18
|
+
end
|
13
19
|
|
14
|
-
|
20
|
+
if skip_module
|
21
|
+
# TODO output: "curb/evma_httpserver gems could not be loaded, skipping web node definition"
|
22
|
+
require 'rjr/missing_node'
|
23
|
+
RJR::WebNode = RJR::MissingNode
|
15
24
|
|
16
|
-
|
17
|
-
|
25
|
+
else
|
26
|
+
require 'socket'
|
18
27
|
|
19
28
|
require 'rjr/node'
|
20
29
|
require 'rjr/message'
|
30
|
+
require 'rjr/dispatcher'
|
31
|
+
require 'rjr/thread_pool2'
|
21
32
|
|
22
33
|
module RJR
|
23
34
|
|
@@ -28,6 +39,7 @@ class WebNodeCallback
|
|
28
39
|
end
|
29
40
|
|
30
41
|
def invoke(callback_method, *data)
|
42
|
+
# TODO throw error?
|
31
43
|
end
|
32
44
|
end
|
33
45
|
|
@@ -49,8 +61,7 @@ class WebRequestHandler < EventMachine::Connection
|
|
49
61
|
def process_http_request
|
50
62
|
# TODO support http protocols other than POST
|
51
63
|
msg = @http_post_content.nil? ? '' : @http_post_content
|
52
|
-
|
53
|
-
handle_request(msg)
|
64
|
+
ThreadPool2Manager << ThreadPool2Job.new(msg) { |m| handle_request(m) }
|
54
65
|
end
|
55
66
|
|
56
67
|
private
|
@@ -59,9 +70,12 @@ class WebRequestHandler < EventMachine::Connection
|
|
59
70
|
def handle_request(message)
|
60
71
|
msg = nil
|
61
72
|
result = nil
|
73
|
+
notification = NotificationMessage.is_notification_message?(msg)
|
74
|
+
|
62
75
|
begin
|
63
76
|
client_port, client_ip = Socket.unpack_sockaddr_in(get_peername)
|
64
|
-
msg =
|
77
|
+
msg = notification ? NotificationMessage.new(:message => message, :headers => @web_node.message_headers) :
|
78
|
+
RequestMessage.new(:message => message, :headers => @web_node.message_headers)
|
65
79
|
headers = @web_node.message_headers.merge(msg.headers)
|
66
80
|
result = Dispatcher.dispatch_request(msg.jr_method,
|
67
81
|
:method_args => msg.jr_args,
|
@@ -79,12 +93,14 @@ class WebRequestHandler < EventMachine::Connection
|
|
79
93
|
msg_id = msg.nil? ? nil : msg.msg_id
|
80
94
|
response = ResponseMessage.new(:id => msg_id, :result => result, :headers => headers)
|
81
95
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
96
|
+
unless notification
|
97
|
+
resp = EventMachine::DelegatedHttpResponse.new(self)
|
98
|
+
#resp.status = response.result.success ? 200 : 500
|
99
|
+
resp.status = 200
|
100
|
+
resp.content = response.to_s
|
101
|
+
resp.content_type "application/json"
|
102
|
+
resp.send_response
|
103
|
+
end
|
88
104
|
end
|
89
105
|
end
|
90
106
|
|
@@ -117,13 +133,48 @@ end
|
|
117
133
|
#
|
118
134
|
class WebNode < RJR::Node
|
119
135
|
private
|
120
|
-
|
121
|
-
|
136
|
+
|
137
|
+
# Internal helper, handle response message received
|
138
|
+
def handle_response(http)
|
139
|
+
msg = ResponseMessage.new(:message => http.response, :headers => @message_headers)
|
140
|
+
res = err = nil
|
141
|
+
begin
|
142
|
+
res = Dispatcher.handle_response(msg.result)
|
143
|
+
rescue Exception => e
|
144
|
+
err = e
|
145
|
+
end
|
146
|
+
|
147
|
+
@response_lock.synchronize {
|
148
|
+
result = [msg.msg_id, res]
|
149
|
+
result << err if !err.nil?
|
150
|
+
@responses << result
|
151
|
+
@response_cv.signal
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
# Internal helper, block until response matching message id is received
|
156
|
+
def wait_for_result(message)
|
157
|
+
res = nil
|
158
|
+
while res.nil?
|
159
|
+
@response_lock.synchronize{
|
160
|
+
# FIXME throw err if more than 1 match found
|
161
|
+
res = @responses.select { |response| message.msg_id == response.first }.first
|
162
|
+
if !res.nil?
|
163
|
+
@responses.delete(res)
|
164
|
+
|
165
|
+
else
|
166
|
+
@response_cv.signal
|
167
|
+
@response_cv.wait @response_lock
|
168
|
+
|
169
|
+
end
|
170
|
+
}
|
171
|
+
end
|
172
|
+
return res
|
122
173
|
end
|
123
174
|
|
124
175
|
public
|
125
176
|
|
126
|
-
#
|
177
|
+
# WebNode initializer
|
127
178
|
# @param [Hash] args the options to create the tcp node with
|
128
179
|
# @option args [String] :host the hostname/ip which to listen on
|
129
180
|
# @option args [Integer] :port the port which to listen on
|
@@ -131,6 +182,10 @@ class WebNode < RJR::Node
|
|
131
182
|
super(args)
|
132
183
|
@host = args[:host]
|
133
184
|
@port = args[:port]
|
185
|
+
|
186
|
+
@response_lock = Mutex.new
|
187
|
+
@response_cv = ConditionVariable.new
|
188
|
+
@responses = []
|
134
189
|
end
|
135
190
|
|
136
191
|
# Register connection event handler,
|
@@ -148,7 +203,6 @@ class WebNode < RJR::Node
|
|
148
203
|
# Implementation of {RJR::Node#listen}
|
149
204
|
def listen
|
150
205
|
em_run do
|
151
|
-
init_node
|
152
206
|
EventMachine::start_server(@host, @port, WebRequestHandler, self)
|
153
207
|
end
|
154
208
|
end
|
@@ -159,15 +213,53 @@ class WebNode < RJR::Node
|
|
159
213
|
# @param [String] rpc_method json-rpc method to invoke on destination
|
160
214
|
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
161
215
|
def invoke_request(uri, rpc_method, *args)
|
162
|
-
init_node
|
163
216
|
message = RequestMessage.new :method => rpc_method,
|
164
217
|
:args => args,
|
165
218
|
:headers => @message_headers
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
219
|
+
cb = lambda { |http|
|
220
|
+
# TODO handle errors
|
221
|
+
handle_response(http)
|
222
|
+
}
|
223
|
+
|
224
|
+
em_run do
|
225
|
+
http = EventMachine::HttpRequest.new(uri).post :body => message.to_s
|
226
|
+
http.errback &cb
|
227
|
+
http.callback &cb
|
228
|
+
end
|
229
|
+
|
230
|
+
# will block until response message is received
|
231
|
+
# TODO optional timeout for response ?
|
232
|
+
result = wait_for_result(message)
|
233
|
+
if result.size > 2
|
234
|
+
raise Exception, result[2]
|
235
|
+
end
|
236
|
+
return result[1]
|
237
|
+
end
|
238
|
+
|
239
|
+
# Instructs node to send rpc notification (immadiately returns / no response is generated)
|
240
|
+
#
|
241
|
+
# @param [String] uri location of node to send request to, should be
|
242
|
+
# in format of http://hostname:port
|
243
|
+
# @param [String] rpc_method json-rpc method to invoke on destination
|
244
|
+
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
245
|
+
def send_notification(uri, rpc_method, *args)
|
246
|
+
# will block until message is published
|
247
|
+
published_l = Mutex.new
|
248
|
+
published_c = ConditionVariable.new
|
249
|
+
|
250
|
+
message = NotificationMessage.new :method => rpc_method,
|
251
|
+
:args => args,
|
252
|
+
:headers => @message_headers
|
253
|
+
cb = lambda { |arg| published_l.synchronize { published_c.signal }}
|
254
|
+
em_run do
|
255
|
+
http = EventMachine::HttpRequest.new(uri).post :body => message.to_s
|
256
|
+
http.errback &cb
|
257
|
+
http.callback &cb
|
258
|
+
end
|
259
|
+
published_l.synchronize { published_c.wait published_l }
|
260
|
+
nil
|
170
261
|
end
|
171
262
|
end
|
172
263
|
|
173
264
|
end # module RJR
|
265
|
+
end
|