workers 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +71 -40
- data/lib/workers.rb +8 -0
- data/lib/workers/task.rb +46 -0
- data/lib/workers/task_group.rb +96 -0
- data/lib/workers/version.rb +1 -1
- metadata +4 -2
data/README.md
CHANGED
@@ -82,15 +82,15 @@ Timers provide a way to execute code in the future:
|
|
82
82
|
timer = Workers::Timer.new(1.5) do
|
83
83
|
puts 'Hello world'
|
84
84
|
end
|
85
|
-
|
85
|
+
|
86
86
|
# Create a periodic timer that loops infinitely or until 'cancel' is called.
|
87
87
|
timer = Workers::PeriodicTimer.new(1) do
|
88
88
|
puts 'Hello world many times'
|
89
89
|
end
|
90
|
-
|
90
|
+
|
91
91
|
# Let the timer print some lines.
|
92
92
|
sleep(5)
|
93
|
-
|
93
|
+
|
94
94
|
# Shutdown the timer.
|
95
95
|
timer.cancel
|
96
96
|
|
@@ -102,21 +102,73 @@ You can create additional or custom ones as necessary:
|
|
102
102
|
|
103
103
|
# Create a workers pool with a larger than default thread count (optional).
|
104
104
|
pool = Workers::Pool.new(:size => 100)
|
105
|
-
|
105
|
+
|
106
106
|
# Create a scheduler.
|
107
107
|
scheduler = Workers::Scheduler.new(:pool => pool)
|
108
|
-
|
108
|
+
|
109
109
|
# Create a timer that uses the above scheduler.
|
110
110
|
timer = Workers::Timer.new(1, :scheduler => scheduler) do
|
111
111
|
puts 'Hello world'
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
# Wait for the timer to fire.
|
115
115
|
sleep(5)
|
116
|
-
|
116
|
+
|
117
117
|
# Shutdown the scheduler.
|
118
118
|
scheduler.dispose
|
119
119
|
|
120
|
+
### Tasks
|
121
|
+
|
122
|
+
Tasks and task groups build on top of worker pools.
|
123
|
+
They provide a means of parallelizing expensive computations and collecing the results:
|
124
|
+
|
125
|
+
# Create a task group (it contains a pool of workers).
|
126
|
+
group = Workers::TaskGroup.new
|
127
|
+
|
128
|
+
# Add tasks to the group.
|
129
|
+
10.times do |i|
|
130
|
+
10.times do |j|
|
131
|
+
group.add(i, j) do
|
132
|
+
i * j # Last statement executed is the result of the task.
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Execute the tasks (blocks until the tasks complete).
|
138
|
+
# Returns true if all tasks succeed. False if any fail.
|
139
|
+
group.run
|
140
|
+
|
141
|
+
# Return an array of all the tasks.
|
142
|
+
group.tasks
|
143
|
+
|
144
|
+
# Return an array of the successful tasks.
|
145
|
+
group.successes
|
146
|
+
|
147
|
+
# Return an array of the failed tasks.
|
148
|
+
group.failures
|
149
|
+
|
150
|
+
# Review the results.
|
151
|
+
group.tasks.each do |t|
|
152
|
+
t.succeeded? # True or false (false if an exception occurred).
|
153
|
+
t.failed? # True or false (true if an exception occurred).
|
154
|
+
t.args # Input arguments (the value of i in this example).
|
155
|
+
t.result # Output value (the result of i * i in this example).
|
156
|
+
t.exception # The exception if one exists.
|
157
|
+
end
|
158
|
+
|
159
|
+
### Parallel Map
|
160
|
+
|
161
|
+
Task groups and tasks are the building blocks of parallel map:
|
162
|
+
|
163
|
+
group = Workers::TaskGroup.new
|
164
|
+
group.map([1,2,3,4,5]) { |i| i * i }
|
165
|
+
|
166
|
+
The below syntax is equivalent to the above:
|
167
|
+
|
168
|
+
Workers.map([1, 2, 3, 4, 5]) { |i| i * i }
|
169
|
+
|
170
|
+
Note that any failures will cause an exception to be raised.
|
171
|
+
|
120
172
|
## Options (defaults below):
|
121
173
|
|
122
174
|
pool = Workers::Pool.new(
|
@@ -136,50 +188,29 @@ You can create additional or custom ones as necessary:
|
|
136
188
|
:scheduler => Workers.scheduler, # The scheduler that manages execution.
|
137
189
|
:callback => nil # The proc to execute (provide this or a block, but not both).
|
138
190
|
)
|
139
|
-
|
191
|
+
|
140
192
|
timer = Workers::PeriodicTimer.new(1,
|
141
193
|
:logger => nil, # Ruby logger instance.
|
142
194
|
:scheduler => Workers.scheduler, # The scheduler that manages execution.
|
143
195
|
:callback => nil # The proc to execute (provide this or a block, but not both).
|
144
196
|
)
|
145
|
-
|
197
|
+
|
146
198
|
scheduler = Workers::Scheduler.new(
|
147
199
|
:logger => nil, # Ruby logger instance.
|
148
200
|
:pool => Workers::Pool.new # The workers pool used to execute timer callbacks.
|
149
201
|
)
|
150
202
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
Tasks and task groups build on top of worker pools.
|
156
|
-
They provide a means of parallelizing expensive computations and collecing the results:
|
157
|
-
|
158
|
-
# Create a task group (it contains a pool of workers).
|
159
|
-
group = Workers::TaskGroup.new
|
160
|
-
|
161
|
-
# Add tasks to the group.
|
162
|
-
100.times do |i|
|
163
|
-
group.add(i) do
|
164
|
-
i * i
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
# Execute the tasks (blocks until the tasks complete).
|
169
|
-
group.run
|
170
|
-
|
171
|
-
# Review the results.
|
172
|
-
group.tasks.each do |t|
|
173
|
-
t.succeeded? # True or false (false if an exception occurred).
|
174
|
-
t.args # Input arguments (the value of i in this example).
|
175
|
-
t.result # Output value (the result of i * i in this example).
|
176
|
-
t.exception # The exception if one exists.
|
177
|
-
end
|
178
|
-
|
179
|
-
TaskGroup and Task can then be used to build an easy to use parallel map.
|
180
|
-
Care will have to taken regarding global data and the thread safety of data structures:
|
203
|
+
group = Workers::TaskGroup.new(
|
204
|
+
:logger => nil, # Ruby logger instance.
|
205
|
+
:pool => Workers::Pool.new # The workers pool used to execute timer callbacks.
|
206
|
+
)
|
181
207
|
|
182
|
-
Workers.
|
208
|
+
task = Workers::Task.new(
|
209
|
+
:logger => nil, # Ruby logger instance.
|
210
|
+
:perform => proc {}, # Required option. Block of code to run.
|
211
|
+
:args => [], # Array of arguments passed to the 'perform' block.
|
212
|
+
:finished => nil, # Callback to execute after attempting to run the task.
|
213
|
+
)
|
183
214
|
|
184
215
|
## Contributing
|
185
216
|
|
data/lib/workers.rb
CHANGED
@@ -10,6 +10,8 @@ require 'workers/log_proxy'
|
|
10
10
|
require 'workers/scheduler'
|
11
11
|
require 'workers/timer'
|
12
12
|
require 'workers/periodic_timer'
|
13
|
+
require 'workers/task'
|
14
|
+
require 'workers/task_group'
|
13
15
|
|
14
16
|
module Workers
|
15
17
|
def self.pool
|
@@ -29,6 +31,12 @@ module Workers
|
|
29
31
|
@scheduler.dispose if @scheduler
|
30
32
|
@scheduler = val
|
31
33
|
end
|
34
|
+
|
35
|
+
def self.map(vals, &block)
|
36
|
+
return Workers::TaskGroup.new.map(vals) do |v|
|
37
|
+
block.call(v)
|
38
|
+
end
|
39
|
+
end
|
32
40
|
end
|
33
41
|
|
34
42
|
# Force initialization of defaults.
|
data/lib/workers/task.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Workers
|
2
|
+
class Task
|
3
|
+
include Workers::Helpers
|
4
|
+
|
5
|
+
attr_reader :args
|
6
|
+
attr_reader :result
|
7
|
+
attr_reader :exception
|
8
|
+
attr_reader :state
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
@logger = Workers::LogProxy.new(options[:logger])
|
12
|
+
@args = options[:args] || []
|
13
|
+
@perform = options[:perform] || raise('Perform callback is required.')
|
14
|
+
@finished = options[:finished]
|
15
|
+
@state = :initialized
|
16
|
+
|
17
|
+
return nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
raise "Invalid state (#{@state})." unless @state == :initialized
|
22
|
+
|
23
|
+
@state = :running
|
24
|
+
|
25
|
+
begin
|
26
|
+
@result = @perform.call(*@args)
|
27
|
+
@state = :succeeded
|
28
|
+
rescue Exception => e
|
29
|
+
@state = :failed
|
30
|
+
@exception = e
|
31
|
+
end
|
32
|
+
|
33
|
+
@finished.call(self)
|
34
|
+
|
35
|
+
return nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def succeeded?
|
39
|
+
return @state == :succeeded
|
40
|
+
end
|
41
|
+
|
42
|
+
def failed?
|
43
|
+
return @state == :failed
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Workers
|
2
|
+
class TaskGroup
|
3
|
+
include Workers::Helpers
|
4
|
+
|
5
|
+
attr_reader :state
|
6
|
+
attr_reader :tasks
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@logger = Workers::LogProxy.new(options[:logger])
|
10
|
+
@pool = options[:pool] || Workers.pool
|
11
|
+
@state = :initialized
|
12
|
+
@tasks = []
|
13
|
+
@lock = Mutex.new
|
14
|
+
@finished_count = 0
|
15
|
+
@conditional = ConditionVariable.new
|
16
|
+
|
17
|
+
return nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def add(*args, &block)
|
21
|
+
state!(:initialized)
|
22
|
+
|
23
|
+
if args[0].is_a?(Workers::Task)
|
24
|
+
@tasks << args[0]
|
25
|
+
else
|
26
|
+
@tasks << Workers::Task.new(:args => args, :perform => block, :finished => method(:finished))
|
27
|
+
end
|
28
|
+
|
29
|
+
return nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def run
|
33
|
+
state!(:initialized)
|
34
|
+
|
35
|
+
@state = :running
|
36
|
+
@run_thread = Thread.current
|
37
|
+
|
38
|
+
@lock.synchronize do
|
39
|
+
@tasks.each do |task|
|
40
|
+
@pool.perform { task.run }
|
41
|
+
end
|
42
|
+
|
43
|
+
@conditional.wait(@lock)
|
44
|
+
end
|
45
|
+
|
46
|
+
return @tasks.all? { |t| t.succeeded? }
|
47
|
+
end
|
48
|
+
|
49
|
+
def successes
|
50
|
+
return @tasks.select { |t| t.succeeded? }
|
51
|
+
end
|
52
|
+
|
53
|
+
def failures
|
54
|
+
return @tasks.select { |t| t.failed? }
|
55
|
+
end
|
56
|
+
|
57
|
+
def map(inputs, &block)
|
58
|
+
inputs.each do |input|
|
59
|
+
add(input) do |i|
|
60
|
+
block.call(i)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
run
|
65
|
+
|
66
|
+
if (failure = failures[0])
|
67
|
+
a = failure.args.inspect
|
68
|
+
m = failure.exception.message
|
69
|
+
b = failure.exception.backtrace.join("\n")
|
70
|
+
|
71
|
+
raise "At least one task failed. ARGS=#{a}, TRACE=#{m}\n#{b}\n----------\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
return tasks.map { |t| t.result }
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def state!(*args)
|
80
|
+
unless args.include?(@state)
|
81
|
+
raise "Invalid state (#{@state})."
|
82
|
+
end
|
83
|
+
|
84
|
+
return nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def finished(task)
|
88
|
+
@lock.synchronize do
|
89
|
+
@finished_count += 1
|
90
|
+
@conditional.signal if @finished_count >= @tasks.count
|
91
|
+
end
|
92
|
+
|
93
|
+
return nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/workers/version.rb
CHANGED
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.0.
|
4
|
+
version: 0.0.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-05-17 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Simple Ruby workers for performing work in background threads.
|
15
15
|
email:
|
@@ -30,6 +30,8 @@ files:
|
|
30
30
|
- lib/workers/periodic_timer.rb
|
31
31
|
- lib/workers/pool.rb
|
32
32
|
- lib/workers/scheduler.rb
|
33
|
+
- lib/workers/task.rb
|
34
|
+
- lib/workers/task_group.rb
|
33
35
|
- lib/workers/timer.rb
|
34
36
|
- lib/workers/version.rb
|
35
37
|
- lib/workers/worker.rb
|