workers 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e3fff7e649a2c1b3cb5c79855466ffc591a1e89e
4
- data.tar.gz: 65bbb33f63f9427a8c6b58961834e44baeac1334
3
+ metadata.gz: 50e412eae6d1bdf77fe6588ffc6a8d2412f739f7
4
+ data.tar.gz: ab20ac26b42dd524faa2d0d1171e6887de2e3bbf
5
5
  SHA512:
6
- metadata.gz: 3324a9047a862ce798de1b78966ca4526b690fdee1ea17933d2cfa4f3c74f9abf9e63eaec91181aa48954b3da213fc94123e4dab52275029bd7859d7d62aaad7
7
- data.tar.gz: e8b7a6b839014c1771dbc23c6fba4488c74338fef156bb2d272cb0718e0a51bf456553a4e98b880f89913a2607cea4411a4e197b2cce3ac146fe68ee0501a373
6
+ metadata.gz: d22b5c5fb81b2b5b7105c1d237b2d9b779af7b2ef9cad993cd80c9b012a851390d851ec976497f51d87a7cbc2265d31e3712c4d2b2d2572da65c7ed8fa9fbec7
7
+ data.tar.gz: 3c44a7b8cc2e9943e919cb02e231f2d25511b5b24a4df1be847ef7e9c3b7ae083d07d35b3f1f99c25d5e8784745953300301ebecce7b67c155de3182ca6ecd94
data/README.md CHANGED
@@ -42,7 +42,7 @@ Any exceptions while mapping with cause the entire map method to fail.
42
42
  If your block is prone to temporary failures (exceptions), you can retry it.
43
43
 
44
44
  Workers.map([1, 2, 3, 4, 5], :max_tries => 100) do |i|
45
- if rand <= 0.5
45
+ if rand <= 0.8
46
46
  puts "Sometimes I like to fail while computing #{i} * #{i}."
47
47
  raise 'sad face'
48
48
  end
@@ -53,7 +53,7 @@ If your block is prone to temporary failures (exceptions), you can retry it.
53
53
  ## Tasks
54
54
 
55
55
  Tasks and task groups provide more flexibility than parallel map.
56
- The main benefit is that you get to decide how you want to handle exceptions.
56
+ For example, you get to decide how you want to handle exceptions.
57
57
 
58
58
  # Create a task group (it contains a pool of worker threads).
59
59
  group = Workers::TaskGroup.new
@@ -61,8 +61,12 @@ The main benefit is that you get to decide how you want to handle exceptions.
61
61
  # Add tasks to the group.
62
62
  10.times do |i|
63
63
  10.times do |j|
64
- group.add do
64
+ group.add(:max_tries => 10) do
65
65
  group.synchronize { puts "Computing #{i} * #{j}..." }
66
+ if rand <= 0.9
67
+ group.synchronize { puts "Sometimes I like to fail while computing #{i} * #{i}." }
68
+ raise 'sad face'
69
+ end
66
70
  i * j # Last statement executed is the result of the task.
67
71
  end
68
72
  end
@@ -104,9 +108,9 @@ This method uses a mutex so you can serialize portions of your tasks that aren't
104
108
 
105
109
  task = Workers::Task.new(
106
110
  :logger => nil, # Ruby logger instance.
107
- :perform => proc {}, # Required option. Block of code to run.
111
+ :on_perform => proc {}, # Required option. Block of code to run.
108
112
  :args => [], # Array of arguments passed to the 'perform' block.
109
- :finished => nil, # Callback to execute after attempting to run the task.
113
+ :on_finished => nil, # Callback to execute after attempting to run the task.
110
114
  :max_tries => 1, # Number of times to try completing the task (without an exception).
111
115
  )
112
116
 
@@ -116,32 +120,27 @@ This method uses a mutex so you can serialize portions of your tasks that aren't
116
120
 
117
121
  The main purpose of the Worker class is to add an event system on top of Ruby's built-in Thread class.
118
122
  This greatly simplifies inter-thread communication.
119
- Workers are fairly low level and don't handle exceptions for you.
120
- Failing to handle exceptions will result in dead workers so you must rescue them in your application code.
123
+ You must manually dispose of pools and workers so they get garbage collected.
121
124
 
122
125
  # Initialize a worker pool.
123
- pool = Workers::Pool.new
126
+ pool = Workers::Pool.new(:on_exception => proc { |e|
127
+ puts "A worker encountered an exception: #{e.class}: #{e.message}"
128
+ })
124
129
 
125
130
  # Perform some work in the background.
126
131
  100.times do
127
132
  pool.perform do
128
- begin
129
- sleep(rand(3))
130
- puts "Hello world from thread #{Thread.current.object_id}"
131
- rescue Exception => e
132
- puts "Oh no, my hello world failed!"
133
- end
133
+ sleep(rand(3))
134
+ raise 'sad face' if rand < 0.5
135
+ puts "Hello world from thread #{Thread.current.object_id}"
134
136
  end
135
137
  end
136
138
 
137
- # Tell the workers to shutdown.
138
- pool.shutdown do
139
+ # Wait up to 30 seconds for the workers to cleanly shutdown (or forcefully kill them).
140
+ pool.dispose(30) do
139
141
  puts "Worker thread #{Thread.current.object_id} is shutting down."
140
142
  end
141
143
 
142
- # Wait for the workers to shutdown.
143
- pool.join
144
-
145
144
  #### Advanced
146
145
 
147
146
  The Worker class is designed to be customized through inheritence and its event system:
@@ -153,12 +152,8 @@ The Worker class is designed to be customized through inheritence and its event
153
152
  def event_handler(event)
154
153
  case event.command
155
154
  when :my_custom
156
- begin
157
- puts "Worker received custom event: #{event.data}"
158
- sleep(1)
159
- rescue Exception => e
160
- puts "This is a very sad program."
161
- end
155
+ puts "Worker received custom event: #{event.data}"
156
+ sleep(1)
162
157
  else
163
158
  super(event)
164
159
  end
@@ -166,27 +161,25 @@ The Worker class is designed to be customized through inheritence and its event
166
161
  end
167
162
 
168
163
  # Create a pool that uses your custom worker class.
169
- pool = Workers::Pool.new(:worker_class => CustomWorker)
164
+ pool = Workers::Pool.new(:worker_class => CustomWorker, :on_exception => proc { |e|
165
+ puts "A worker encountered an exception: #{e.class}: e.message}"
166
+ })
170
167
 
171
168
  # Tell the workers to do some work using custom events.
172
169
  100.times do |i|
173
170
  pool.enqueue(:my_custom, i)
174
171
  end
175
172
 
176
- # Tell the workers to shutdown.
177
- pool.shutdown do
173
+ # Wait up to 30 seconds for the workers to cleanly shutdown (or forcefully kill them).
174
+ pool.dispose(30) do
178
175
  puts "Worker thread #{Thread.current.object_id} is shutting down."
179
176
  end
180
177
 
181
- # Wait for the workers to shutdown.
182
- pool.join
183
-
184
178
  #### Without pools
185
179
 
186
180
  In most cases you will be using a group of workers (a pool) as demonstrated above.
187
181
  In certain cases, you may want to use a worker directly without the pool.
188
- This effectively gives you direct access to a single event-driven thread.
189
- Note that you must handle exceptions yourself since you are working directly with the worker class:
182
+ This gives you direct access to a single event-driven thread that won't die on an exception.
190
183
 
191
184
  # Create a single worker.
192
185
  worker = Workers::Worker.new
@@ -194,23 +187,20 @@ Note that you must handle exceptions yourself since you are working directly wit
194
187
  # Perform some work in the background.
195
188
  25.times do |i|
196
189
  worker.perform do
197
- begin
198
- sleep(0.1)
199
- puts "Hello world from thread #{Thread.current.object_id}"
200
- rescue Exception => e
201
- puts "Oh no, my hello world failed!"
202
- end
190
+ sleep(0.1)
191
+ puts "Hello world from thread #{Thread.current.object_id}"
203
192
  end
204
193
  end
205
194
 
206
- # Tell the worker to shutdown.
207
- worker.shutdown
195
+ # Wait up to 30 seconds for the worker to cleanly shutdown (or forcefully kill it).
196
+ worker.dispose(30)
208
197
 
209
198
  #### Options
210
199
 
211
200
  worker = Workers::Worker.new(
212
201
  :logger => nil, # Ruby Logger instance.
213
- :input_queue => nil # Ruby Queue used for input events.
202
+ :input_queue => nil, # Ruby Queue used for input events.
203
+ :on_exception => nil # Callback to execute on exception (exception passed as only argument).
214
204
  )
215
205
 
216
206
  ## Pools
@@ -240,6 +230,7 @@ Pools can be adjusted using the below methods:
240
230
  :size => 20, # Number of threads to create.
241
231
  :logger => nil, # Ruby Logger instance.
242
232
  :worker_class => Workers::Worker # Class of worker to use for this pool.
233
+ :on_exception => nil # Callback to execute on exception (exception passed as only argument).
243
234
  )
244
235
 
245
236
  ## Timers
@@ -346,7 +337,7 @@ Threads performing such IO will temporarily release the GIL and thus let another
346
337
 
347
338
  #### MRI 1.8.x or older (not supported)
348
339
 
349
- These old versions of Ruby used green threads (application layer threads) instead of operating system level threads.
340
+ These old versions of Ruby use green threads (application layer threads) instead of operating system level threads.
350
341
  I recommend you upgrade to a newer version as I haven't tested Workers with them.
351
342
  They also aren't officially supported by the Ruby community at this point.
352
343
 
@@ -4,6 +4,8 @@ module Workers
4
4
 
5
5
  DEFAULT_POOL_SIZE = 20
6
6
 
7
+ attr_accessor :on_exception
8
+
7
9
  def initialize(options = {})
8
10
  @logger = Workers::LogProxy.new(options[:logger])
9
11
  @worker_class = options[:worker_class] || Workers::Worker
@@ -11,7 +13,7 @@ module Workers
11
13
  @lock = Monitor.new
12
14
  @workers = Set.new
13
15
  @size = 0
14
- @exception_callback = options[:on_exception]
16
+ @on_exception = options[:on_exception]
15
17
 
16
18
  expand(options[:size] || Workers::Pool::DEFAULT_POOL_SIZE)
17
19
 
@@ -48,8 +50,11 @@ module Workers
48
50
  results
49
51
  end
50
52
 
51
- def dispose(max_wait = nil)
52
- shutdown
53
+ def dispose(max_wait = nil, &block)
54
+ shutdown do
55
+ block.call if block
56
+ end
57
+
53
58
  join(max_wait)
54
59
  end
55
60
 
@@ -66,8 +71,7 @@ module Workers
66
71
  def expand(count)
67
72
  @lock.synchronize do
68
73
  count.times do
69
- worker = @worker_class.new(:input_queue => @input_queue, :die_on_exception => false,
70
- :on_exception => @exception_callback, :logger => @logger)
74
+ worker = @worker_class.new(:input_queue => @input_queue, :on_exception => @on_exception, :logger => @logger)
71
75
  @workers << worker
72
76
  @size += 1
73
77
  end
@@ -12,8 +12,8 @@ module Workers
12
12
  def initialize(options = {})
13
13
  @logger = Workers::LogProxy.new(options[:logger])
14
14
  @input = options[:input] || []
15
- @perform = options[:perform] || raise(Workers::MissingCallbackError, 'Perform callback is required.')
16
- @finished = options[:finished]
15
+ @on_perform = options[:on_perform] || raise(Workers::MissingCallbackError, 'on_perform callback is required.')
16
+ @on_finished = options[:on_finished]
17
17
  @max_tries = options[:max_tries] || 1
18
18
  @state = :initialized
19
19
  @tries = 0
@@ -32,7 +32,7 @@ module Workers
32
32
  @tries += 1
33
33
 
34
34
  begin
35
- @result = @perform.call(@input)
35
+ @result = @on_perform.call(@input)
36
36
  @state = :succeeded
37
37
  @exception = nil
38
38
  rescue Exception => e
@@ -41,7 +41,7 @@ module Workers
41
41
  end
42
42
  end
43
43
 
44
- @finished.call(self)
44
+ @on_finished.call(self)
45
45
 
46
46
  nil
47
47
  end
@@ -21,8 +21,8 @@ module Workers
21
21
  def add(options = {}, &block)
22
22
  state!(:initialized)
23
23
 
24
- options[:finished] = method(:finished)
25
- options[:perform] ||= block
24
+ options[:on_finished] = method(:finished)
25
+ options[:on_perform] ||= block
26
26
 
27
27
  @tasks << Workers::Task.new(options)
28
28
 
@@ -67,10 +67,11 @@ module Workers
67
67
 
68
68
  if (failure = failures[0])
69
69
  a = failure.input.inspect
70
+ c = failure.exception.class.to_s
70
71
  m = failure.exception.message
71
72
  b = failure.exception.backtrace.join("\n")
72
73
 
73
- raise Workers::FailedTaskError, "At least one task failed. ARGS=#{a}, TRACE=#{m}\n#{b}\n----------\n"
74
+ raise Workers::FailedTaskError, "#{failures.count} task(s) failed (Only the first failure is shown).\nARGS=#{a}, EXCEPTION=#{c}: #{m}\n#{b}\n----------\n"
74
75
  end
75
76
 
76
77
  tasks.map { |t| t.result }
@@ -1,3 +1,3 @@
1
1
  module Workers
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
@@ -3,13 +3,13 @@ module Workers
3
3
  include Workers::Helpers
4
4
 
5
5
  attr_accessor :exception
6
+ attr_accessor :on_exception
6
7
 
7
8
  def initialize(options = {})
8
9
  @logger = Workers::LogProxy.new(options[:logger])
9
10
  @input_queue = options[:input_queue] || Queue.new
10
11
  @thread = Thread.new { start_event_loop }
11
- @exception_callback = options[:on_exception]
12
- @die_on_exception = options.include?(:die_on_exception) ? options[:die_on_exception] : true
12
+ @on_exception = options[:on_exception]
13
13
  @run = true
14
14
 
15
15
  nil
@@ -105,8 +105,12 @@ module Workers
105
105
 
106
106
  def exception_handler(e)
107
107
  @exception = e
108
- @exception_callback.call(e) if @exception_callback
109
- raise(e) if @die_on_exception
108
+
109
+ begin
110
+ @on_exception.call(e) if @on_exception
111
+ rescue Exception => e2
112
+ STDERR.puts "Your #{self.class.to_s} exception handler raised an error. Fix your handler!\n#{e2.class.to_s}: #{e2.message}\n#{e2.backtrace.join("\n")}"
113
+ end
110
114
 
111
115
  nil
112
116
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: workers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chad Remesch
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-09-14 00:00:00.000000000 Z
11
+ date: 2015-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler