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 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