workers 0.1.4 → 0.2.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.
- data/README.md +69 -56
- data/lib/workers/task.rb +15 -6
- data/lib/workers/task_group.rb +7 -8
- data/lib/workers/version.rb +1 -1
- data/lib/workers.rb +3 -3
- metadata +1 -1
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
|
25
|
-
|
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
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
101
|
+
#### Basic
|
81
102
|
|
82
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
26
|
-
@
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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)
|
data/lib/workers/task_group.rb
CHANGED
@@ -18,14 +18,13 @@ module Workers
|
|
18
18
|
return nil
|
19
19
|
end
|
20
20
|
|
21
|
-
def add(
|
21
|
+
def add(options = {}, &block)
|
22
22
|
state!(:initialized)
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
data/lib/workers/version.rb
CHANGED
data/lib/workers.rb
CHANGED
@@ -33,9 +33,9 @@ module Workers
|
|
33
33
|
@scheduler = val
|
34
34
|
end
|
35
35
|
|
36
|
-
def self.map(
|
37
|
-
return Workers::TaskGroup.new.map(
|
38
|
-
block.call(
|
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
|