rjr 0.12.2 → 0.15.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/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>
|