workers 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  Workers is a Ruby gem for performing work in background threads.
4
4
  Design goals include high performance, low latency, simple API, customizability, and multi-layered architecture.
5
+ It provides a number of simple to use classes that solve a wide range of concurrency problems.
5
6
  It is used by [Tribe](https://github.com/chadrem/tribe "Tribe") to implement event-driven actors.
6
7
 
7
8
  ## Installation
@@ -74,53 +75,31 @@ The Worker class is designed to be customized through inheritence and its event
74
75
  Note that you can use custom workers without a pool.
75
76
  This effectively gives you direct access to a single event-driven thread.
76
77
 
77
- ## Timers
78
-
79
- Timers provide a way to execute code in the future:
80
-
81
- # Create a one shot timer that executes in 1.5 seconds.
82
- timer = Workers::Timer.new(1.5) do
83
- puts 'Hello world'
84
- end
85
-
86
- # Create a periodic timer that loops infinitely or until 'cancel' is called.
87
- timer = Workers::PeriodicTimer.new(1) do
88
- puts 'Hello world many times'
89
- end
78
+ ## Pools - Advanced
90
79
 
91
- # Let the timer print some lines.
92
- sleep(5)
93
-
94
- # Shutdown the timer.
95
- timer.cancel
96
-
97
- ## Schedulers
80
+ As shown above, pools effectively allow a group of workers to share a work queue.
81
+ They have a few additional methods described below:
98
82
 
99
- Schedulers are what trigger Timers to fire.
100
- The system has a global default scheduler which should meet most needs (Workers.scheduler).
101
- You can create additional or custom ones as necessary:
102
-
103
- # Create a workers pool with a larger than default thread count (optional).
104
- pool = Workers::Pool.new(:size => 100)
83
+ # Create a pool.
84
+ pool = Workers::Pool.new
105
85
 
106
- # Create a scheduler.
107
- scheduler = Workers::Scheduler.new(:pool => pool)
86
+ # Return the number of workers in the pool.
87
+ pool.size
108
88
 
109
- # Create a timer that uses the above scheduler.
110
- timer = Workers::Timer.new(1, :scheduler => scheduler) do
111
- puts 'Hello world'
112
- end
89
+ # Increase the number of workers in the pool.
90
+ pool.expand(5)
113
91
 
114
- # Wait for the timer to fire.
115
- sleep(5)
92
+ # Decrease the number of workers in the pool.
93
+ pool.contract(5)
116
94
 
117
- # Shutdown the scheduler.
118
- scheduler.dispose
95
+ # Resize the pool size to a specific value.
96
+ pool.resize(20)
119
97
 
120
- ### Tasks
98
+ ## Tasks
121
99
 
122
100
  Tasks and task groups build on top of worker pools.
123
- They provide a means of parallelizing expensive computations and collecing the results:
101
+ They provide a means of parallelizing expensive computations and collecing the results.
102
+ These are the classes you normally work with in your application level code.
124
103
 
125
104
  # Create a task group (it contains a pool of workers).
126
105
  group = Workers::TaskGroup.new
@@ -156,7 +135,7 @@ They provide a means of parallelizing expensive computations and collecing the r
156
135
  t.exception # The exception if one exists.
157
136
  end
158
137
 
159
- ### Parallel Map
138
+ ## Parallel Map
160
139
 
161
140
  Task groups and tasks are the building blocks of parallel map:
162
141
 
@@ -169,6 +148,49 @@ The below syntax is equivalent to the above:
169
148
 
170
149
  Note that any failures will cause an exception to be raised.
171
150
 
151
+ ## Timers
152
+
153
+ Timers provide a way to execute code in the future:
154
+
155
+ # Create a one shot timer that executes in 1.5 seconds.
156
+ timer = Workers::Timer.new(1.5) do
157
+ puts 'Hello world'
158
+ end
159
+
160
+ # Create a periodic timer that loops infinitely or until 'cancel' is called.
161
+ timer = Workers::PeriodicTimer.new(1) do
162
+ puts 'Hello world many times'
163
+ end
164
+
165
+ # Let the timer print some lines.
166
+ sleep(5)
167
+
168
+ # Shutdown the timer.
169
+ timer.cancel
170
+
171
+ ## Schedulers
172
+
173
+ Schedulers are what trigger Timers to fire.
174
+ The system has a global default scheduler which should meet most needs (Workers.scheduler).
175
+ You can create additional or custom ones as necessary:
176
+
177
+ # Create a workers pool with a larger than default thread count (optional).
178
+ pool = Workers::Pool.new(:size => 100)
179
+
180
+ # Create a scheduler.
181
+ scheduler = Workers::Scheduler.new(:pool => pool)
182
+
183
+ # Create a timer that uses the above scheduler.
184
+ timer = Workers::Timer.new(1, :scheduler => scheduler) do
185
+ puts 'Hello world'
186
+ end
187
+
188
+ # Wait for the timer to fire.
189
+ sleep(5)
190
+
191
+ # Shutdown the scheduler.
192
+ scheduler.dispose
193
+
172
194
  ## Options (defaults below):
173
195
 
174
196
  pool = Workers::Pool.new(
@@ -189,17 +211,6 @@ Note that any failures will cause an exception to be raised.
189
211
  :callback => nil # The proc to execute (provide this or a block, but not both).
190
212
  )
191
213
 
192
- timer = Workers::PeriodicTimer.new(1,
193
- :logger => nil, # Ruby logger instance.
194
- :scheduler => Workers.scheduler, # The scheduler that manages execution.
195
- :callback => nil # The proc to execute (provide this or a block, but not both).
196
- )
197
-
198
- scheduler = Workers::Scheduler.new(
199
- :logger => nil, # Ruby logger instance.
200
- :pool => Workers::Pool.new # The workers pool used to execute timer callbacks.
201
- )
202
-
203
214
  group = Workers::TaskGroup.new(
204
215
  :logger => nil, # Ruby logger instance.
205
216
  :pool => Workers::Pool.new # The workers pool used to execute timer callbacks.
@@ -212,6 +223,17 @@ Note that any failures will cause an exception to be raised.
212
223
  :finished => nil, # Callback to execute after attempting to run the task.
213
224
  )
214
225
 
226
+ timer = Workers::PeriodicTimer.new(1,
227
+ :logger => nil, # Ruby logger instance.
228
+ :scheduler => Workers.scheduler, # The scheduler that manages execution.
229
+ :callback => nil # The proc to execute (provide this or a block, but not both).
230
+ )
231
+
232
+ scheduler = Workers::Scheduler.new(
233
+ :logger => nil, # Ruby logger instance.
234
+ :pool => Workers::Pool.new # The workers pool used to execute timer callbacks.
235
+ )
236
+
215
237
  ## Contributing
216
238
 
217
239
  1. Fork it
data/lib/workers/event.rb CHANGED
@@ -6,6 +6,8 @@ module Workers
6
6
  def initialize(command, data)
7
7
  @command = command
8
8
  @data = data
9
+
10
+ return nil
9
11
  end
10
12
  end
11
13
  end
@@ -4,22 +4,32 @@ module Workers
4
4
 
5
5
  def initialize(logger)
6
6
  @logger = logger.is_a?(Workers::LogProxy) ? logger.logger : logger
7
+
8
+ return nil
7
9
  end
8
10
 
9
11
  def debug(msg)
10
12
  @logger.debug(msg) if @logger
13
+
14
+ return nil
11
15
  end
12
16
 
13
17
  def info(msg)
14
18
  @logger.info(msg) if @logger
19
+
20
+ return nil
15
21
  end
16
22
 
17
23
  def warn(msg)
18
24
  @logger.warn(msg) if @logger
25
+
26
+ return nil
19
27
  end
20
28
 
21
29
  def error(msg)
22
30
  @logger.error(msg) if @logger
31
+
32
+ return nil
23
33
  end
24
34
  end
25
35
  end
@@ -4,6 +4,8 @@ module Workers
4
4
  options[:repeat] = true
5
5
 
6
6
  super(delay, options, &block)
7
+
8
+ return nil
7
9
  end
8
10
  end
9
11
  end
data/lib/workers/pool.rb CHANGED
@@ -6,12 +6,15 @@ module Workers
6
6
 
7
7
  def initialize(options = {})
8
8
  @logger = Workers::LogProxy.new(options[:logger])
9
- @size = options[:size] || Workers::Pool::DEFAULT_POOL_SIZE
10
9
  @worker_class = options[:worker_class] || Workers::Worker
11
-
12
10
  @input_queue = Queue.new
13
- @workers = []
14
- @size.times { @workers << @worker_class.new(:input_queue => @input_queue) }
11
+ @lock = Monitor.new
12
+ @workers = Set.new
13
+ @size = 0
14
+
15
+ expand(options[:size] || Workers::Pool::DEFAULT_POOL_SIZE)
16
+
17
+ return nil
15
18
  end
16
19
 
17
20
  def enqueue(command, data = nil)
@@ -27,24 +30,91 @@ module Workers
27
30
  end
28
31
 
29
32
  def shutdown(&block)
30
- @size.times { enqueue(:shutdown, block) }
33
+ @lock.synchronize do
34
+ @size.times do
35
+ enqueue(:shutdown, block)
36
+ end
37
+ end
31
38
 
32
39
  return nil
33
40
  end
34
41
 
35
42
  def join(max_wait = nil)
36
- return @workers.map { |w| w.join(max_wait) }
43
+ results = @workers.map { |w| w.join(max_wait) }
44
+ @workers.clear
45
+ @size = 0
46
+
47
+ return results
37
48
  end
38
49
 
39
50
  def dispose
40
- shutdown
41
- join
51
+ @lock.synchronize do
52
+ shutdown
53
+ join
54
+ end
42
55
 
43
56
  return nil
44
57
  end
45
58
 
46
59
  def inspect
47
- return "#<#{self.class.to_s}:0x#{(object_id << 1).to_s(16)} size=#{@size}>"
60
+ return "#<#{self.class.to_s}:0x#{(object_id << 1).to_s(16)} size=#{size}>"
61
+ end
62
+
63
+ def size
64
+ @lock.synchronize do
65
+ return @size
66
+ end
67
+ end
68
+
69
+ def expand(count)
70
+ @lock.synchronize do
71
+ count.times do
72
+ @workers << @worker_class.new(:input_queue => @input_queue)
73
+ @size += 1
74
+ end
75
+ end
76
+
77
+ return nil
78
+ end
79
+
80
+ def contract(count, &block)
81
+ @lock.synchronize do
82
+ raise 'Count is too large.' if count > @size
83
+
84
+ count.times do
85
+ callback = Proc.new do |worker|
86
+ remove_worker(worker)
87
+ block.call if block
88
+ end
89
+
90
+ enqueue(:shutdown, callback)
91
+ @size -= 1
92
+ end
93
+ end
94
+
95
+ return nil
96
+ end
97
+
98
+ def resize(new_size)
99
+ @lock.synchronize do
100
+ if new_size > @size
101
+ expand(new_size - @size)
102
+ elsif new_size < @size
103
+ contract(@size - new_size)
104
+ end
105
+ end
106
+
107
+ return nil
108
+ end
109
+
110
+ private
111
+
112
+ def remove_worker(worker)
113
+ @lock.synchronize do
114
+ @workers.delete(worker)
115
+ end
116
+
117
+ return nil
48
118
  end
49
119
  end
50
120
  end
@@ -8,6 +8,8 @@ module Workers
8
8
  @schedule = SortedSet.new
9
9
  @mutex = Mutex.new
10
10
  @thread = Thread.new { start_loop }
11
+
12
+ return nil
11
13
  end
12
14
 
13
15
  def schedule(timer)
data/lib/workers/timer.rb CHANGED
@@ -11,12 +11,12 @@ module Workers
11
11
  @callback = options[:callback] || block
12
12
  @repeat = options[:repeat] || false
13
13
  @scheduler = options[:scheduler] || Workers.scheduler
14
-
15
14
  @mutex = Mutex.new
16
15
 
17
16
  reset
18
-
19
17
  @scheduler.schedule(self)
18
+
19
+ return nil
20
20
  end
21
21
 
22
22
  def <=>(other)
@@ -1,3 +1,3 @@
1
1
  module Workers
2
- VERSION = '0.0.9'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -5,8 +5,9 @@ module Workers
5
5
  def initialize(options = {})
6
6
  @logger = Workers::LogProxy.new(options[:logger])
7
7
  @input_queue = options[:input_queue] || Queue.new
8
-
9
8
  @thread = Thread.new { start_event_loop }
9
+
10
+ return nil
10
11
  end
11
12
 
12
13
  def enqueue(command, data = nil)
@@ -64,25 +65,17 @@ module Workers
64
65
  end
65
66
 
66
67
  def shutdown_handler(event)
67
- try_callback(event.data)
68
+ event.data.call(self) if event.data
68
69
  end
69
70
 
70
71
  def perform_handler(event)
71
- try_callback(event.data)
72
+ event.data.call if event.data
72
73
  end
73
74
 
74
75
  def exception_handler(e)
75
76
  puts concat_e('Worker event loop died.', e)
76
77
  end
77
78
 
78
- def try_callback(callback, &block)
79
- begin
80
- callback.call
81
- rescue Exception => e
82
- block.call(e) if block
83
- end
84
- end
85
-
86
79
  def process_event(event)
87
80
  raise "Unhandled event (#{event.inspect}). Subclass and override if you need custom events."
88
81
  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.9
4
+ version: 0.1.0
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-05-17 00:00:00.000000000 Z
12
+ date: 2013-05-18 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Simple Ruby workers for performing work in background threads.
15
15
  email: