rjr 0.12.2 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
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,65 +0,0 @@
1
- # RJR MultiNode Endpoint
2
- #
3
- # Implements the RJR::Node interface to satisty JSON-RPC requests over multiple protocols
4
- #
5
- # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
6
- # Licensed under the Apache License, Version 2.0
7
-
8
- require 'eventmachine'
9
- require 'rjr/node'
10
- require 'rjr/message'
11
-
12
- module RJR
13
-
14
- # Multiple node definition, allows a developer to easily multiplex transport
15
- # mechanisms to serve JSON-RPC requests over.
16
- #
17
- # @example Listening for json-rpc requests over amqp, tcp, http, and websockets
18
- # # register rjr dispatchers (see RJR::Dispatcher)
19
- # RJR::Dispatcher.add_handler('hello') { |name|
20
- # # optionally use @rjr_node_type to handle different transport types
21
- # "Hello #{name}!"
22
- # }
23
- #
24
- # amqp_server = RJR::TCPNode.new :node_id => 'amqp_server', :broker => 'localhost'
25
- # tcp_server = RJR::TCPNode.new :node_id => 'tcp_server', :host => 'localhost', :port => '7777'
26
- # web_server = RJR::WebNode.new :node_id => 'tcp_server', :host => 'localhost', :port => '80'
27
- # ws_server = RJR::WebNode.new :node_id => 'tcp_server', :host => 'localhost', :port => '8080'
28
- #
29
- # server = RJR::MultiNode.new :node_id => 'server',
30
- # :nodes => [amqp_server, tcp_server, web_server, ws_server]
31
- # server.listen
32
- # server.join
33
- #
34
- # # invoke requests as you normally would via any protocol
35
- #
36
- class MultiNode < RJR::Node
37
- # Return the nodes
38
- attr_reader :nodes
39
-
40
- # MultiNode initializer
41
- # @param [Hash] args the options to create the tcp node with
42
- # @option args [Array<RJR::Node>] :nodes array of nodes to use to listen to new requests on
43
- def initialize(args = {})
44
- super(args)
45
- @nodes = args[:nodes]
46
- end
47
-
48
- # Add node to multinode
49
- # @param [RJR::Node] node the node to add
50
- def <<(node)
51
- @nodes << node
52
- end
53
-
54
-
55
- # Instruct Node to start listening for and dispatching rpc requests
56
- #
57
- # Implementation of {RJR::Node#listen}
58
- def listen
59
- @nodes.each { |node|
60
- node.listen
61
- }
62
- end
63
- end
64
-
65
- end
data/lib/rjr/tcp_node.rb DELETED
@@ -1,323 +0,0 @@
1
- # RJR TCP Endpoint
2
- #
3
- # Implements the RJR::Node interface to satisty JSON-RPC requests over the TCP protocol
4
- #
5
- # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
6
- # Licensed under the Apache License, Version 2.0
7
-
8
- require 'uri'
9
- require 'socket'
10
- require 'eventmachine'
11
-
12
- require 'rjr/node'
13
- require 'rjr/message'
14
- require 'rjr/message'
15
- require 'rjr/dispatcher'
16
- require 'rjr/errors'
17
- require 'rjr/thread_pool2'
18
-
19
- module RJR
20
-
21
- # TCP node callback interface, used to invoke json-rpc methods
22
- # against a remote node via a tcp socket connection previously opened
23
- #
24
- # After a node sends a json-rpc request to another, the either node may send
25
- # additional requests to each other via the socket already established until
26
- # it is closed on either end
27
- class TCPNodeCallback
28
-
29
- # TCPNodeCallback initializer
30
- # @param [Hash] args the options to create the tcp node callback with
31
- # @option args [TCPNodeEndpoint] :endpoint tcp node endpoint used to send/receive messages
32
- # @option args [Hash] :headers hash of rjr message headers present in client request when callback is established
33
- def initialize(args = {})
34
- @endpoint = args[:endpoint]
35
- @message_headers = args[:headers]
36
- end
37
-
38
- # Implementation of {RJR::NodeCallback#invoke}
39
- def invoke(callback_method, *data)
40
- msg = NotificationMessage.new :method => callback_method, :args => data, :headers => @message_headers
41
- # TODO surround w/ begin/rescue block incase of socket errors / raise RJR::ConnectionError
42
- @endpoint.safe_send msg.to_s
43
- end
44
- end
45
-
46
- # @private
47
- # Helper class intialized by eventmachine encapsulating a socket connection
48
- class TCPNodeEndpoint < EventMachine::Connection
49
-
50
- attr_reader :host
51
- attr_reader :port
52
-
53
- # TCPNodeEndpoint intializer
54
- #
55
- # specify the TCPNode establishing the connection
56
- def initialize(args = {})
57
- @rjr_node = args[:rjr_node]
58
- @host = args[:host]
59
- @port = args[:port]
60
-
61
- # used to serialize requests to send data via a connection
62
- @send_lock = Mutex.new
63
- end
64
-
65
- # {EventMachine::Connection#receive_data} callback, handle request / response messages
66
- def receive_data(data)
67
- # a large json-rpc message may be split over multiple packets (invocations of receive_data)
68
- # and multiple messages may be concatinated into one packet
69
- @data ||= ""
70
- @data += data
71
- while extracted = MessageUtil.retrieve_json(@data)
72
- msg, @data = *extracted
73
- if RequestMessage.is_request_message?(msg)
74
- ThreadPool2Manager << ThreadPool2Job.new(msg) { |m| handle_request(m, false) }
75
-
76
- elsif NotificationMessage.is_notification_message?(msg)
77
- ThreadPool2Manager << ThreadPool2Job.new(msg) { |m| handle_request(m, true) }
78
-
79
- elsif ResponseMessage.is_response_message?(msg)
80
- handle_response(msg)
81
-
82
- end
83
- end
84
- end
85
-
86
- # {EventMachine::Connection#unbind} callback, connection was closed
87
- def unbind
88
- end
89
-
90
- # Helper to send data safely, this should be invoked instead of send_data
91
- # in all cases
92
- def safe_send(data)
93
- @send_lock.synchronize{
94
- send_data(data)
95
- }
96
- end
97
-
98
-
99
- private
100
-
101
- # Internal helper, handle request message received
102
- def handle_request(data, notification=false)
103
- # XXX hack to handle client disconnection (should grap port/ip immediately on connection and use that)
104
- client_port,client_ip = nil,nil
105
- begin
106
- client_port, client_ip = Socket.unpack_sockaddr_in(get_peername)
107
- rescue Exception=>e
108
- end
109
-
110
- msg = notification ? NotificationMessage.new(:message => data, :headers => @rjr_node.message_headers) :
111
- RequestMessage.new(:message => data, :headers => @rjr_node.message_headers)
112
- headers = @rjr_node.message_headers.merge(msg.headers)
113
- result = Dispatcher.dispatch_request(msg.jr_method,
114
- :method_args => msg.jr_args,
115
- :headers => headers,
116
- :client_ip => client_ip,
117
- :client_port => client_port,
118
- :rjr_node => @rjr_node,
119
- :rjr_node_id => @rjr_node.node_id,
120
- :rjr_node_type => TCPNode::RJR_NODE_TYPE,
121
- :rjr_callback =>
122
- TCPNodeCallback.new(:endpoint => self,
123
- :headers => headers))
124
- unless notification
125
- response = ResponseMessage.new(:id => msg.msg_id, :result => result, :headers => headers)
126
- safe_send(response.to_s)
127
- end
128
- end
129
-
130
- # Internal helper, handle response message received
131
- def handle_response(data)
132
- msg = ResponseMessage.new(:message => data, :headers => @rjr_node.message_headers)
133
- res = err = nil
134
- begin
135
- res = Dispatcher.handle_response(msg.result)
136
- rescue Exception => e
137
- err = e
138
- end
139
-
140
- @rjr_node.response_lock.synchronize {
141
- result = [msg.msg_id, res]
142
- result << err if !err.nil?
143
- @rjr_node.responses << result
144
- @rjr_node.response_cv.signal
145
- }
146
- end
147
- end
148
-
149
- # TCP node definition, listen for and invoke json-rpc requests via TCP sockets
150
- #
151
- # Clients should specify the hostname / port when listening for requests and
152
- # when invoking them.
153
- #
154
- # @example Listening for json-rpc requests over tcp
155
- # # register rjr dispatchers (see RJR::Dispatcher)
156
- # RJR::Dispatcher.add_handler('hello') { |name|
157
- # "Hello #{name}!"
158
- # }
159
- #
160
- # # initialize node, listen, and block
161
- # server = RJR::TCPNode.new :node_id => 'server', :host => 'localhost', :port => '7777'
162
- # server.listen
163
- # server.join
164
- #
165
- # @example Invoking json-rpc requests over tcp
166
- # client = RJR::TCPNode.new :node_id => 'client', :host => 'localhost', :port => '8888'
167
- # puts client.invoke_request('jsonrpc://localhost:7777', 'hello', 'mo')
168
- #
169
- class TCPNode < RJR::Node
170
- RJR_NODE_TYPE = :tcp
171
-
172
- attr_accessor :connections
173
-
174
- attr_accessor :response_lock
175
- attr_accessor :response_cv
176
- attr_accessor :responses
177
-
178
- private
179
- # Internal helper, initialize new connection
180
- def init_node(args={}, &on_init)
181
- host,port = args[:host], args[:port]
182
- connection = nil
183
- @connections_lock.synchronize {
184
- connection = @connections.find { |c|
185
- port == c.port && host == c.host
186
- }
187
- if connection.nil?
188
- connection =
189
- EventMachine::connect host, port,
190
- TCPNodeEndpoint, args
191
- @connections << connection
192
- end
193
- }
194
- on_init.call(connection)
195
- end
196
-
197
- # Internal helper, block until response matching message id is received
198
- def wait_for_result(message)
199
- res = nil
200
- while res.nil?
201
- @response_lock.synchronize{
202
- # FIXME throw err if more than 1 match found
203
- res = @responses.select { |response| message.msg_id == response.first }.first
204
- if !res.nil?
205
- @responses.delete(res)
206
-
207
- else
208
- @response_cv.signal
209
- @response_cv.wait @response_lock
210
-
211
- end
212
- }
213
- end
214
- return res
215
- end
216
-
217
- public
218
- # TCPNode initializer
219
- # @param [Hash] args the options to create the tcp node with
220
- # @option args [String] :host the hostname/ip which to listen on
221
- # @option args [Integer] :port the port which to listen on
222
- def initialize(args = {})
223
- super(args)
224
- @host = args[:host]
225
- @port = args[:port]
226
-
227
- @connections = []
228
- @connections_lock = Mutex.new
229
-
230
- @response_lock = Mutex.new
231
- @response_cv = ConditionVariable.new
232
- @responses = []
233
-
234
- @connection_event_handlers = {:closed => [], :error => []}
235
- end
236
-
237
- # Register connection event handler
238
- # @param [:error, :close] event the event to register the handler for
239
- # @param [Callable] handler block param to be added to array of handlers that are called when event occurs
240
- # @yield [TCPNode] self is passed to each registered handler when event occurs
241
- def on(event, &handler)
242
- if @connection_event_handlers.keys.include?(event)
243
- @connection_event_handlers[event] << handler
244
- end
245
- end
246
-
247
- # Instruct Node to start listening for and dispatching rpc requests
248
- #
249
- # Implementation of {RJR::Node#listen}
250
- def listen
251
- em_run {
252
- EventMachine::start_server @host, @port, TCPNodeEndpoint, { :rjr_node => self }
253
- }
254
- end
255
-
256
- # Instructs node to send rpc request, and wait for / return response.
257
- #
258
- # Do not invoke directly from em event loop or callback as will block the message
259
- # subscription used to receive responses
260
- #
261
- # @param [String] uri location of node to send request to, should be
262
- # in format of jsonrpc://hostname:port
263
- # @param [String] rpc_method json-rpc method to invoke on destination
264
- # @param [Array] args array of arguments to convert to json and invoke remote method wtih
265
- def invoke_request(uri, rpc_method, *args)
266
- uri = URI.parse(uri)
267
- host,port = uri.host, uri.port
268
-
269
- message = RequestMessage.new :method => rpc_method,
270
- :args => args,
271
- :headers => @message_headers
272
- em_run{
273
- init_node(:host => host, :port => port,
274
- :rjr_node => self) { |c|
275
- c.safe_send message.to_s
276
- }
277
- }
278
-
279
- # TODO optional timeout for response ?
280
- result = wait_for_result(message)
281
-
282
- if result.size > 2
283
- raise Exception, result[2]
284
- end
285
- return result[1]
286
- end
287
-
288
- # Instructs node to send rpc notification (immadiately returns / no response is generated)
289
- #
290
- # @param [String] uri location of node to send notification to, should be
291
- # in format of jsonrpc://hostname:port
292
- # @param [String] rpc_method json-rpc method to invoke on destination
293
- # @param [Array] args array of arguments to convert to json and invoke remote method wtih
294
- def send_notification(uri, rpc_method, *args)
295
- # will block until message is published
296
- published_l = Mutex.new
297
- published_c = ConditionVariable.new
298
-
299
- uri = URI.parse(uri)
300
- host,port = uri.host, uri.port
301
-
302
- invoked = false
303
- conn = nil
304
- message = NotificationMessage.new :method => rpc_method,
305
- :args => args,
306
- :headers => @message_headers
307
- em_run{
308
- init_node(:host => host, :port => port,
309
- :rjr_node => self) { |c|
310
- conn = c
311
- c.safe_send message.to_s
312
- # XXX big bug w/ tcp node, this should be invoked only when
313
- # we are sure event machine sent message
314
- published_l.synchronize { invoked = true ; published_c.signal }
315
- }
316
- }
317
- published_l.synchronize { published_c.wait published_l unless invoked }
318
- #sleep 0.01 until conn.get_outbound_data_size == 0
319
- nil
320
- end
321
- end
322
-
323
- end # module RJR
@@ -1,272 +0,0 @@
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
- # TODO option to increase worker threads if work queue gets saturated
217
- @work_queue.push work
218
- end
219
-
220
- # Terminate the thread pool, stopping all worker threads
221
- def stop
222
- @pool_lock.synchronize {
223
- @terminate = true
224
-
225
- # wakeup management thread so it can kill workers
226
- # before terminating on its own
227
- begin
228
- @manager_thread.wakeup
229
-
230
- # incase thread wakes up / terminates on its own
231
- rescue ThreadError
232
-
233
- end
234
- }
235
- join
236
- end
237
-
238
- # Block until all worker threads have finished executing
239
- def join
240
- #@pool_lock.synchronize { @worker_threads.each { |t| t.join unless @terminate } }
241
- th = nil
242
- @pool_lock.synchronize { th = @manager_thread if @manager_thread }
243
- th.join if th
244
- end
245
- end
246
-
247
- # Providers an interface to access a shared thread pool.
248
- #
249
- # Thread pool operations may be invoked on this class after
250
- # the 'init' method is called
251
- #
252
- # ThreadPool2Manager.init
253
- # ThreadPool2Manager << ThreadPool2Job(:foo) { "do something" }
254
- class ThreadPool2Manager
255
- # Initialize thread pool if it doesn't exist
256
- def self.init(num_threads, params = {})
257
- if @thread_pool.nil?
258
- @thread_pool = ThreadPool2.new(num_threads, params)
259
- end
260
- @thread_pool.start
261
- end
262
-
263
- # Return shared thread pool
264
- def self.thread_pool
265
- @thread_pool
266
- end
267
-
268
- # Delegates all methods invoked on calls to thread pool
269
- def self.method_missing(method_id, *args, &bl)
270
- @thread_pool.send method_id, *args, &bl
271
- end
272
- end