workers 0.0.8 → 0.0.9
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 +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
|