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 +72 -50
- data/lib/workers/event.rb +2 -0
- data/lib/workers/log_proxy.rb +10 -0
- data/lib/workers/periodic_timer.rb +2 -0
- data/lib/workers/pool.rb +79 -9
- data/lib/workers/scheduler.rb +2 -0
- data/lib/workers/timer.rb +2 -2
- data/lib/workers/version.rb +1 -1
- data/lib/workers/worker.rb +4 -11
- metadata +2 -2
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
|
-
##
|
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
|
-
|
92
|
-
|
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
|
-
|
100
|
-
|
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
|
-
#
|
107
|
-
|
86
|
+
# Return the number of workers in the pool.
|
87
|
+
pool.size
|
108
88
|
|
109
|
-
#
|
110
|
-
|
111
|
-
puts 'Hello world'
|
112
|
-
end
|
89
|
+
# Increase the number of workers in the pool.
|
90
|
+
pool.expand(5)
|
113
91
|
|
114
|
-
#
|
115
|
-
|
92
|
+
# Decrease the number of workers in the pool.
|
93
|
+
pool.contract(5)
|
116
94
|
|
117
|
-
#
|
118
|
-
|
95
|
+
# Resize the pool size to a specific value.
|
96
|
+
pool.resize(20)
|
119
97
|
|
120
|
-
|
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
|
-
|
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
data/lib/workers/log_proxy.rb
CHANGED
@@ -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
|
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
|
-
@
|
14
|
-
@
|
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
|
-
@
|
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
|
-
|
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
|
-
|
41
|
-
|
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=#{
|
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
|
data/lib/workers/scheduler.rb
CHANGED
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)
|
data/lib/workers/version.rb
CHANGED
data/lib/workers/worker.rb
CHANGED
@@ -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
|
-
|
68
|
+
event.data.call(self) if event.data
|
68
69
|
end
|
69
70
|
|
70
71
|
def perform_handler(event)
|
71
|
-
|
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
|
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-
|
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:
|