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.
Files changed (65) hide show
  1. data/README.md +49 -36
  2. data/Rakefile +2 -0
  3. data/bin/rjr-client +11 -9
  4. data/bin/rjr-server +12 -10
  5. data/examples/amqp.rb +29 -0
  6. data/examples/client.rb +32 -0
  7. data/examples/complete.rb +36 -0
  8. data/examples/local.rb +29 -0
  9. data/examples/server.rb +26 -0
  10. data/examples/tcp.rb +29 -0
  11. data/examples/web.rb +22 -0
  12. data/examples/ws.rb +29 -0
  13. data/lib/rjr/common.rb +7 -12
  14. data/lib/rjr/dispatcher.rb +171 -239
  15. data/lib/rjr/em_adapter.rb +33 -66
  16. data/lib/rjr/message.rb +43 -12
  17. data/lib/rjr/node.rb +197 -103
  18. data/lib/rjr/nodes/amqp.rb +216 -0
  19. data/lib/rjr/nodes/easy.rb +159 -0
  20. data/lib/rjr/nodes/local.rb +118 -0
  21. data/lib/rjr/{missing_node.rb → nodes/missing.rb} +4 -2
  22. data/lib/rjr/nodes/multi.rb +79 -0
  23. data/lib/rjr/nodes/tcp.rb +211 -0
  24. data/lib/rjr/nodes/web.rb +197 -0
  25. data/lib/rjr/nodes/ws.rb +187 -0
  26. data/lib/rjr/stats.rb +70 -0
  27. data/lib/rjr/thread_pool.rb +178 -123
  28. data/site/index.html +45 -0
  29. data/site/jquery-latest.js +9404 -0
  30. data/site/jrw.js +297 -0
  31. data/site/json.js +199 -0
  32. data/specs/dispatcher_spec.rb +244 -198
  33. data/specs/em_adapter_spec.rb +52 -80
  34. data/specs/message_spec.rb +223 -197
  35. data/specs/node_spec.rb +67 -163
  36. data/specs/nodes/amqp_spec.rb +82 -0
  37. data/specs/nodes/easy_spec.rb +13 -0
  38. data/specs/nodes/local_spec.rb +72 -0
  39. data/specs/nodes/multi_spec.rb +65 -0
  40. data/specs/nodes/tcp_spec.rb +75 -0
  41. data/specs/nodes/web_spec.rb +77 -0
  42. data/specs/nodes/ws_spec.rb +78 -0
  43. data/specs/stats_spec.rb +59 -0
  44. data/specs/thread_pool_spec.rb +44 -35
  45. metadata +40 -30
  46. data/lib/rjr/amqp_node.rb +0 -330
  47. data/lib/rjr/inspect.rb +0 -65
  48. data/lib/rjr/local_node.rb +0 -150
  49. data/lib/rjr/multi_node.rb +0 -65
  50. data/lib/rjr/tcp_node.rb +0 -323
  51. data/lib/rjr/thread_pool2.rb +0 -272
  52. data/lib/rjr/util.rb +0 -104
  53. data/lib/rjr/web_node.rb +0 -266
  54. data/lib/rjr/ws_node.rb +0 -289
  55. data/lib/rjr.rb +0 -16
  56. data/specs/amqp_node_spec.rb +0 -31
  57. data/specs/inspect_spec.rb +0 -60
  58. data/specs/local_node_spec.rb +0 -43
  59. data/specs/multi_node_spec.rb +0 -45
  60. data/specs/tcp_node_spec.rb +0 -33
  61. data/specs/util_spec.rb +0 -46
  62. data/specs/web_node_spec.rb +0 -32
  63. data/specs/ws_node_spec.rb +0 -32
  64. /data/lib/rjr/{tcp_node2.rb → nodes/tcp2.rb} +0 -0
  65. /data/lib/rjr/{udp_node.rb → nodes/udp.rb} +0 -0
@@ -1,184 +1,239 @@
1
- # Thread Pool
1
+ # Thread Pool (second implementation)
2
2
  #
3
- # Copyright (C) 2010-2012 Mohammed Morsi <mo@morsi.org>
3
+ # Copyright (C) 2010-2013 Mohammed Morsi <mo@morsi.org>
4
4
  # Licensed under the Apache License, Version 2.0
5
5
 
6
- # Work item to be executed in a thread launched by pool
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 developer to kill and restart
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
- # @private
29
- # Helper class to encapsulate each thread pool thread
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
- def initialize(thread_pool)
34
- @thread_pool = thread_pool
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
- # Start thread and pull a work items off the thread pool work queue and execute them.
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
- # Return boolean indicating if worker thread is running or not
64
- def running?
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
- # Return boolean indicating if worker thread run time has exceeded timeout
73
- # Should not invoke after stop is called
74
- def check_timeout(timeout)
75
- @timeout_lock.synchronize {
76
- if !@time_started.nil? && Time.now - @time_started > timeout
77
- stop
78
- run
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
- # Stop the worker thread being executed
84
- def stop
85
- @thread_lock.synchronize {
86
- if @thread.alive?
87
- @thread.kill
88
- @thread.join
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
- @thread = nil
91
- }
92
- end
157
+ end
93
158
 
94
- # Block until the worker thread is finished
95
- def join
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 with a specified number of threads
103
- # @param [Integer] num_threads the number of worker threads to create
104
- # @param [Hash] args optional arguments to initialize thread pool with
105
- # @option args [Integer] :timeout optional timeout to use to kill long running worker jobs
106
- def initialize(num_threads, args = {})
107
- @num_threads = num_threads
108
- @timeout = args[:timeout]
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 = Queue.new
171
+ @work_queue = Queue.new
172
+ @running_queue = Queue.new
115
173
 
116
- 0.upto(@num_threads) { |i|
117
- runner = ThreadPoolJobRunner.new(self)
118
- @job_runners << runner
119
- runner.run
120
- }
174
+ @thread_lock = Mutex.new
175
+ @terminate = true
121
176
 
122
- # optional timeout thread
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
- # Return boolean indicated if thread pool is running.
138
- #
139
- # If at least one worker thread isn't terminated, the pool is still considered running
140
- def running?
141
- !terminate && (@timeout.nil? || (!@timeout_thread.nil? && @timeout_thread.status)) &&
142
- @job_runners.all? { |r| r.running? }
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
- # Return boolean indicating if the thread pool should be terminated
146
- def terminate
147
- @terminate_lock.synchronize { @terminate }
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
- # Instruct thread pool to terminate
151
- # @param [Boolean] val true/false indicating if thread pool should terminate
152
- def terminate=(val)
153
- @terminate_lock.synchronize { @terminate = val }
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
- end
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
- unless @timout_thread.nil?
171
- @timeout_thread.join
172
- @timeout_thread.terminate
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
- @timeout_thread = nil
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
- @job_runners_lock.synchronize { @job_runners.each { |jr| jr.join } }
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>