dat-worker-pool 0.5.0 → 0.6.0

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.
@@ -0,0 +1,60 @@
1
+ require 'thread'
2
+
3
+ class DatWorkerPool
4
+
5
+ class LockedObject
6
+ attr_reader :mutex
7
+
8
+ def initialize(object = nil)
9
+ @object = object
10
+ @mutex = Mutex.new
11
+ end
12
+
13
+ def value
14
+ @mutex.synchronize{ @object }
15
+ end
16
+
17
+ def set(new_object)
18
+ @mutex.synchronize{ @object = new_object }
19
+ end
20
+
21
+ def with_lock(&block)
22
+ @mutex.synchronize{ block.call(@mutex, @object) }
23
+ end
24
+
25
+ end
26
+
27
+ class LockedArray < LockedObject
28
+ def initialize(array = nil)
29
+ super(array || [])
30
+ end
31
+
32
+ alias :values :value
33
+
34
+ def first; @mutex.synchronize{ @object.first }; end
35
+ def last; @mutex.synchronize{ @object.last }; end
36
+ def size; @mutex.synchronize{ @object.size }; end
37
+ def empty?; @mutex.synchronize{ @object.empty? }; end
38
+
39
+ def push(new_item); @mutex.synchronize{ @object.push(new_item) }; end
40
+ def pop; @mutex.synchronize{ @object.pop }; end
41
+
42
+ def shift; @mutex.synchronize{ @object.shift }; end
43
+ def unshift(new_item); @mutex.synchronize{ @object.unshift(new_item) }; end
44
+
45
+ def delete(item); @mutex.synchronize{ @object.delete(item) }; end
46
+ end
47
+
48
+ class LockedSet < LockedObject
49
+ def initialize; super(Set.new); end
50
+
51
+ alias :values :value
52
+
53
+ def size; @mutex.synchronize{ @object.size }; end
54
+ def empty?; @mutex.synchronize{ @object.empty? }; end
55
+
56
+ def add(item); @mutex.synchronize{ @object.add(item) }; end
57
+ def remove(item); @mutex.synchronize{ @object.delete(item) }; end
58
+ end
59
+
60
+ end
@@ -1,65 +1,88 @@
1
- require 'thread'
2
-
3
1
  class DatWorkerPool
4
2
 
5
- class Queue
3
+ module Queue
6
4
 
7
- attr_accessor :on_push_callbacks, :on_pop_callbacks
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ include InstanceMethods
8
+ end
9
+ end
8
10
 
9
- def initialize
10
- @work_items = []
11
- @shutdown = false
12
- @mutex = Mutex.new
13
- @condition_variable = ConditionVariable.new
11
+ module InstanceMethods
14
12
 
15
- @on_pop_callbacks = []
16
- @on_push_callbacks = []
17
- end
13
+ # overwrite this method to add custom logic for reading the current work
14
+ # items on the queue
15
+ def work_items
16
+ raise NotImplementedError
17
+ end
18
18
 
19
- def start
20
- @shutdown = false
21
- end
19
+ def dwp_start
20
+ @dwp_running = true
21
+ start!
22
+ end
22
23
 
23
- # * Wakes up any threads (`@condition_variable.broadcast`) who are sleeping
24
- # because of `pop`.
25
- def shutdown
26
- @shutdown = true
27
- @mutex.synchronize{ @condition_variable.broadcast }
28
- end
24
+ def dwp_signal_shutdown
25
+ @dwp_running = false
26
+ end
29
27
 
30
- # * Add the work and wake up the first thread waiting from calling `pop`
31
- # (`@condition_variable.signal`).
32
- def push(work_item)
33
- raise "Unable to add work while shutting down" if @shutdown
34
- @mutex.synchronize do
35
- @work_items << work_item
36
- @condition_variable.signal
28
+ def dwp_shutdown
29
+ self.dwp_signal_shutdown
30
+ shutdown!
37
31
  end
38
- @on_push_callbacks.each(&:call)
39
- end
40
32
 
41
- # * Sleeps the current thread (`@condition_variable.wait(@mutex)`) until it
42
- # is signaled via `push` or `shutdown`.
43
- def pop
44
- return if @shutdown
45
- item = @mutex.synchronize do
46
- @condition_variable.wait(@mutex) while !@shutdown && @work_items.empty?
47
- @work_items.shift
33
+ def running?
34
+ !!@dwp_running
48
35
  end
49
- @on_pop_callbacks.each(&:call)
50
- item
51
- end
52
36
 
53
- def work_items
54
- @mutex.synchronize{ @work_items }
55
- end
37
+ def shutdown?
38
+ !self.running?
39
+ end
56
40
 
57
- def empty?
58
- @mutex.synchronize{ @work_items.empty? }
59
- end
41
+ def dwp_push(*args)
42
+ raise "Unable to add work when shut down" if self.shutdown?
43
+ push!(*args)
44
+ end
45
+
46
+ def dwp_pop
47
+ return if self.shutdown?
48
+ pop!
49
+ end
50
+
51
+ private
52
+
53
+ # overwrite this method to add custom start logic; this is a no-op by
54
+ # default because we don't require a queue to have custom start logic
55
+ def start!; end
56
+
57
+ # overwrite this method to add custom shutdown logic; this is a no-op by
58
+ # default because we don't require a queue to have custom shutdown logic;
59
+ # more than likely you will want to use this to "wakeup" worker threads
60
+ # that are sleeping waiting to pop work from the queue (see the default
61
+ # queue for an example using mutexes and condition variables)
62
+ def shutdown!; end
63
+
64
+ # overwrite this method to add custom push logic; this doesn't have to be
65
+ # overwritten but if it isn't, you will not be able to add work items using
66
+ # the queue (and the `add_work` method on `DatWorkerPool` will not work);
67
+ # more than likely this should add work to the queue and "signal" the
68
+ # workers so they know to process it (see the default queue for an example
69
+ # using mutexes and condition variables)
70
+ def push!(*args)
71
+ raise NotImplementedError
72
+ end
73
+
74
+ # overwrite this method to add custom pop logic; this has to be overwritten
75
+ # or the workers will not be able to get work that needs to be processed;
76
+ # this is intended to sleep the worker threads (see the default queue for an
77
+ # example using mutexes and condition variables); if this returns `nil` the
78
+ # workers will ignore it and go back to sleep, `nil` is not a valid work
79
+ # item to process; also check if the queue is shutdown when waking up
80
+ # workers, you probably don't want to hand-off work while everything is
81
+ # shutting down
82
+ def pop!
83
+ raise NotImplementedError
84
+ end
60
85
 
61
- def shutdown?
62
- @shutdown
63
86
  end
64
87
 
65
88
  end
@@ -0,0 +1,196 @@
1
+ require 'set'
2
+ require 'system_timer'
3
+ require 'dat-worker-pool'
4
+ require 'dat-worker-pool/locked_object'
5
+
6
+ class DatWorkerPool
7
+
8
+ class Runner
9
+
10
+ attr_reader :num_workers, :worker_class, :worker_params
11
+ attr_reader :logger_proxy, :queue
12
+
13
+ def initialize(args)
14
+ @num_workers = args[:num_workers]
15
+ @queue = args[:queue]
16
+ @worker_class = args[:worker_class]
17
+ @worker_params = args[:worker_params]
18
+
19
+ @logger_proxy = if args[:logger]
20
+ LoggerProxy.new(args[:logger])
21
+ else
22
+ NullLoggerProxy.new
23
+ end
24
+
25
+ @workers = LockedArray.new
26
+ @available_workers = LockedSet.new
27
+ end
28
+
29
+ def workers
30
+ @workers.values
31
+ end
32
+
33
+ def start
34
+ log{ "Starting worker pool with #{@num_workers} worker(s)" }
35
+ @queue.dwp_start
36
+ @num_workers.times.each{ |n| build_worker(n + 1) }
37
+ end
38
+
39
+ # the workers should be told to shutdown before the queue because the queue
40
+ # shutdown will wake them up; a worker popping on a shutdown queue will
41
+ # always get `nil` back and will loop as fast as allowed until its shutdown
42
+ # flag is flipped, so shutting down the workers then the queue keeps them
43
+ # from looping as fast as possible; if any kind of standard error or the
44
+ # expected timeout error (assuming the workers take too long to shutdown) is
45
+ # raised, force a shutdown; this ensures we shutdown as best as possible
46
+ # instead of letting ruby kill the threads when the process exits;
47
+ # non-timeout errors will be re-raised so they can be caught and handled (or
48
+ # shown when the process exits)
49
+ def shutdown(timeout = nil, backtrace = nil)
50
+ log do
51
+ timeout_message = timeout ? "#{timeout} second(s)" : "none"
52
+ "Shutting down worker pool (timeout: #{timeout_message})"
53
+ end
54
+ begin
55
+ @workers.with_lock{ |m, ws| ws.each(&:dwp_signal_shutdown) }
56
+ @queue.dwp_signal_shutdown
57
+ OptionalTimeout.new(timeout) do
58
+ @queue.dwp_shutdown
59
+ wait_for_workers_to_shutdown
60
+ end
61
+ rescue StandardError => exception
62
+ force_workers_to_shutdown(exception, timeout, backtrace)
63
+ raise exception
64
+ rescue TimeoutInterruptError => exception
65
+ force_workers_to_shutdown(exception, timeout, backtrace)
66
+ end
67
+ log{ "Finished shutting down" }
68
+ end
69
+
70
+ def available_worker_count
71
+ @available_workers.size
72
+ end
73
+
74
+ def worker_available?
75
+ self.available_worker_count > 0
76
+ end
77
+
78
+ def make_worker_available(worker)
79
+ @available_workers.add(worker.object_id)
80
+ end
81
+
82
+ def make_worker_unavailable(worker)
83
+ @available_workers.remove(worker.object_id)
84
+ end
85
+
86
+ def log(&message_block)
87
+ @logger_proxy.runner_log(&message_block)
88
+ end
89
+
90
+ def worker_log(worker, &message_block)
91
+ @logger_proxy.worker_log(worker, &message_block)
92
+ end
93
+
94
+ private
95
+
96
+ def build_worker(number)
97
+ @workers.push(@worker_class.new(self, @queue, number).tap(&:dwp_start))
98
+ end
99
+
100
+ # use an until loop instead of each to join all the workers, while we are
101
+ # joining a worker a different worker can shutdown and remove itself from
102
+ # the `@workers` array; rescue when joining the workers, ruby will raise any
103
+ # exceptions that aren't handled by a thread when its joined, this allows
104
+ # all the workers to be joined
105
+ def wait_for_workers_to_shutdown
106
+ log{ "Waiting for #{@workers.size} workers to shutdown" }
107
+ while !(worker = @workers.first).nil?
108
+ begin
109
+ worker.dwp_join
110
+ rescue StandardError => exception
111
+ log{ "An error occurred while waiting for worker " \
112
+ "to shutdown ##{worker.dwp_number}" }
113
+ end
114
+ remove_worker(worker)
115
+ log{ "Worker ##{worker.dwp_number} shutdown" }
116
+ end
117
+ end
118
+
119
+ # use an until loop instead of each to join all the workers, while we are
120
+ # joining a worker a different worker can shutdown and remove itself from
121
+ # the `@workers` array; rescue when joining the workers, ruby will raise any
122
+ # exceptions that aren't handled by a thread when its joined, this ensures
123
+ # if the hard shutdown is raised and not rescued (for example, in the
124
+ # workers ensure), then it won't cause the forced shutdown to end
125
+ # prematurely
126
+ def force_workers_to_shutdown(orig_exception, timeout, backtrace)
127
+ log{ "Forcing #{@workers.size} workers to shutdown" }
128
+ error = build_forced_shutdown_error(orig_exception, timeout, backtrace)
129
+ while !(worker = @workers.first).nil?
130
+ worker.dwp_raise(error)
131
+ begin
132
+ worker.dwp_join
133
+ rescue StandardError => exception
134
+ log{ "An error occurred while waiting for worker " \
135
+ "to shutdown ##{worker.dwp_number} (forced)" }
136
+ rescue ShutdownError
137
+ # these are expected (because we raised them in the thread) so they
138
+ # don't need to be logged
139
+ end
140
+ remove_worker(worker)
141
+ log{ "Worker ##{worker.dwp_number} shutdown (forced)" }
142
+ end
143
+ end
144
+
145
+ # make sure the worker has been removed from the available workers, in case
146
+ # it errored before it was able to make itself unavailable
147
+ def remove_worker(worker)
148
+ self.make_worker_unavailable(worker)
149
+ @workers.delete(worker)
150
+ end
151
+
152
+ def build_forced_shutdown_error(orig_exception, timeout, backtrace)
153
+ if orig_exception.kind_of?(TimeoutInterruptError)
154
+ ShutdownError.new("Timed out shutting down (#{timeout} seconds).").tap do |e|
155
+ e.set_backtrace(backtrace) if backtrace
156
+ end
157
+ else
158
+ ShutdownError.new("Errored while shutting down: #{orig_exception.inspect}").tap do |e|
159
+ e.set_backtrace(orig_exception.backtrace)
160
+ end
161
+ end
162
+ end
163
+
164
+ # this needs to be an `Interrupt` to be sure we don't accidentally catch it
165
+ # when rescueing exceptions; in the shutdown methods we rescue any errors
166
+ # from `worker.join`, this will also rescue the timeout error if its a
167
+ # standard error and will keep it from doing a forced shutdown
168
+ TimeoutInterruptError = Class.new(Interrupt)
169
+
170
+ module OptionalTimeout
171
+ def self.new(seconds, &block)
172
+ if seconds
173
+ SystemTimer.timeout(seconds, TimeoutInterruptError, &block)
174
+ else
175
+ block.call
176
+ end
177
+ end
178
+ end
179
+
180
+ class LoggerProxy < Struct.new(:logger)
181
+ def runner_log(&message_block)
182
+ self.logger.debug("[DWP] #{message_block.call}")
183
+ end
184
+ def worker_log(worker, &message_block)
185
+ self.logger.debug("[DWP-#{worker.dwp_number}] #{message_block.call}")
186
+ end
187
+ end
188
+
189
+ class NullLoggerProxy
190
+ def runner_log(&block); end
191
+ def worker_log(worker, &block); end
192
+ end
193
+
194
+ end
195
+
196
+ end
@@ -1,3 +1,3 @@
1
1
  class DatWorkerPool
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -1,91 +1,270 @@
1
1
  require 'thread'
2
- require 'dat-worker-pool'
2
+ require 'dat-worker-pool/runner'
3
3
 
4
4
  class DatWorkerPool
5
5
 
6
- class Worker
7
-
8
- attr_accessor :on_work, :on_error_callbacks
9
- attr_accessor :on_start_callbacks, :on_shutdown_callbacks
10
- attr_accessor :on_sleep_callbacks, :on_wakeup_callbacks
11
- attr_accessor :before_work_callbacks, :after_work_callbacks
12
-
13
- def initialize(queue)
14
- @queue = queue
15
- @on_work = proc{ |worker, work_item| }
16
- @on_error_callbacks = []
17
- @on_start_callbacks = []
18
- @on_shutdown_callbacks = []
19
- @on_sleep_callbacks = []
20
- @on_wakeup_callbacks = []
21
- @before_work_callbacks = []
22
- @after_work_callbacks = []
23
-
24
- @shutdown = false
25
- @thread = nil
26
- end
6
+ module Worker
27
7
 
28
- def start
29
- @thread ||= Thread.new{ work_loop }
8
+ def self.included(klass)
9
+ klass.class_eval do
10
+ extend ClassMethods
11
+ include InstanceMethods
12
+ end
30
13
  end
31
14
 
32
- def shutdown
33
- @shutdown = true
34
- end
15
+ module ClassMethods
35
16
 
36
- def running?
37
- !!(@thread && @thread.alive?)
38
- end
17
+ def on_start_callbacks; @on_start_callbacks ||= []; end
18
+ def on_shutdown_callbacks; @on_shutdown_callbacks ||= []; end
19
+ def on_available_callbacks; @on_available_callbacks ||= []; end
20
+ def on_unavailable_callbacks; @on_unavailable_callbacks ||= []; end
21
+ def on_error_callbacks; @on_error_callbacks ||= []; end
22
+ def before_work_callbacks; @before_work_callbacks ||= []; end
23
+ def after_work_callbacks; @after_work_callbacks ||= []; end
39
24
 
40
- def join(*args)
41
- @thread.join(*args) if running?
42
- end
25
+ def on_start(&block); self.on_start_callbacks << block; end
26
+ def on_shutdown(&block); self.on_shutdown_callbacks << block; end
27
+ def on_available(&block); self.on_available_callbacks << block; end
28
+ def on_unavailable(&block); self.on_unavailable_callbacks << block; end
29
+ def on_error(&block); self.on_error_callbacks << block; end
30
+ def before_work(&block); self.before_work_callbacks << block; end
31
+ def after_work(&block); self.after_work_callbacks << block; end
43
32
 
44
- def raise(*args)
45
- @thread.raise(*args) if running?
46
- end
33
+ def prepend_on_start(&block); self.on_start_callbacks.unshift(block); end
34
+ def prepend_on_shutdown(&block); self.on_shutdown_callbacks.unshift(block); end
35
+ def prepend_on_available(&block); self.on_available_callbacks.unshift(block); end
36
+ def prepend_on_unavailable(&block); self.on_unavailable_callbacks.unshift(block); end
37
+ def prepend_on_error(&block); self.on_error_callbacks.unshift(block); end
38
+ def prepend_before_work(&block); self.before_work_callbacks.unshift(block); end
39
+ def prepend_after_work(&block); self.after_work_callbacks.unshift(block); end
47
40
 
48
- private
49
-
50
- # * Rescue `ShutdownError` but don't do anything with it. We want to handle
51
- # the error but we just want it to cause the worker to exit its work loop.
52
- # If the `ShutdownError` isn't rescued, it will be raised when the worker
53
- # is joined.
54
- def work_loop
55
- @on_start_callbacks.each{ |p| p.call(self) }
56
- loop do
57
- break if @shutdown
58
- fetch_and_do_work
59
- end
60
- rescue ShutdownError
61
- ensure
62
- @on_shutdown_callbacks.each{ |p| p.call(self) }
63
- @thread = nil
64
41
  end
65
42
 
66
- # * Rescue `ShutdownError` but re-raise it after calling the error
67
- # callbacks. This ensures it causes the work loop to exit (see
68
- # `work_loop`).
69
- def fetch_and_do_work
70
- @on_sleep_callbacks.each{ |p| p.call(self) }
71
- work_item = @queue.pop
72
- @on_wakeup_callbacks.each{ |p| p.call(self) }
73
- do_work(work_item) if work_item
74
- rescue ShutdownError => exception
75
- handle_exception(exception, work_item)
76
- raise exception
77
- rescue StandardError => exception
78
- handle_exception(exception, work_item)
79
- end
43
+ module InstanceMethods
44
+
45
+ attr_reader :dwp_number
46
+
47
+ def initialize(runner, queue, number)
48
+ @dwp_runner, @dwp_queue, @dwp_number = runner, queue, number
49
+ @dwp_running = false
50
+ @dwp_thread = nil
51
+ end
52
+
53
+ def dwp_start
54
+ @dwp_running = true
55
+ @dwp_thread ||= Thread.new{ dwp_work_loop }
56
+ end
57
+
58
+ def dwp_signal_shutdown
59
+ @dwp_running = false
60
+ end
61
+
62
+ def dwp_running?
63
+ !!@dwp_running
64
+ end
65
+
66
+ def dwp_shutdown?
67
+ !self.dwp_running?
68
+ end
69
+
70
+ # this is needed because even if the running flag has been set to false
71
+ # (meaning the worker has been shutdown) the thread may still be alive
72
+ # because its `work` is taking a long time or its still trying to shut
73
+ # down
74
+ def dwp_thread_alive?
75
+ !!(@dwp_thread && @dwp_thread.alive?)
76
+ end
77
+
78
+ def dwp_join(*args)
79
+ @dwp_thread.join(*args) if self.dwp_thread_alive?
80
+ end
81
+
82
+ def dwp_raise(*args)
83
+ @dwp_thread.raise(*args) if self.dwp_thread_alive?
84
+ end
85
+
86
+ private
87
+
88
+ # Helpers
89
+ def number; @dwp_number; end
90
+ def params; @dwp_runner.worker_params; end
91
+ def queue; @dwp_runner.queue; end
92
+
93
+ # overwrite this method to add custom work logic; this has to be
94
+ # overwritten or the workers will not know how to handle a work item
95
+ def work!(work_item)
96
+ raise NotImplementedError
97
+ end
98
+
99
+ # rescue `ShutdownError` but re-raise it after calling the on-error
100
+ # callbacks, this ensures it causes the loop to exit
101
+ def dwp_work_loop
102
+ dwp_setup
103
+ while self.dwp_running?
104
+ begin
105
+ if !(work_item = queue.dwp_pop).nil?
106
+ begin
107
+ dwp_make_unavailable
108
+ dwp_work(work_item)
109
+ rescue ShutdownError => exception
110
+ dwp_handle_exception(exception, work_item)
111
+ Thread.current.raise exception
112
+ rescue StandardError => exception
113
+ dwp_handle_exception(exception, work_item)
114
+ ensure
115
+ dwp_make_available
116
+ end
117
+ end
118
+ rescue StandardError => exception
119
+ dwp_handle_exception(exception, work_item)
120
+ end
121
+ end
122
+ ensure
123
+ dwp_teardown
124
+ end
125
+
126
+ def dwp_setup
127
+ dwp_log{ "Starting" }
128
+ begin
129
+ dwp_run_callback 'on_start'
130
+ dwp_make_available
131
+ rescue StandardError => exception
132
+ dwp_handle_exception(exception)
133
+ Thread.current.raise exception
134
+ end
135
+ end
136
+
137
+ # this is a separate method so the test runner can call it individually
138
+ def dwp_make_unavailable
139
+ @dwp_runner.make_worker_unavailable(self)
140
+ dwp_run_callback 'on_unavailable'
141
+ dwp_log{ "Unavailable" }
142
+ end
143
+
144
+ # this is a separate method so the test runner can call it individually
145
+ def dwp_make_available
146
+ @dwp_runner.make_worker_available(self)
147
+ dwp_run_callback 'on_available'
148
+ dwp_log{ "Available" }
149
+ end
150
+
151
+ # this is a separate method so the test runner can call it individually
152
+ def dwp_work(work_item)
153
+ dwp_log{ "Working, item: #{work_item.inspect}" }
154
+ dwp_run_callback('before_work', work_item)
155
+ work!(work_item)
156
+ dwp_run_callback('after_work', work_item)
157
+ end
158
+
159
+ def dwp_teardown
160
+ begin
161
+ dwp_make_unavailable
162
+ dwp_run_callback 'on_shutdown'
163
+ rescue StandardError => exception
164
+ dwp_handle_exception(exception)
165
+ end
166
+ dwp_log{ "Shutdown" }
167
+ @dwp_running = false
168
+ @dwp_thread = nil
169
+ end
170
+
171
+ def dwp_handle_exception(exception, work_item = nil)
172
+ begin
173
+ dwp_log_exception(exception)
174
+ dwp_run_callback('on_error', exception, work_item)
175
+ rescue StandardError => on_error_exception
176
+ # errors while running on-error callbacks are logged but otherwise
177
+ # ignored to keep the worker from crashing, ideally these should be
178
+ # caught by the on-error callbacks themselves and never get here
179
+ dwp_log_exception(on_error_exception)
180
+ end
181
+ end
182
+
183
+ def dwp_run_callback(callback, *args)
184
+ (self.class.send("#{callback}_callbacks") || []).each do |callback|
185
+ self.instance_exec(*args, &callback)
186
+ end
187
+ end
188
+
189
+ def dwp_log(&message_block)
190
+ @dwp_runner.worker_log(self, &message_block)
191
+ end
192
+
193
+ def dwp_log_exception(exception)
194
+ dwp_log{ "#{exception.class}: #{exception.message}" }
195
+ (exception.backtrace || []).each{ |l| dwp_log{ l } }
196
+ end
80
197
 
81
- def do_work(work_item)
82
- @before_work_callbacks.each{ |p| p.call(self, work_item) }
83
- @on_work.call(self, work_item)
84
- @after_work_callbacks.each{ |p| p.call(self, work_item) }
85
198
  end
86
199
 
87
- def handle_exception(exception, work_item = nil)
88
- @on_error_callbacks.each{ |p| p.call(self, exception, work_item) }
200
+ module TestHelpers
201
+
202
+ def test_runner(worker_class, options = nil)
203
+ TestRunner.new(worker_class, options)
204
+ end
205
+
206
+ class TestRunner
207
+ attr_reader :worker_class, :worker
208
+ attr_reader :queue, :dwp_runner
209
+
210
+ def initialize(worker_class, options = nil)
211
+ @worker_class = worker_class
212
+
213
+ @queue = options[:queue] || begin
214
+ require 'dat-worker-pool/default_queue'
215
+ DatWorkerPool::DefaultQueue.new
216
+ end
217
+
218
+ @dwp_runner = DatWorkerPool::Runner.new({
219
+ :num_workers => MIN_WORKERS,
220
+ :logger => options[:logger],
221
+ :queue => @queue,
222
+ :worker_class => @worker_class,
223
+ :worker_params => options[:params]
224
+ })
225
+
226
+ @worker = worker_class.new(@dwp_runner, @queue, 1)
227
+ end
228
+
229
+ def run(work_item)
230
+ self.start
231
+ self.make_unavailable
232
+ self.work(work_item)
233
+ self.make_available
234
+ self.shutdown
235
+ end
236
+
237
+ def work(work_item)
238
+ self.worker.instance_eval{ dwp_work(work_item) }
239
+ end
240
+
241
+ def error(exception, work_item = nil)
242
+ run_callback('on_error', self.worker, exception, work_item)
243
+ end
244
+
245
+ def start
246
+ run_callback('on_start', self.worker)
247
+ end
248
+
249
+ def shutdown
250
+ run_callback('on_shutdown', self.worker)
251
+ end
252
+
253
+ def make_unavailable
254
+ self.worker.instance_eval{ dwp_make_unavailable }
255
+ end
256
+
257
+ def make_available
258
+ self.worker.instance_eval{ dwp_make_available }
259
+ end
260
+
261
+ private
262
+
263
+ def run_callback(callback, worker, *args)
264
+ self.worker.instance_eval{ dwp_run_callback(callback, *args) }
265
+ end
266
+ end
267
+
89
268
  end
90
269
 
91
270
  end