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 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
- ## TODO - missing features
152
-
153
- ### Tasks
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.map([1, 2, 3, 4]) { |i| i * i }
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
 
@@ -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.
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Workers
2
- VERSION = '0.0.8'
2
+ VERSION = '0.0.9'
3
3
  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.0.8
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-02-02 00:00:00.000000000 Z
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