workers 0.0.9 → 0.1.0

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