process_pool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .idea/*
2
+ vendor/*
3
+ bin/*
4
+ tmp/*
5
+ scratch_directory/*
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ disable_system_gems
2
+
3
+ gem 'json'
4
+
5
+ only :test do
6
+ gem 'shoulda'
7
+ gem 'mocha'
8
+ end
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
@@ -0,0 +1,3 @@
1
+ #!/home/psyho/.rvm/ruby-1.8.7-p174/bin/ruby
2
+ require File.join(File.dirname(__FILE__), "../vendor/gems/environment")
3
+ load File.join(File.dirname(__FILE__), "../vendor/gems/gems/shoulda-2.10.3/bin/convert_to_should_syntax")
data/bin/edit_json.rb ADDED
@@ -0,0 +1,3 @@
1
+ #!/home/psyho/.rvm/ruby-1.8.7-p174/bin/ruby
2
+ require File.join(File.dirname(__FILE__), "../vendor/gems/environment")
3
+ load File.join(File.dirname(__FILE__), "../vendor/gems/gems/json-1.2.0/bin/edit_json.rb")
data/bin/flay ADDED
@@ -0,0 +1,3 @@
1
+ #!/home/psyho/.rvm/ruby-1.8.7-p174/bin/ruby
2
+ require File.join(File.dirname(__FILE__), "../vendor/gems/environment")
3
+ load File.join(File.dirname(__FILE__), "../vendor/gems/gems/flay-1.4.0/bin/flay")
data/bin/flog ADDED
@@ -0,0 +1,3 @@
1
+ #!/home/psyho/.rvm/ruby-1.8.7-p174/bin/ruby -w
2
+ require File.join(File.dirname(__FILE__), "../vendor/gems/environment")
3
+ load File.join(File.dirname(__FILE__), "../vendor/gems/gems/flog-2.2.0/bin/flog")
@@ -0,0 +1,3 @@
1
+ #!/home/psyho/.rvm/ruby-1.8.7-p174/bin/ruby
2
+ require File.join(File.dirname(__FILE__), "../vendor/gems/environment")
3
+ load File.join(File.dirname(__FILE__), "../vendor/gems/gems/json-1.2.0/bin/prettify_json.rb")
data/bin/rake ADDED
@@ -0,0 +1,3 @@
1
+ #!/home/psyho/.rvm/ruby-1.8.7-p174/bin/ruby
2
+ require File.join(File.dirname(__FILE__), "../vendor/gems/environment")
3
+ load File.join(File.dirname(__FILE__), "../vendor/gems/gems/rake-0.8.7/bin/rake")
data/bin/rcov ADDED
@@ -0,0 +1,3 @@
1
+ #!/home/psyho/.rvm/ruby-1.8.7-p174/bin/ruby
2
+ require File.join(File.dirname(__FILE__), "../vendor/gems/environment")
3
+ load File.join(File.dirname(__FILE__), "../vendor/gems/gems/rcov-0.9.7.1/bin/rcov")
data/bin/ruby_parse ADDED
@@ -0,0 +1,3 @@
1
+ #!/home/psyho/.rvm/ruby-1.8.7-p174/bin/ruby -s
2
+ require File.join(File.dirname(__FILE__), "../vendor/gems/environment")
3
+ load File.join(File.dirname(__FILE__), "../vendor/gems/gems/ruby_parser-2.0.4/bin/ruby_parse")
data/bin/rubyforge ADDED
@@ -0,0 +1,3 @@
1
+ #!/home/psyho/.rvm/ruby-1.8.7-p174/bin/ruby
2
+ require File.join(File.dirname(__FILE__), "../vendor/gems/environment")
3
+ load File.join(File.dirname(__FILE__), "../vendor/gems/gems/rubyforge-2.0.3/bin/rubyforge")
data/bin/sow ADDED
@@ -0,0 +1,3 @@
1
+ #!/home/psyho/.rvm/ruby-1.8.7-p174/bin/ruby -ws
2
+ require File.join(File.dirname(__FILE__), "../vendor/gems/environment")
3
+ load File.join(File.dirname(__FILE__), "../vendor/gems/gems/hoe-2.5.0/bin/sow")
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'))
@@ -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
@@ -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
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'vendor', 'gems', 'environment'))
2
+
3
+ Bundler.require_env :test
4
+
5
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'init'))
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