process_pool 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/.gitignore +5 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README +21 -0
- data/Rakefile +13 -0
- data/VERSION +1 -0
- data/bin/convert_to_should_syntax +3 -0
- data/bin/edit_json.rb +3 -0
- data/bin/flay +3 -0
- data/bin/flog +3 -0
- data/bin/prettify_json.rb +3 -0
- data/bin/rake +3 -0
- data/bin/rcov +3 -0
- data/bin/ruby_parse +3 -0
- data/bin/rubyforge +3 -0
- data/bin/sow +3 -0
- data/lib/init.rb +6 -0
- data/lib/process_pool.rb +98 -0
- data/lib/simple_logger.rb +19 -0
- data/lib/simple_queue.rb +93 -0
- data/test/process_pool_test.rb +218 -0
- data/test/simple_queue_test.rb +101 -0
- data/test/test_helper.rb +5 -0
- metadata +88 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
== MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2010, Adam Pohorecki
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Process Pool is something I created to replace a thread pool that had major performance problems caused by Ruby's (MRI)
|
2
|
+
poor thread implementation.
|
3
|
+
|
4
|
+
The basic usage is:
|
5
|
+
|
6
|
+
class SomeTask
|
7
|
+
def initialize(x, y, z)
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
pool = ProcessPool.new(5) # first constructor argument is a number of worker processes
|
15
|
+
pool.schedule SomeTask, 1, 2, 3 # adds task SomeTask.new(1,2,3) to the queue (instantiation happens just before calling run())
|
16
|
+
pool.start # starts the workers
|
17
|
+
pool.schedule SomeTask, 3, 2, 1 # tasks can be added also after calling start
|
18
|
+
pool.shutdown # waits for all the tasks in the queue to be processed and kills all the worker processes
|
19
|
+
|
20
|
+
For now there is only one, very naive, queue implementation, which uses file locking and JSON storage format. I plan on adding
|
21
|
+
some other (better) queue backend soon.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "process_pool"
|
5
|
+
gemspec.summary = "ProcessPool with interchangeable job queue backends for Ruby"
|
6
|
+
gemspec.email = "adam@pohorecki.pl"
|
7
|
+
gemspec.homepage = "http://github.com/psyho/process_pool"
|
8
|
+
gemspec.authors = ["Adam Pohorecki"]
|
9
|
+
gemspec.add_dependency 'json'
|
10
|
+
end
|
11
|
+
rescue LoadError
|
12
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
13
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/edit_json.rb
ADDED
data/bin/flay
ADDED
data/bin/flog
ADDED
data/bin/rake
ADDED
data/bin/rcov
ADDED
data/bin/ruby_parse
ADDED
data/bin/rubyforge
ADDED
data/bin/sow
ADDED
data/lib/init.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'simple_logger'))
|
5
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'simple_queue'))
|
6
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'process_pool'))
|
data/lib/process_pool.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
class ProcessPool
|
2
|
+
|
3
|
+
class InvalidStateError < StandardError;
|
4
|
+
end
|
5
|
+
|
6
|
+
attr_reader :workers_count
|
7
|
+
|
8
|
+
def initialize(workers_count, queue = SimpleQueue.create, logger = SimpleLogger.new)
|
9
|
+
self.state = :stopped
|
10
|
+
self.logger = logger
|
11
|
+
self.workers_count = workers_count
|
12
|
+
self.queue = queue
|
13
|
+
self.worker_pids = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def schedule(job_class, *args)
|
17
|
+
raise InvalidStateError.new('Can not add more jobs after shut down was called') if is_shutdown?
|
18
|
+
logger.debug("Scheduling task #{job_class}(#{args})")
|
19
|
+
push_task(job_class, args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def start
|
23
|
+
raise InvalidStateError.new('Can not start a pool more than once') unless is_stopped?
|
24
|
+
logger.info("Starting process pool")
|
25
|
+
self.state = :running
|
26
|
+
|
27
|
+
workers_count.times do
|
28
|
+
pid = fork do
|
29
|
+
child_queue = get_child_queue()
|
30
|
+
while true
|
31
|
+
task_class, args = child_queue.pop
|
32
|
+
begin
|
33
|
+
task = get_task_class(task_class).new(*args)
|
34
|
+
task.run
|
35
|
+
rescue => e
|
36
|
+
logger.warn("Exception occurred while executing task #{task_class}(#{args}): #{e}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
self.worker_pids << pid
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def shutdown
|
45
|
+
raise InvalidStateError.new('Can not shut down pool that is not running') unless is_running?
|
46
|
+
logger.info("Shutting down process pool")
|
47
|
+
self.state = :shutdown
|
48
|
+
|
49
|
+
workers_count.times do
|
50
|
+
push_task(EndTask, [])
|
51
|
+
end
|
52
|
+
|
53
|
+
worker_pids.each do |pid|
|
54
|
+
Process.wait(pid)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def is_running?
|
59
|
+
return state == :running
|
60
|
+
end
|
61
|
+
|
62
|
+
def is_stopped?
|
63
|
+
return state == :stopped
|
64
|
+
end
|
65
|
+
|
66
|
+
def is_shutdown?
|
67
|
+
return state == :shutdown
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
attr_accessor :state, :logger, :queue, :worker_pids
|
73
|
+
attr_writer :workers_count
|
74
|
+
|
75
|
+
def push_task(job_class, args)
|
76
|
+
queue.push([job_class.name.to_s, args])
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_child_queue
|
80
|
+
queue.class.get(queue.uri)
|
81
|
+
end
|
82
|
+
|
83
|
+
# this is taken from ActiveSupport (String#constantize)
|
84
|
+
def get_task_class(class_name)
|
85
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ class_name
|
86
|
+
raise NameError, "#{class_name.inspect} is not a valid constant name!"
|
87
|
+
end
|
88
|
+
|
89
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
90
|
+
end
|
91
|
+
|
92
|
+
class EndTask
|
93
|
+
def run
|
94
|
+
exit(0)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class SimpleLogger
|
2
|
+
LEVELS = [:debug, :info, :warn, :error, :fatal]
|
3
|
+
|
4
|
+
attr_accessor :level
|
5
|
+
|
6
|
+
def initialize(level = :info)
|
7
|
+
self.level = level
|
8
|
+
end
|
9
|
+
|
10
|
+
LEVELS.each do |level|
|
11
|
+
define_method(level) do |msg|
|
12
|
+
idx = LEVELS.index(level)
|
13
|
+
if idx >= LEVELS.index(self.level)
|
14
|
+
puts "Process #{Process.pid}: [#{level.to_s.upcase}] #{msg}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/lib/simple_queue.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
class SimpleQueue
|
6
|
+
|
7
|
+
attr_reader :uri
|
8
|
+
|
9
|
+
def close
|
10
|
+
File.delete(uri)
|
11
|
+
end
|
12
|
+
|
13
|
+
def push(value)
|
14
|
+
with_queue_file do |file|
|
15
|
+
contents = file.read
|
16
|
+
queue = JSON.parse(contents)
|
17
|
+
queue.push(value)
|
18
|
+
store_queue(file, queue)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def pop
|
23
|
+
result = nil
|
24
|
+
queue_empty = true
|
25
|
+
|
26
|
+
while queue_empty
|
27
|
+
queue_empty, result = pop_nowait()
|
28
|
+
sleep(0.1) if queue_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
return result
|
32
|
+
end
|
33
|
+
|
34
|
+
def size
|
35
|
+
contents = ''
|
36
|
+
with_queue_file do |file|
|
37
|
+
contents = file.read
|
38
|
+
end
|
39
|
+
return JSON.parse(contents).size
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.create
|
43
|
+
file = Tempfile.new('simple_queue')
|
44
|
+
file.puts [].to_json
|
45
|
+
uri = file.path
|
46
|
+
file.close
|
47
|
+
return new(uri)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.get(uri)
|
51
|
+
raise ArgumentError.new("Queue file must exist: #{uri}") unless File.exists?(uri)
|
52
|
+
return new(uri)
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
attr_writer :uri
|
58
|
+
|
59
|
+
def initialize(uri)
|
60
|
+
self.uri = uri
|
61
|
+
end
|
62
|
+
|
63
|
+
def with_queue_file(&block)
|
64
|
+
file = File.new(uri, 'r+')
|
65
|
+
if file.flock(File::LOCK_EX)
|
66
|
+
block.call(file)
|
67
|
+
end
|
68
|
+
file.close
|
69
|
+
end
|
70
|
+
|
71
|
+
def store_queue(file, queue)
|
72
|
+
file.truncate(0)
|
73
|
+
file.seek(0)
|
74
|
+
|
75
|
+
file.puts queue.to_json
|
76
|
+
end
|
77
|
+
|
78
|
+
def pop_nowait
|
79
|
+
queue_empty = true
|
80
|
+
result = nil
|
81
|
+
with_queue_file do |file|
|
82
|
+
contents = file.read
|
83
|
+
queue = JSON.parse(contents)
|
84
|
+
unless queue.empty?
|
85
|
+
queue_empty = false
|
86
|
+
result = queue.shift
|
87
|
+
store_queue(file, queue)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
return queue_empty, result
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
class SampleQueue
|
4
|
+
|
5
|
+
attr_accessor :data
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
self.data = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def uri
|
12
|
+
'test-queue'
|
13
|
+
end
|
14
|
+
|
15
|
+
def push(value)
|
16
|
+
self.data << value
|
17
|
+
end
|
18
|
+
|
19
|
+
def pop
|
20
|
+
self.data.shift
|
21
|
+
end
|
22
|
+
|
23
|
+
def size
|
24
|
+
self.data.size
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.create
|
28
|
+
new
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.get(uri)
|
32
|
+
new
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
class SampleTask
|
38
|
+
|
39
|
+
attr_accessor :args
|
40
|
+
|
41
|
+
def initialize(*args)
|
42
|
+
self.args = args
|
43
|
+
end
|
44
|
+
|
45
|
+
def run
|
46
|
+
return args
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
class ExceptionTask
|
52
|
+
def run
|
53
|
+
raise ArgumentError.new("something went wrong")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class WrongArgumentsTask
|
58
|
+
def initialize(x, y, z)
|
59
|
+
end
|
60
|
+
|
61
|
+
def run
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class ProcessPoolTest < Test::Unit::TestCase
|
66
|
+
|
67
|
+
context "state" do
|
68
|
+
setup do
|
69
|
+
@pool = ProcessPool.new(10, SampleQueue.new, SimpleLogger.new(:debug))
|
70
|
+
@pool.stubs(:fork => 1)
|
71
|
+
Process.stubs(:wait => 0)
|
72
|
+
end
|
73
|
+
|
74
|
+
should "be stopped after initialization" do
|
75
|
+
assert @pool.is_stopped?
|
76
|
+
assert !@pool.is_running?
|
77
|
+
assert !@pool.is_shutdown?
|
78
|
+
end
|
79
|
+
|
80
|
+
should "be running after start" do
|
81
|
+
@pool.start
|
82
|
+
assert !@pool.is_stopped?
|
83
|
+
assert @pool.is_running?
|
84
|
+
assert !@pool.is_shutdown?
|
85
|
+
end
|
86
|
+
|
87
|
+
should "be shutdown after shutdown" do
|
88
|
+
@pool.start
|
89
|
+
@pool.shutdown
|
90
|
+
assert !@pool.is_stopped?
|
91
|
+
assert !@pool.is_running?
|
92
|
+
assert @pool.is_shutdown?
|
93
|
+
end
|
94
|
+
|
95
|
+
context "invalid actions" do
|
96
|
+
should "raise InvalidStateError when calling shutdown on a not started pool" do
|
97
|
+
assert_raises ProcessPool::InvalidStateError do
|
98
|
+
@pool.shutdown
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
should "raise InvalidStateError when calling start twice" do
|
103
|
+
@pool.start
|
104
|
+
assert_raises ProcessPool::InvalidStateError do
|
105
|
+
@pool.start
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
should "raise InvalidStateError when calling shutdown twice" do
|
110
|
+
@pool.start
|
111
|
+
@pool.shutdown
|
112
|
+
assert_raises ProcessPool::InvalidStateError do
|
113
|
+
@pool.shutdown
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
should "raise InvalidStateError when calling schedule on a shutdown pool" do
|
118
|
+
@pool.start
|
119
|
+
@pool.shutdown
|
120
|
+
assert_raises ProcessPool::InvalidStateError do
|
121
|
+
@pool.schedule(SampleTask)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context :schedule do
|
128
|
+
setup do
|
129
|
+
@queue = SampleQueue.new
|
130
|
+
@pool = ProcessPool.new(10, @queue, SimpleLogger.new(:debug))
|
131
|
+
@pool.stubs(:fork => 1)
|
132
|
+
end
|
133
|
+
|
134
|
+
should "add jobs to the queue" do
|
135
|
+
assert_equal 0, @queue.size
|
136
|
+
@pool.schedule(SampleTask, 1, 2, 3)
|
137
|
+
assert_equal 1, @queue.size
|
138
|
+
assert_equal ["SampleTask", [1, 2, 3]], @queue.pop
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context :start do
|
143
|
+
setup do
|
144
|
+
@queue = SampleQueue.new
|
145
|
+
@workers_count = 10
|
146
|
+
@pool = ProcessPool.new(@workers_count, @queue, SimpleLogger.new(:debug))
|
147
|
+
end
|
148
|
+
|
149
|
+
should "fork workers_count workers" do
|
150
|
+
@pool.expects(:fork).times(@workers_count).returns(0)
|
151
|
+
@pool.start
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context :shutdown do
|
156
|
+
setup do
|
157
|
+
@queue = SampleQueue.new
|
158
|
+
@pool = ProcessPool.new(3, @queue, SimpleLogger.new(:debug)) # no workers so that nothing processes the queue
|
159
|
+
@pool.schedule(SampleTask)
|
160
|
+
@pool.stubs(:fork => 1)
|
161
|
+
@pool.start
|
162
|
+
end
|
163
|
+
|
164
|
+
should "schedule same number od EndTasks as worker_count" do
|
165
|
+
Process.stubs(:wait => 0)
|
166
|
+
@pool.shutdown
|
167
|
+
assert_equal 4, @queue.size
|
168
|
+
assert_equal "SampleTask", @queue.data[0].first
|
169
|
+
(1..3).each do |n|
|
170
|
+
assert_equal "ProcessPool::EndTask", @queue.data[n].first
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
should "wait on all of the worker processes to end" do
|
175
|
+
Process.expects(:wait).with(1).times(3).returns(0)
|
176
|
+
@pool.shutdown
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context "with default queue and two workers" do
|
181
|
+
setup do
|
182
|
+
@pool = ProcessPool.new(1)
|
183
|
+
2.times { |n| @pool.schedule(SampleTask, n) }
|
184
|
+
end
|
185
|
+
|
186
|
+
should "empty the queue before returning from shutdown" do
|
187
|
+
assert_equal 2, @pool.send(:queue).size
|
188
|
+
@pool.start
|
189
|
+
@pool.shutdown
|
190
|
+
assert_equal 0, @pool.send(:queue).size
|
191
|
+
end
|
192
|
+
|
193
|
+
should "empty the queue even if some tasks result in exception" do
|
194
|
+
@pool.schedule(ExceptionTask)
|
195
|
+
assert_equal 3, @pool.send(:queue).size
|
196
|
+
@pool.start
|
197
|
+
@pool.shutdown
|
198
|
+
assert_equal 0, @pool.send(:queue).size
|
199
|
+
end
|
200
|
+
|
201
|
+
should "empty the queue even if some tasks can not be loaded" do
|
202
|
+
@pool.schedule(String)
|
203
|
+
assert_equal 3, @pool.send(:queue).size
|
204
|
+
@pool.start
|
205
|
+
@pool.shutdown
|
206
|
+
assert_equal 0, @pool.send(:queue).size
|
207
|
+
end
|
208
|
+
|
209
|
+
should "empty the queue even if some tasks can not be initialized" do
|
210
|
+
@pool.schedule(WrongArgumentsTask, 1)
|
211
|
+
assert_equal 3, @pool.send(:queue).size
|
212
|
+
@pool.start
|
213
|
+
@pool.shutdown
|
214
|
+
assert_equal 0, @pool.send(:queue).size
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
class SimpleQueueTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@queue = SimpleQueue.create
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
@queue.close if @queue
|
11
|
+
end
|
12
|
+
|
13
|
+
context :create do
|
14
|
+
should "return a queue" do
|
15
|
+
assert @queue.is_a?(SimpleQueue)
|
16
|
+
end
|
17
|
+
|
18
|
+
should "return queues with different URIs every time" do
|
19
|
+
new_queue = SimpleQueue.create
|
20
|
+
new_queue.close
|
21
|
+
assert new_queue.uri != @queue.uri
|
22
|
+
end
|
23
|
+
|
24
|
+
should "return a queue that is empty" do
|
25
|
+
assert_equal 0, @queue.size
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context :get do
|
30
|
+
setup do
|
31
|
+
@uri = @queue.uri
|
32
|
+
end
|
33
|
+
|
34
|
+
should "raise an ArgumentError if queue does not exist" do
|
35
|
+
assert_raises(ArgumentError) { SimpleQueue.get('not existing') }
|
36
|
+
end
|
37
|
+
|
38
|
+
should "return a queue if it does exist" do
|
39
|
+
queue = SimpleQueue.get(@uri)
|
40
|
+
assert queue
|
41
|
+
assert_equal @uri, queue.uri
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context :pop do
|
46
|
+
context "on a queue that contains something" do
|
47
|
+
setup do
|
48
|
+
@queue.push('hello')
|
49
|
+
end
|
50
|
+
|
51
|
+
context "pop" do
|
52
|
+
setup do
|
53
|
+
@result = @queue.pop
|
54
|
+
end
|
55
|
+
|
56
|
+
should "return 'hello'" do
|
57
|
+
assert_equal 'hello', @result
|
58
|
+
end
|
59
|
+
|
60
|
+
should_change('queue size', :by => -1) { @queue.size }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'on an empty queue' do
|
65
|
+
context 'pop' do
|
66
|
+
should 'block until something gets pushed' do
|
67
|
+
pid = fork do
|
68
|
+
queue = SimpleQueue.get(@queue.uri)
|
69
|
+
value = queue.pop
|
70
|
+
exit(value || 0)
|
71
|
+
end
|
72
|
+
sleep(0.5) # give the child process some time to start
|
73
|
+
@queue.push(19)
|
74
|
+
pid, status = Process.waitpid2(pid)
|
75
|
+
assert_equal 19, status.exitstatus
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context :push do
|
82
|
+
context "on a queue that contains something" do
|
83
|
+
setup do
|
84
|
+
@queue.push('hello')
|
85
|
+
end
|
86
|
+
|
87
|
+
should_change('queue size', :by => 1) { @queue.size }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
should "work on a FIFO basis" do
|
92
|
+
elements = [1, 2, 3, 4, 5]
|
93
|
+
elements.each { |e| @queue.push(e) }
|
94
|
+
popped = []
|
95
|
+
elements.size.times do
|
96
|
+
popped << @queue.pop
|
97
|
+
end
|
98
|
+
assert_equal elements, popped
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: process_pool
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Pohorecki
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-17 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: json
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: adam@pohorecki.pl
|
27
|
+
executables:
|
28
|
+
- convert_to_should_syntax
|
29
|
+
- rake
|
30
|
+
- rubyforge
|
31
|
+
- flog
|
32
|
+
- ruby_parse
|
33
|
+
- flay
|
34
|
+
- rcov
|
35
|
+
- edit_json.rb
|
36
|
+
- sow
|
37
|
+
- prettify_json.rb
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- LICENSE
|
42
|
+
- README
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- Gemfile
|
46
|
+
- LICENSE
|
47
|
+
- README
|
48
|
+
- Rakefile
|
49
|
+
- VERSION
|
50
|
+
- lib/init.rb
|
51
|
+
- lib/process_pool.rb
|
52
|
+
- lib/simple_logger.rb
|
53
|
+
- lib/simple_queue.rb
|
54
|
+
- test/process_pool_test.rb
|
55
|
+
- test/simple_queue_test.rb
|
56
|
+
- test/test_helper.rb
|
57
|
+
has_rdoc: true
|
58
|
+
homepage: http://github.com/psyho/process_pool
|
59
|
+
licenses: []
|
60
|
+
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options:
|
63
|
+
- --charset=UTF-8
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.3.5
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: ProcessPool with interchangeable job queue backends for Ruby
|
85
|
+
test_files:
|
86
|
+
- test/test_helper.rb
|
87
|
+
- test/simple_queue_test.rb
|
88
|
+
- test/process_pool_test.rb
|