rjr 0.12.2 → 0.15.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +49 -36
- data/Rakefile +2 -0
- data/bin/rjr-client +11 -9
- data/bin/rjr-server +12 -10
- data/examples/amqp.rb +29 -0
- data/examples/client.rb +32 -0
- data/examples/complete.rb +36 -0
- data/examples/local.rb +29 -0
- data/examples/server.rb +26 -0
- data/examples/tcp.rb +29 -0
- data/examples/web.rb +22 -0
- data/examples/ws.rb +29 -0
- data/lib/rjr/common.rb +7 -12
- data/lib/rjr/dispatcher.rb +171 -239
- data/lib/rjr/em_adapter.rb +33 -66
- data/lib/rjr/message.rb +43 -12
- data/lib/rjr/node.rb +197 -103
- data/lib/rjr/nodes/amqp.rb +216 -0
- data/lib/rjr/nodes/easy.rb +159 -0
- data/lib/rjr/nodes/local.rb +118 -0
- data/lib/rjr/{missing_node.rb → nodes/missing.rb} +4 -2
- data/lib/rjr/nodes/multi.rb +79 -0
- data/lib/rjr/nodes/tcp.rb +211 -0
- data/lib/rjr/nodes/web.rb +197 -0
- data/lib/rjr/nodes/ws.rb +187 -0
- data/lib/rjr/stats.rb +70 -0
- data/lib/rjr/thread_pool.rb +178 -123
- data/site/index.html +45 -0
- data/site/jquery-latest.js +9404 -0
- data/site/jrw.js +297 -0
- data/site/json.js +199 -0
- data/specs/dispatcher_spec.rb +244 -198
- data/specs/em_adapter_spec.rb +52 -80
- data/specs/message_spec.rb +223 -197
- data/specs/node_spec.rb +67 -163
- data/specs/nodes/amqp_spec.rb +82 -0
- data/specs/nodes/easy_spec.rb +13 -0
- data/specs/nodes/local_spec.rb +72 -0
- data/specs/nodes/multi_spec.rb +65 -0
- data/specs/nodes/tcp_spec.rb +75 -0
- data/specs/nodes/web_spec.rb +77 -0
- data/specs/nodes/ws_spec.rb +78 -0
- data/specs/stats_spec.rb +59 -0
- data/specs/thread_pool_spec.rb +44 -35
- metadata +40 -30
- data/lib/rjr/amqp_node.rb +0 -330
- data/lib/rjr/inspect.rb +0 -65
- data/lib/rjr/local_node.rb +0 -150
- data/lib/rjr/multi_node.rb +0 -65
- data/lib/rjr/tcp_node.rb +0 -323
- data/lib/rjr/thread_pool2.rb +0 -272
- data/lib/rjr/util.rb +0 -104
- data/lib/rjr/web_node.rb +0 -266
- data/lib/rjr/ws_node.rb +0 -289
- data/lib/rjr.rb +0 -16
- data/specs/amqp_node_spec.rb +0 -31
- data/specs/inspect_spec.rb +0 -60
- data/specs/local_node_spec.rb +0 -43
- data/specs/multi_node_spec.rb +0 -45
- data/specs/tcp_node_spec.rb +0 -33
- data/specs/util_spec.rb +0 -46
- data/specs/web_node_spec.rb +0 -32
- data/specs/ws_node_spec.rb +0 -32
- /data/lib/rjr/{tcp_node2.rb → nodes/tcp2.rb} +0 -0
- /data/lib/rjr/{udp_node.rb → nodes/udp.rb} +0 -0
data/lib/rjr/thread_pool.rb
CHANGED
@@ -1,184 +1,239 @@
|
|
1
|
-
# Thread Pool
|
1
|
+
# Thread Pool (second implementation)
|
2
2
|
#
|
3
|
-
# Copyright (C) 2010-
|
3
|
+
# Copyright (C) 2010-2013 Mohammed Morsi <mo@morsi.org>
|
4
4
|
# Licensed under the Apache License, Version 2.0
|
5
5
|
|
6
|
-
|
6
|
+
require 'singleton'
|
7
|
+
|
8
|
+
module RJR
|
9
|
+
|
10
|
+
# Work item to be executed in a thread launched by {ThreadPool}.
|
11
|
+
#
|
12
|
+
# The end user should initialize this class with a handle
|
13
|
+
# to the job to be executed and the params to pass to it, then
|
14
|
+
# hand the instance off to the thread pool to take care of the rest.
|
7
15
|
class ThreadPoolJob
|
16
|
+
# Proc to be invoked to perform work
|
8
17
|
attr_accessor :handler
|
18
|
+
|
19
|
+
# Parameters to pass to handler proc
|
9
20
|
attr_accessor :params
|
10
21
|
|
22
|
+
# Time job started, if nil job hasn't started yet
|
23
|
+
attr_accessor :time_started
|
24
|
+
|
25
|
+
# Time job completed, if nil job hasn't completed yet
|
26
|
+
attr_accessor :time_completed
|
27
|
+
|
28
|
+
# Thread running the job
|
29
|
+
attr_accessor :thread
|
30
|
+
|
11
31
|
# ThreadPoolJob initializer
|
12
32
|
# @param [Array] params arguments to pass to the job when it is invoked
|
13
33
|
# @param [Callable] block handle to callable object corresponding to job to invoke
|
14
34
|
def initialize(*params, &block)
|
15
35
|
@params = params
|
16
36
|
@handler = block
|
37
|
+
@being_executed = false
|
38
|
+
@timestamp = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return bool indicating if job has started
|
42
|
+
def started?
|
43
|
+
!@time_started.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return bool indicating if job has completed
|
47
|
+
def completed?
|
48
|
+
!@time_started.nil? && !@time_completed.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return bool indicating if the job has started but not completed
|
52
|
+
# and the specified timeout has expired
|
53
|
+
def expired?(timeout)
|
54
|
+
!@time_started.nil? && @time_completed.nil? && ((Time.now - @time_started) > timeout)
|
17
55
|
end
|
18
|
-
end
|
19
56
|
|
57
|
+
# Set job metadata and execute job with specified params.
|
58
|
+
#
|
59
|
+
# Used internally by thread pool
|
60
|
+
def exec(lock)
|
61
|
+
lock.synchronize {
|
62
|
+
@thread = Thread.current
|
63
|
+
@time_started = Time.now
|
64
|
+
}
|
65
|
+
|
66
|
+
@handler.call *@params
|
67
|
+
|
68
|
+
# ensure we do not switch to another job
|
69
|
+
# before atomic check expiration / terminate
|
70
|
+
# expired threads happens below
|
71
|
+
lock.synchronize {
|
72
|
+
@time_completed = Time.now
|
73
|
+
@thread = nil
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
20
77
|
|
21
78
|
# Utility to launches a specified number of threads on instantiation,
|
22
79
|
# assigning work to them in order as it arrives.
|
23
80
|
#
|
24
|
-
# Supports optional timeout which allows the
|
81
|
+
# Supports optional timeout which allows the user to kill and restart
|
25
82
|
# threads if a job is taking too long to run.
|
26
83
|
class ThreadPool
|
84
|
+
include Singleton
|
27
85
|
|
28
|
-
|
29
|
-
|
30
|
-
class ThreadPoolJobRunner
|
31
|
-
attr_accessor :time_started
|
86
|
+
class << self
|
87
|
+
# @!group Config options (must be set before first node is instantiated)
|
32
88
|
|
33
|
-
|
34
|
-
|
35
|
-
@timeout_lock = Mutex.new
|
36
|
-
@thread_lock = Mutex.new
|
37
|
-
end
|
89
|
+
# Number of threads to instantiate in local worker pool
|
90
|
+
attr_accessor :num_threads
|
38
91
|
|
39
|
-
#
|
40
|
-
|
41
|
-
# This method will return immediately after the worker thread is started but the
|
42
|
-
# thread launched will persist until {#stop} is invoked
|
43
|
-
def run
|
44
|
-
@thread_lock.synchronize {
|
45
|
-
@thread = Thread.new {
|
46
|
-
until @thread_pool.terminate
|
47
|
-
@timeout_lock.synchronize { @time_started = nil }
|
48
|
-
work = @thread_pool.next_job
|
49
|
-
@timeout_lock.synchronize { @time_started = Time.now }
|
50
|
-
unless work.nil?
|
51
|
-
begin
|
52
|
-
work.handler.call *work.params
|
53
|
-
rescue Exception => e
|
54
|
-
puts "Thread raised Fatal Exception #{e}"
|
55
|
-
puts "\n#{e.backtrace.join("\n")}"
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
}
|
60
|
-
}
|
61
|
-
end
|
92
|
+
# Timeout after which worker threads are killed
|
93
|
+
attr_accessor :timeout
|
62
94
|
|
63
|
-
#
|
64
|
-
|
65
|
-
res = nil
|
66
|
-
@thread_lock.synchronize{
|
67
|
-
res = (!@thread.nil? && (@thread.status != false))
|
68
|
-
}
|
69
|
-
res
|
70
|
-
end
|
95
|
+
# @!endgroup
|
96
|
+
end
|
71
97
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
98
|
+
private
|
99
|
+
|
100
|
+
# Internal helper, launch worker thread
|
101
|
+
def launch_worker
|
102
|
+
@worker_threads << Thread.new {
|
103
|
+
while work = @work_queue.pop
|
104
|
+
begin
|
105
|
+
#RJR::Logger.debug "launch thread pool job #{work}"
|
106
|
+
@running_queue << work
|
107
|
+
work.exec(@thread_lock)
|
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")}"
|
79
114
|
end
|
115
|
+
end
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
# Internal helper, performs checks on workers
|
120
|
+
def check_workers
|
121
|
+
if @terminate
|
122
|
+
@worker_threads.each { |t|
|
123
|
+
t.kill
|
80
124
|
}
|
125
|
+
@worker_threads = []
|
126
|
+
|
127
|
+
elsif @timeout
|
128
|
+
readd = []
|
129
|
+
while @running_queue.size > 0 && work = @running_queue.pop
|
130
|
+
# check expiration / killing expired threads must be atomic
|
131
|
+
# and mutually exclusive with the process of marking a job completed above
|
132
|
+
@thread_lock.synchronize{
|
133
|
+
if work.expired?(@timeout)
|
134
|
+
work.thread.kill
|
135
|
+
@worker_threads.delete(work.thread)
|
136
|
+
launch_worker
|
137
|
+
|
138
|
+
elsif !work.completed?
|
139
|
+
readd << work
|
140
|
+
end
|
141
|
+
}
|
142
|
+
end
|
143
|
+
readd.each { |work| @running_queue << work }
|
81
144
|
end
|
145
|
+
end
|
82
146
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
@
|
147
|
+
# Internal helper, launch management thread
|
148
|
+
def launch_manager
|
149
|
+
@manager_thread = Thread.new {
|
150
|
+
until @terminate
|
151
|
+
if @timeout
|
152
|
+
sleep @timeout
|
153
|
+
check_workers
|
154
|
+
else
|
155
|
+
Thread.yield
|
89
156
|
end
|
90
|
-
|
91
|
-
}
|
92
|
-
end
|
157
|
+
end
|
93
158
|
|
94
|
-
|
95
|
-
|
96
|
-
@thread_lock.synchronize {
|
97
|
-
@thread.join unless @thread.nil?
|
98
|
-
}
|
99
|
-
end
|
159
|
+
check_workers
|
160
|
+
}
|
100
161
|
end
|
101
162
|
|
102
|
-
# Create a thread pool
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
@
|
108
|
-
@
|
109
|
-
@job_runners = []
|
110
|
-
@job_runners_lock = Mutex.new
|
111
|
-
@terminate = false
|
112
|
-
@terminate_lock = Mutex.new
|
163
|
+
# Create a new thread pool
|
164
|
+
def initialize
|
165
|
+
RJR::ThreadPool.num_threads ||= 20
|
166
|
+
RJR::ThreadPool.timeout ||= 10
|
167
|
+
@num_threads = RJR::ThreadPool.num_threads
|
168
|
+
@timeout = RJR::ThreadPool.timeout
|
169
|
+
@worker_threads = []
|
113
170
|
|
114
|
-
@work_queue
|
171
|
+
@work_queue = Queue.new
|
172
|
+
@running_queue = Queue.new
|
115
173
|
|
116
|
-
|
117
|
-
|
118
|
-
@job_runners << runner
|
119
|
-
runner.run
|
120
|
-
}
|
174
|
+
@thread_lock = Mutex.new
|
175
|
+
@terminate = true
|
121
176
|
|
122
|
-
|
123
|
-
unless @timeout.nil?
|
124
|
-
@timeout_thread = Thread.new {
|
125
|
-
until terminate
|
126
|
-
sleep @timeout
|
127
|
-
@job_runners_lock.synchronize {
|
128
|
-
@job_runners.each { |jr|
|
129
|
-
jr.check_timeout(@timeout)
|
130
|
-
}
|
131
|
-
}
|
132
|
-
end
|
133
|
-
}
|
134
|
-
end
|
177
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(self))
|
135
178
|
end
|
136
179
|
|
137
|
-
#
|
138
|
-
#
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
180
|
+
# Ruby ObjectSpace finalizer to ensure that thread pool terminates all
|
181
|
+
# threads when object is destroyed.
|
182
|
+
def self.finalize(thread_pool)
|
183
|
+
# TODO this isn't doing much as by the time this is invoked threads will
|
184
|
+
# already be shutdown
|
185
|
+
proc { thread_pool.stop ; thread_pool.join }
|
143
186
|
end
|
144
187
|
|
145
|
-
|
146
|
-
|
147
|
-
|
188
|
+
public
|
189
|
+
|
190
|
+
# Start the thread pool
|
191
|
+
def start
|
192
|
+
return self unless @terminate
|
193
|
+
@terminate = false
|
194
|
+
0.upto(@num_threads-1) { |i| launch_worker }
|
195
|
+
launch_manager
|
196
|
+
self
|
148
197
|
end
|
149
198
|
|
150
|
-
#
|
151
|
-
|
152
|
-
|
153
|
-
@
|
199
|
+
# Return boolean indicating if thread pool is running
|
200
|
+
def running?
|
201
|
+
!@manager_thread.nil? &&
|
202
|
+
['sleep', 'run'].include?(@manager_thread.status)
|
154
203
|
end
|
155
204
|
|
156
205
|
# Add work to the pool
|
157
206
|
# @param [ThreadPoolJob] work job to execute in first available thread
|
207
|
+
# @return self
|
158
208
|
def <<(work)
|
209
|
+
# TODO option to increase worker threads if work queue gets saturated
|
159
210
|
@work_queue.push work
|
160
|
-
|
161
|
-
|
162
|
-
# Return the next job queued up and remove it from the queue
|
163
|
-
def next_job
|
164
|
-
@work_queue.pop
|
211
|
+
self
|
165
212
|
end
|
166
213
|
|
167
214
|
# Terminate the thread pool, stopping all worker threads
|
215
|
+
#
|
216
|
+
# @return self
|
168
217
|
def stop
|
169
|
-
terminate = true
|
170
|
-
|
171
|
-
|
172
|
-
|
218
|
+
@terminate = true
|
219
|
+
|
220
|
+
# this will wake up on it's own, but we can
|
221
|
+
# speed things up if we manually wake it up,
|
222
|
+
# surround w/ block incase thread cleans up on its own
|
223
|
+
begin
|
224
|
+
@manager_thread.wakeup if @manager_thread
|
225
|
+
rescue
|
173
226
|
end
|
174
|
-
|
175
|
-
@work_queue.clear
|
176
|
-
@job_runners_lock.synchronize { @job_runners.each { |jr| jr.stop } }
|
227
|
+
self
|
177
228
|
end
|
178
229
|
|
179
230
|
# Block until all worker threads have finished executing
|
231
|
+
#
|
232
|
+
# @return self
|
180
233
|
def join
|
181
|
-
@
|
234
|
+
@manager_thread.join if @manager_thread
|
235
|
+
self
|
182
236
|
end
|
183
237
|
end
|
184
238
|
|
239
|
+
end # module RJR
|
data/site/index.html
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
<!-- An example rjr js client.
|
2
|
+
To run:
|
3
|
+
- start bin/rjr-server
|
4
|
+
- setup a frontend webserver,
|
5
|
+
for example, add the following apache conf:
|
6
|
+
Alias /rjr-test /path/to/rjr/site
|
7
|
+
ProxyPass /rjr http://localhost:8888
|
8
|
+
ProxyPassReverse /rjr http://localhost:8888
|
9
|
+
- startup your webserver:
|
10
|
+
sudo service httpd start
|
11
|
+
(make sure to configure selinux / resolve permissions / etc)
|
12
|
+
- navigate to http://localhost/rjr-test
|
13
|
+
|
14
|
+
Copyright (C) 2013 Mohammed Morsi <mo@morsi.org>
|
15
|
+
Licensed under the Apache License, Version 2.0
|
16
|
+
-->
|
17
|
+
<html>
|
18
|
+
<head>
|
19
|
+
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
|
20
|
+
<script type="text/javascript" src="json.js"></script>
|
21
|
+
<script type="text/javascript" src="jrw.js"></script>
|
22
|
+
<script type="text/javascript">
|
23
|
+
var ws_node = new WSNode('127.0.0.1', '8080');
|
24
|
+
ws_node.onopen = function(){
|
25
|
+
ws_node.invoke('stress', 'ws1', function(res){
|
26
|
+
alert(res.result);
|
27
|
+
});
|
28
|
+
|
29
|
+
ws_node.invoke('failed', 'ws2', function(res){
|
30
|
+
alert(res.error.message);
|
31
|
+
});
|
32
|
+
ws_node.invoke('stress_callback', 3);
|
33
|
+
};
|
34
|
+
ws_node.open();
|
35
|
+
|
36
|
+
var www_node = new WebNode('http://localhost/rjr');
|
37
|
+
www_node.invoke('stress', 'http1', function(res){
|
38
|
+
alert(res.result);
|
39
|
+
})
|
40
|
+
www_node.invoke('failed', 'http2', function(res){
|
41
|
+
alert(res.error.message);
|
42
|
+
})
|
43
|
+
</script>
|
44
|
+
</head>
|
45
|
+
</html>
|