workers 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -19,11 +19,29 @@ Or install it yourself as:
19
19
 
20
20
  $ gem install workers
21
21
 
22
+ ## Parallel Map
23
+
24
+ Parallel map is the simplest way to get started with the Workers gem.
25
+ It is similar to Ruby's built in Array#map method except each element is mapped in parallel.
26
+
27
+ Workers.map([1, 2, 3, 4, 5]) { |i| i * i }
28
+
29
+ Any exceptions while mapping with cause the entire map method to fail.
30
+ If your block is prone to temporary failures (exceptions), you can retry it.
31
+
32
+ Workers.map([1, 2, 3, 4, 5], :max_tries => 100) do |i|
33
+ if rand <= 0.5
34
+ puts "Sometimes I like to fail while computing #{i} * #{i}."
35
+ raise 'sad face'
36
+ end
37
+
38
+ i * i
39
+ end
40
+
22
41
  ## Tasks
23
42
 
24
- Tasks and task groups build on top of pools of worker threads.
25
- They provide a means of parallelizing expensive computations, collecing results, and handling exceptions.
26
- These are the classes you normally work with in your application level code because they remove boiletplate.
43
+ Tasks and task groups provide more flexibility than parallel map.
44
+ The main benefit is that you get to decide how you want to handle exceptions.
27
45
 
28
46
  # Create a task group (it contains a pool of worker threads).
29
47
  group = Workers::TaskGroup.new
@@ -31,7 +49,7 @@ These are the classes you normally work with in your application level code beca
31
49
  # Add tasks to the group.
32
50
  10.times do |i|
33
51
  10.times do |j|
34
- group.add(i, j) do
52
+ group.add do
35
53
  group.synchronize { puts "Computing #{i} * #{j}..." }
36
54
  i * j # Last statement executed is the result of the task.
37
55
  end
@@ -63,25 +81,26 @@ These are the classes you normally work with in your application level code beca
63
81
  Note that instances of TaskGroup provide a 'synchronize' method.
64
82
  This method uses a mutex so you can serialize portions of your tasks that aren't thread safe.
65
83
 
66
- ## Parallel Map
67
-
68
- Parallel Map is syntactic sugar for tasks and task groups:
69
-
70
- group = Workers::TaskGroup.new
71
- group.map([1,2,3,4,5]) { |i| i * i }
84
+ #### Options
72
85
 
73
- The above syntax is equivalent to the below:
86
+ group = Workers::TaskGroup.new(
87
+ :logger => nil, # Ruby logger instance.
88
+ :pool => Workers.pool # The workers pool used to execute timer callbacks.
89
+ )
74
90
 
75
- Workers.map([1, 2, 3, 4, 5]) { |i| i * i }
91
+ task = Workers::Task.new(
92
+ :logger => nil, # Ruby logger instance.
93
+ :perform => proc {}, # Required option. Block of code to run.
94
+ :args => [], # Array of arguments passed to the 'perform' block.
95
+ :finished => nil, # Callback to execute after attempting to run the task.
96
+ :max_tries => 1, # Number of times to try completing the task (without an exception).
97
+ )
76
98
 
77
- Note that any task failures (exceptions) will cause an exception to be raised.
78
- This means that parallel map is "all or nothing" where as tasks and task groups leave error processing up to you.
99
+ ## Workers
79
100
 
80
- ## Workers - Basic
101
+ #### Basic
81
102
 
82
- There are many cases where tasks, task groups, and parallel map are too high-level.
83
- Fortunately you can use the lower level Worker class to solve these problems.
84
- The main purpose of the Worker class is to add an event system on top of the Thread class.
103
+ The main purpose of the Worker class is to add an event system on top of Ruby's built-in Thread class.
85
104
  This greatly simplifies inter-thread communication.
86
105
 
87
106
  # Initialize a worker pool.
@@ -103,7 +122,7 @@ This greatly simplifies inter-thread communication.
103
122
  # Wait for the workers to shutdown.
104
123
  pool.join
105
124
 
106
- ## Workers - Advanced
125
+ #### Advanced
107
126
 
108
127
  The Worker class is designed to be customized through inheritence and its event system:
109
128
 
@@ -138,6 +157,13 @@ The Worker class is designed to be customized through inheritence and its event
138
157
  Note that you can use custom workers without a pool.
139
158
  This effectively gives you direct access to a single event-driven thread.
140
159
 
160
+ #### Options
161
+
162
+ worker = Workers::Worker.new(
163
+ :logger => nil, # Ruby Logger instance.
164
+ :input_queue => nil # Ruby Queue used for input events.
165
+ )
166
+
141
167
  ## Pools
142
168
 
143
169
  As shown above, pools effectively allow a group of workers to share a work queue.
@@ -158,6 +184,14 @@ They have a few additional methods described below:
158
184
  # Resize the pool size to a specific value.
159
185
  pool.resize(20)
160
186
 
187
+ #### Options
188
+
189
+ pool = Workers::Pool.new(
190
+ :size => 20, # Number of threads to create.
191
+ :logger => nil, # Ruby Logger instance.
192
+ :worker_class => Workers::Worker # Class of worker to use for this pool.
193
+ )
194
+
161
195
  ## Timers
162
196
 
163
197
  Timers provide a way to execute code in the future:
@@ -178,6 +212,21 @@ Timers provide a way to execute code in the future:
178
212
  # Shutdown the timer.
179
213
  timer.cancel
180
214
 
215
+ #### Options
216
+
217
+ timer = Workers::Timer.new(1,
218
+ :logger => nil, # Ruby logger instance.
219
+ :repeat => false, # Repeat the timer until 'cancel' is called.
220
+ :scheduler => Workers.scheduler, # The scheduler that manages execution.
221
+ :callback => nil # The proc to execute (provide this or a block, but not both).
222
+ )
223
+
224
+ timer = Workers::PeriodicTimer.new(1,
225
+ :logger => nil, # Ruby logger instance.
226
+ :scheduler => Workers.scheduler, # The scheduler that manages execution.
227
+ :callback => nil # The proc to execute (provide this or a block, but not both).
228
+ )
229
+
181
230
  ## Schedulers
182
231
 
183
232
  Schedulers are what trigger Timers to fire.
@@ -201,43 +250,7 @@ You can create additional or custom ones as necessary:
201
250
  # Shutdown the scheduler.
202
251
  scheduler.dispose
203
252
 
204
- ## Options (defaults below):
205
-
206
- pool = Workers::Pool.new(
207
- :size => 20, # Number of threads to create.
208
- :logger => nil, # Ruby Logger instance.
209
- :worker_class => Workers::Worker # Class of worker to use for this pool.
210
- )
211
-
212
- worker = Workers::Worker.new(
213
- :logger => nil, # Ruby Logger instance.
214
- :input_queue => nil # Ruby Queue used for input events.
215
- )
216
-
217
- timer = Workers::Timer.new(1,
218
- :logger => nil, # Ruby logger instance.
219
- :repeat => false, # Repeat the timer until 'cancel' is called.
220
- :scheduler => Workers.scheduler, # The scheduler that manages execution.
221
- :callback => nil # The proc to execute (provide this or a block, but not both).
222
- )
223
-
224
- group = Workers::TaskGroup.new(
225
- :logger => nil, # Ruby logger instance.
226
- :pool => Workers.pool # The workers pool used to execute timer callbacks.
227
- )
228
-
229
- task = Workers::Task.new(
230
- :logger => nil, # Ruby logger instance.
231
- :perform => proc {}, # Required option. Block of code to run.
232
- :args => [], # Array of arguments passed to the 'perform' block.
233
- :finished => nil, # Callback to execute after attempting to run the task.
234
- )
235
-
236
- timer = Workers::PeriodicTimer.new(1,
237
- :logger => nil, # Ruby logger instance.
238
- :scheduler => Workers.scheduler, # The scheduler that manages execution.
239
- :callback => nil # The proc to execute (provide this or a block, but not both).
240
- )
253
+ #### Options
241
254
 
242
255
  scheduler = Workers::Scheduler.new(
243
256
  :logger => nil, # Ruby logger instance.
data/lib/workers/task.rb CHANGED
@@ -12,7 +12,11 @@ module Workers
12
12
  @args = options[:args] || []
13
13
  @perform = options[:perform] || raise('Perform callback is required.')
14
14
  @finished = options[:finished]
15
+ @max_tries = options[:max_tries] || 1
15
16
  @state = :initialized
17
+ @tries = 0
18
+
19
+ raise 'max_tries must be >= 1' unless @max_tries >= 1
16
20
 
17
21
  return nil
18
22
  end
@@ -22,12 +26,17 @@ module Workers
22
26
 
23
27
  @state = :running
24
28
 
25
- begin
26
- @result = @perform.call(*@args)
27
- @state = :succeeded
28
- rescue Exception => e
29
- @state = :failed
30
- @exception = e
29
+ while(@tries < @max_tries && @state != :succeeded)
30
+ @tries += 1
31
+
32
+ begin
33
+ @result = @perform.call(*@args)
34
+ @state = :succeeded
35
+ @exception = nil
36
+ rescue Exception => e
37
+ @state = :failed
38
+ @exception = e
39
+ end
31
40
  end
32
41
 
33
42
  @finished.call(self)
@@ -18,14 +18,13 @@ module Workers
18
18
  return nil
19
19
  end
20
20
 
21
- def add(*args, &block)
21
+ def add(options = {}, &block)
22
22
  state!(:initialized)
23
23
 
24
- if args[0].is_a?(Workers::Task)
25
- @tasks << args[0]
26
- else
27
- @tasks << Workers::Task.new(:args => args, :perform => block, :finished => method(:finished))
28
- end
24
+ options[:finished] = method(:finished)
25
+ options[:perform] ||= block
26
+
27
+ @tasks << Workers::Task.new(options)
29
28
 
30
29
  return nil
31
30
  end
@@ -57,9 +56,9 @@ module Workers
57
56
  return @tasks.select { |t| t.failed? }
58
57
  end
59
58
 
60
- def map(inputs, &block)
59
+ def map(inputs, options = {}, &block)
61
60
  inputs.each do |input|
62
- add(input) do |i|
61
+ add(:args => input, :max_tries => options[:max_tries]) do |i|
63
62
  block.call(i)
64
63
  end
65
64
  end
@@ -1,3 +1,3 @@
1
1
  module Workers
2
- VERSION = '0.1.4'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/workers.rb CHANGED
@@ -33,9 +33,9 @@ module Workers
33
33
  @scheduler = val
34
34
  end
35
35
 
36
- def self.map(vals, &block)
37
- return Workers::TaskGroup.new.map(vals) do |v|
38
- block.call(v)
36
+ def self.map(inputs, options = {}, &block)
37
+ return Workers::TaskGroup.new.map(inputs, options) do |i|
38
+ block.call(i)
39
39
  end
40
40
  end
41
41
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: workers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: