thpool 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/.document +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +20 -0
- data/README.md +87 -0
- data/Rakefile +37 -0
- data/VERSION +2 -0
- data/lib/thpool.rb +147 -0
- data/lib/thpool/worker.rb +71 -0
- data/test/helper.rb +31 -0
- data/test/unit/test_thpool.rb +142 -0
- data/test/unit/test_thpool_worker.rb +14 -0
- data/test/unit/test_worker_pool.rb +142 -0
- metadata +91 -0
data/.document
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Scott Tadman
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# Thpool
|
2
|
+
|
3
|
+
This is a simple threaded pool worker system that can process tasks in the
|
4
|
+
order they are received.
|
5
|
+
|
6
|
+
## Example
|
7
|
+
|
8
|
+
```
|
9
|
+
pool = Thpool.new
|
10
|
+
|
11
|
+
pool.perform do
|
12
|
+
# ... Action to be enqueued here
|
13
|
+
end
|
14
|
+
```
|
15
|
+
|
16
|
+
The worker pool has some sensible defaults as to how many workers will be
|
17
|
+
created, zero when there's no work, up to twenty when there's enough work
|
18
|
+
to be performed. These settings can be customized.
|
19
|
+
|
20
|
+
Constructor options:
|
21
|
+
|
22
|
+
* `:worker_class` - What kind of worker to spawn. Should be a Worker subclass.
|
23
|
+
* `:workers_min` - The minimum number of workers to have running.
|
24
|
+
* `:workers_max` - The maximum number of workers to have running.
|
25
|
+
* `:count_per_worker` - The ratio of tasks to workers.
|
26
|
+
|
27
|
+
The default `EnThpool::Worker` class should suffice for most tasks.
|
28
|
+
If necessary, this can be subclassed. This would be useful if the worker
|
29
|
+
needs to perform some kind of resource initialization before it's able to
|
30
|
+
complete any tasks, such as establishing a database connection.
|
31
|
+
|
32
|
+
There is a method `after_initialize` that will execute on the worker thread
|
33
|
+
immediately after the worker is created. This is useful for performing
|
34
|
+
post-initialization functions that would otherwise block the main thread:
|
35
|
+
|
36
|
+
```
|
37
|
+
class ExampleDatabaseWorker < Thpool::Worker
|
38
|
+
def after_initialize
|
39
|
+
# Create a database handle.
|
40
|
+
@db = DatabaseDriver::Handle.new
|
41
|
+
|
42
|
+
# Pass in the database handle as the arguments to the blocks being
|
43
|
+
# processed.
|
44
|
+
@args = [ @db ]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
It's also possible to re-write the `perform` method to pass in additional
|
50
|
+
arguments.
|
51
|
+
|
52
|
+
If you need to do something immediately before or after processing of a block,
|
53
|
+
two methods are available. As an example this can be used to record the amount
|
54
|
+
of time it took to complete a task:
|
55
|
+
|
56
|
+
```
|
57
|
+
class ExampleDatabaseWorker < Thpool::Worker
|
58
|
+
def before_perform(block)
|
59
|
+
@start_time = Time.now
|
60
|
+
end
|
61
|
+
|
62
|
+
def after_perform(block)
|
63
|
+
puts "Took %ds" % (Time.now - @start_time)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
If exceptions are generated within the worker thread either because of
|
69
|
+
processing a task or otherwise, these are passed back to the Thpool
|
70
|
+
object via the `handle_exception` method. The default behavior is to re-raise
|
71
|
+
these, but it's also possible to perform some additional handling here to
|
72
|
+
rescue from or ignore them:
|
73
|
+
|
74
|
+
```
|
75
|
+
class ExampleDatabasePool < Thpool
|
76
|
+
def handle_exception(worker, exception, block = nil)
|
77
|
+
# Pass through to a custom exception logger
|
78
|
+
ExceptionHandler.log(exception)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
## Copyright
|
84
|
+
|
85
|
+
Copyright (c) 2013 Scott Tadman, The Working Group Inc.
|
86
|
+
See LICENSE.txt for further details.
|
87
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
|
6
|
+
begin
|
7
|
+
Bundler.setup(:default, :development)
|
8
|
+
rescue Bundler::BundlerError => e
|
9
|
+
$stderr.puts e.message
|
10
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
11
|
+
exit e.status_code
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'rake'
|
15
|
+
require 'jeweler'
|
16
|
+
|
17
|
+
Jeweler::Tasks.new do |gem|
|
18
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
19
|
+
gem.name = "thpool"
|
20
|
+
gem.homepage = "http://github.com/twg/thpool"
|
21
|
+
gem.license = "MIT"
|
22
|
+
gem.summary = %Q{Threaded Pool/Worker System}
|
23
|
+
gem.description = %Q{Simple thread pool/worker system}
|
24
|
+
gem.email = "scott@twg.ca"
|
25
|
+
gem.authors = [ "Scott Tadman" ]
|
26
|
+
# dependencies defined in Gemfile
|
27
|
+
end
|
28
|
+
|
29
|
+
Jeweler::RubygemsDotOrgTasks.new
|
30
|
+
|
31
|
+
require 'rake/testtask'
|
32
|
+
|
33
|
+
Rake::TestTask.new(:test) do |test|
|
34
|
+
test.libs << 'lib' << 'test'
|
35
|
+
test.pattern = 'test/**/test_*.rb'
|
36
|
+
test.verbose = true
|
37
|
+
end
|
data/VERSION
ADDED
data/lib/thpool.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class Thpool
|
4
|
+
# == Submodules ===========================================================
|
5
|
+
|
6
|
+
autoload(:Worker, 'thpool/worker')
|
7
|
+
|
8
|
+
# == Constants ============================================================
|
9
|
+
|
10
|
+
OPTIONS_DEFAULT = {
|
11
|
+
:worker_class => Thpool::Worker,
|
12
|
+
:workers_min => 0,
|
13
|
+
:workers_max => 20,
|
14
|
+
:count_per_worker => 1,
|
15
|
+
:args => [ ]
|
16
|
+
}
|
17
|
+
|
18
|
+
# == Properties ===========================================================
|
19
|
+
|
20
|
+
attr_reader *OPTIONS_DEFAULT.keys
|
21
|
+
|
22
|
+
# == Instance Methods =====================================================
|
23
|
+
|
24
|
+
# Creates a new instance of Pool with an optional set of options:
|
25
|
+
# * :worker_class - The class of worker to spawn when tasks arrive.
|
26
|
+
# * :workers_min - The minimum number of workers to have running.
|
27
|
+
# * :workers_max - The maximum number of workers to spawn.
|
28
|
+
# * :count_per_worker - Ratio of items in queue to workers.
|
29
|
+
# * :args - An array of arguments to be passed through to the workers.
|
30
|
+
def initialize(options = nil)
|
31
|
+
@queue = Queue.new
|
32
|
+
@workers = [ ]
|
33
|
+
|
34
|
+
options = options ? OPTIONS_DEFAULT.merge(options) : OPTIONS_DEFAULT
|
35
|
+
|
36
|
+
@worker_class = options[:worker_class]
|
37
|
+
@workers_min = options[:workers_min]
|
38
|
+
@workers_max = options[:workers_max]
|
39
|
+
@count_per_worker = options[:count_per_worker]
|
40
|
+
@args = options[:args]
|
41
|
+
|
42
|
+
@workers_min.times do
|
43
|
+
self.worker_create!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the number of active worker threads.
|
48
|
+
def workers_count
|
49
|
+
@workers.length
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the number of workers required for the current loading.
|
53
|
+
def workers_needed
|
54
|
+
n = ((@queue.length + @workers.length - @queue.num_waiting) / @count_per_worker)
|
55
|
+
|
56
|
+
if (n > @workers_max)
|
57
|
+
@workers_max
|
58
|
+
elsif (n < @workers_min)
|
59
|
+
@workers_min
|
60
|
+
else
|
61
|
+
n
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Makes a blocking call to pop an item from the queue, returning that item.
|
66
|
+
# If the queue is empty, also has the effect of sleeping the calling thread
|
67
|
+
# until something is pushed into the queue.
|
68
|
+
def block_pop
|
69
|
+
@queue.pop
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns true if more workers are needed to satisfy the current backlog,
|
73
|
+
# or false otherwise.
|
74
|
+
def workers_needed?
|
75
|
+
@workers.length < self.workers_needed
|
76
|
+
end
|
77
|
+
|
78
|
+
def worker_needed?(worker)
|
79
|
+
@queue.length > 0 or @workers.length <= self.workers_needed
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns an array of the current workers.
|
83
|
+
def workers
|
84
|
+
@workers.dup
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the current number of workers.
|
88
|
+
def workers_count
|
89
|
+
@workers.length
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns true if there are any workers, false otherwise.
|
93
|
+
def workers?
|
94
|
+
@workers.any?
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns true if there is some outstanding work to be performed, false
|
98
|
+
# otherwise.
|
99
|
+
def busy?
|
100
|
+
@queue.num_waiting < @workers.length
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns the number of workers that are waiting for something to do.
|
104
|
+
def waiting
|
105
|
+
@queue.num_waiting
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns true if anything is queued, false otherwise. Note that this does
|
109
|
+
# not count anything that might be active within a worker.
|
110
|
+
def queue?
|
111
|
+
@queue.length > 0
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns the number of items in the queue. Note that this does not count
|
115
|
+
# anything that might be active within a worker.
|
116
|
+
def queue_size
|
117
|
+
@queue.size
|
118
|
+
end
|
119
|
+
|
120
|
+
# Receives reports of exceptions from workers. Default behavior is to re-raise.
|
121
|
+
def report_exception!(worker, exception, block = nil)
|
122
|
+
raise(exception)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Schedules a block to be acted upon by the workers.
|
126
|
+
def perform
|
127
|
+
@queue << Proc.new
|
128
|
+
|
129
|
+
if (self.workers_count < self.workers_needed)
|
130
|
+
self.worker_create!
|
131
|
+
end
|
132
|
+
|
133
|
+
true
|
134
|
+
end
|
135
|
+
|
136
|
+
# Called by a worker when it's finished. Should not be called otherwise.
|
137
|
+
def worker_finished!(worker)
|
138
|
+
@workers.delete(worker)
|
139
|
+
worker.join
|
140
|
+
end
|
141
|
+
|
142
|
+
protected
|
143
|
+
# Creates a new worker and puts it into the list of available workers.
|
144
|
+
def worker_create!
|
145
|
+
@workers << worker_class.new(self, *@args)
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Thpool::Worker
|
2
|
+
# == Properties ===========================================================
|
3
|
+
|
4
|
+
attr_reader :pool
|
5
|
+
attr_reader :args
|
6
|
+
attr_reader :block
|
7
|
+
|
8
|
+
# == Instance Methods =====================================================
|
9
|
+
|
10
|
+
# Creates a new worker attached to the provided pool. Optional arguments
|
11
|
+
# may be supplied, which are passed on to the blocks it processes.
|
12
|
+
def initialize(pool, *args)
|
13
|
+
@pool = pool
|
14
|
+
@args = args
|
15
|
+
|
16
|
+
@thread = Thread.new do
|
17
|
+
Thread.abort_on_exception = true
|
18
|
+
begin
|
19
|
+
self.after_initialize
|
20
|
+
|
21
|
+
while (block = @pool.block_pop)
|
22
|
+
begin
|
23
|
+
@block = block
|
24
|
+
self.before_perform(block)
|
25
|
+
perform(&block)
|
26
|
+
self.after_perform(block)
|
27
|
+
@block = nil
|
28
|
+
|
29
|
+
unless (@pool.worker_needed?(self))
|
30
|
+
@pool.worker_finished!(self)
|
31
|
+
break
|
32
|
+
end
|
33
|
+
rescue => exception
|
34
|
+
@pool.handle_exception(self, exception, block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
rescue => exception
|
38
|
+
@pool.handle_exception(self, exception, nil)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Calls the Proc pulled from the queue. Subclasses can implement their own
|
44
|
+
# method here which might pass in arguments to the block for contextual
|
45
|
+
# purposes.
|
46
|
+
def perform
|
47
|
+
yield(*@args)
|
48
|
+
end
|
49
|
+
|
50
|
+
# This method is called after the worker is initialized within the thread
|
51
|
+
# used by the worker. It can be customized in sub-classes as required.
|
52
|
+
def after_initialize
|
53
|
+
end
|
54
|
+
|
55
|
+
# This method is called just before the worker executes the given block.
|
56
|
+
# This should be customized in sub-classes to do any additional processing
|
57
|
+
# required.
|
58
|
+
def before_perform(block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# This method is called just after the worker has finished executing the
|
62
|
+
# given block This should be customized in sub-classes to do any additional
|
63
|
+
# processing required.
|
64
|
+
def after_perform(block)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Called by the pool to reap this thread when it is finished.
|
68
|
+
def join
|
69
|
+
@thread.join
|
70
|
+
end
|
71
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
|
4
|
+
begin
|
5
|
+
Bundler.setup(:default, :development)
|
6
|
+
rescue Bundler::BundlerError => e
|
7
|
+
$stderr.puts e.message
|
8
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
9
|
+
exit e.status_code
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'test/unit'
|
13
|
+
|
14
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
15
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
16
|
+
|
17
|
+
require 'thpool'
|
18
|
+
|
19
|
+
class Test::Unit::TestCase
|
20
|
+
def assert_eventually(time = nil, message = nil, &block)
|
21
|
+
start_time = Time.now.to_i
|
22
|
+
|
23
|
+
while (!block.call)
|
24
|
+
select(nil, nil, nil, 0.1)
|
25
|
+
|
26
|
+
if (time and (Time.now.to_i - start_time > time))
|
27
|
+
flunk(message || 'assert_eventually timed out')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require_relative '../helper'
|
2
|
+
|
3
|
+
class TestThpool < Test::Unit::TestCase
|
4
|
+
def test_defaults
|
5
|
+
pool = Thpool.new
|
6
|
+
|
7
|
+
assert_equal false, pool.queue?
|
8
|
+
assert_equal 0, pool.queue_size
|
9
|
+
|
10
|
+
assert_equal 0, pool.workers_count
|
11
|
+
assert_equal 0, pool.workers_needed
|
12
|
+
assert_equal false, pool.workers_needed?
|
13
|
+
assert_equal 0, pool.waiting
|
14
|
+
|
15
|
+
assert_equal [ ], pool.workers
|
16
|
+
|
17
|
+
options_default = Thpool::OPTIONS_DEFAULT
|
18
|
+
|
19
|
+
assert_equal options_default[:worker_class], pool.worker_class
|
20
|
+
assert_equal options_default[:workers_min], pool.workers_min
|
21
|
+
assert_equal options_default[:workers_max], pool.workers_max
|
22
|
+
assert_equal options_default[:count_per_worker], pool.count_per_worker
|
23
|
+
assert_equal options_default[:args], pool.args
|
24
|
+
end
|
25
|
+
|
26
|
+
class ExampleWorker < Thpool::Worker
|
27
|
+
attr_reader :after_initialized
|
28
|
+
attr_reader :before_performed
|
29
|
+
attr_reader :after_performed
|
30
|
+
|
31
|
+
def after_initialize
|
32
|
+
@after_initialized = :after_initialized
|
33
|
+
end
|
34
|
+
|
35
|
+
def before_perform(block)
|
36
|
+
@before_performed = :before_performed
|
37
|
+
end
|
38
|
+
|
39
|
+
def after_perform(block)
|
40
|
+
@after_performed = :after_performed
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_options
|
45
|
+
pool = Thpool.new(
|
46
|
+
:worker_class => ExampleWorker,
|
47
|
+
:workers_min => 1,
|
48
|
+
:workers_max => 5,
|
49
|
+
:count_per_worker => 2,
|
50
|
+
:args => [ :example ]
|
51
|
+
)
|
52
|
+
|
53
|
+
assert_equal false, pool.queue?
|
54
|
+
assert_equal 0, pool.queue_size
|
55
|
+
|
56
|
+
assert_equal 1, pool.workers_count
|
57
|
+
assert_equal 1, pool.workers_needed
|
58
|
+
assert_equal false, pool.workers_needed?
|
59
|
+
|
60
|
+
assert_equal ExampleWorker, pool.worker_class
|
61
|
+
assert_equal 1, pool.workers_min
|
62
|
+
assert_equal 5, pool.workers_max
|
63
|
+
assert_equal 2, pool.count_per_worker
|
64
|
+
assert_equal [ :example ], pool.args
|
65
|
+
|
66
|
+
worker = pool.workers[0]
|
67
|
+
|
68
|
+
assert worker
|
69
|
+
assert worker.is_a?(ExampleWorker)
|
70
|
+
|
71
|
+
assert_equal pool, worker.pool
|
72
|
+
assert_equal nil, worker.block
|
73
|
+
assert_equal [ :example ], worker.args
|
74
|
+
|
75
|
+
assert_eventually(1) do
|
76
|
+
worker.after_initialized
|
77
|
+
end
|
78
|
+
|
79
|
+
assert_equal :after_initialized, worker.after_initialized
|
80
|
+
assert_equal nil, worker.before_performed
|
81
|
+
assert_equal nil, worker.after_performed
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_simple_tasks
|
85
|
+
pool = Thpool.new
|
86
|
+
count = 0
|
87
|
+
|
88
|
+
100.times do
|
89
|
+
pool.perform do
|
90
|
+
count += 1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
assert_eventually do
|
95
|
+
count == 100
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_with_context
|
100
|
+
pool = Thpool.new(
|
101
|
+
:args => [ :test, 'arguments' ]
|
102
|
+
)
|
103
|
+
|
104
|
+
args = nil
|
105
|
+
|
106
|
+
pool.perform do |*_args|
|
107
|
+
args = _args
|
108
|
+
end
|
109
|
+
|
110
|
+
assert_eventually(1) do
|
111
|
+
args
|
112
|
+
end
|
113
|
+
|
114
|
+
assert_equal [ :test, 'arguments' ], args
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_with_recursion
|
118
|
+
pool = Thpool.new
|
119
|
+
|
120
|
+
times = 100
|
121
|
+
queued = 0
|
122
|
+
count = 0
|
123
|
+
|
124
|
+
times.times do |n|
|
125
|
+
pool.perform do
|
126
|
+
n.times do
|
127
|
+
queued += 1
|
128
|
+
pool.perform do |*args|
|
129
|
+
count += 1
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
assert_eventually(600) do
|
136
|
+
!pool.queue? and !pool.busy?
|
137
|
+
end
|
138
|
+
|
139
|
+
assert_equal (0..times - 1).inject(0) { |s, r| s + r }, queued
|
140
|
+
assert_equal queued, count
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative '../helper'
|
2
|
+
|
3
|
+
class TestThpoolWorker < Test::Unit::TestCase
|
4
|
+
def test_defaults
|
5
|
+
pool = Thpool.new
|
6
|
+
args = %w[ test arguments ]
|
7
|
+
|
8
|
+
worker = Thpool::Worker.new(pool, *args)
|
9
|
+
|
10
|
+
assert_equal pool, worker.pool
|
11
|
+
assert_equal nil, worker.block
|
12
|
+
assert_equal args, worker.args
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require_relative '../helper'
|
2
|
+
|
3
|
+
class TestThpool < Test::Unit::TestCase
|
4
|
+
def test_defaults
|
5
|
+
pool = Thpool.new
|
6
|
+
|
7
|
+
assert_equal false, pool.queue?
|
8
|
+
assert_equal 0, pool.queue_size
|
9
|
+
|
10
|
+
assert_equal 0, pool.workers_count
|
11
|
+
assert_equal 0, pool.workers_needed
|
12
|
+
assert_equal false, pool.workers_needed?
|
13
|
+
assert_equal 0, pool.waiting
|
14
|
+
|
15
|
+
assert_equal [ ], pool.workers
|
16
|
+
|
17
|
+
options_default = Thpool::OPTIONS_DEFAULT
|
18
|
+
|
19
|
+
assert_equal options_default[:worker_class], pool.worker_class
|
20
|
+
assert_equal options_default[:workers_min], pool.workers_min
|
21
|
+
assert_equal options_default[:workers_max], pool.workers_max
|
22
|
+
assert_equal options_default[:count_per_worker], pool.count_per_worker
|
23
|
+
assert_equal options_default[:args], pool.args
|
24
|
+
end
|
25
|
+
|
26
|
+
class ExampleWorker < Thpool::Worker
|
27
|
+
attr_reader :after_initialized
|
28
|
+
attr_reader :before_performed
|
29
|
+
attr_reader :after_performed
|
30
|
+
|
31
|
+
def after_initialize
|
32
|
+
@after_initialized = :after_initialized
|
33
|
+
end
|
34
|
+
|
35
|
+
def before_perform(block)
|
36
|
+
@before_performed = :before_performed
|
37
|
+
end
|
38
|
+
|
39
|
+
def after_perform(block)
|
40
|
+
@after_performed = :after_performed
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_options
|
45
|
+
pool = Thpool.new(
|
46
|
+
:worker_class => ExampleWorker,
|
47
|
+
:workers_min => 1,
|
48
|
+
:workers_max => 5,
|
49
|
+
:count_per_worker => 2,
|
50
|
+
:args => [ :example ]
|
51
|
+
)
|
52
|
+
|
53
|
+
assert_equal false, pool.queue?
|
54
|
+
assert_equal 0, pool.queue_size
|
55
|
+
|
56
|
+
assert_equal 1, pool.workers_count
|
57
|
+
assert_equal 1, pool.workers_needed
|
58
|
+
assert_equal false, pool.workers_needed?
|
59
|
+
|
60
|
+
assert_equal ExampleWorker, pool.worker_class
|
61
|
+
assert_equal 1, pool.workers_min
|
62
|
+
assert_equal 5, pool.workers_max
|
63
|
+
assert_equal 2, pool.count_per_worker
|
64
|
+
assert_equal [ :example ], pool.args
|
65
|
+
|
66
|
+
worker = pool.workers[0]
|
67
|
+
|
68
|
+
assert worker
|
69
|
+
assert worker.is_a?(ExampleWorker)
|
70
|
+
|
71
|
+
assert_equal pool, worker.pool
|
72
|
+
assert_equal nil, worker.block
|
73
|
+
assert_equal [ :example ], worker.args
|
74
|
+
|
75
|
+
assert_eventually(1) do
|
76
|
+
worker.after_initialized
|
77
|
+
end
|
78
|
+
|
79
|
+
assert_equal :after_initialized, worker.after_initialized
|
80
|
+
assert_equal nil, worker.before_performed
|
81
|
+
assert_equal nil, worker.after_performed
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_simple_tasks
|
85
|
+
pool = Thpool.new
|
86
|
+
count = 0
|
87
|
+
|
88
|
+
100.times do
|
89
|
+
pool.perform do
|
90
|
+
count += 1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
assert_eventually do
|
95
|
+
count == 100
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_with_context
|
100
|
+
pool = Thpool.new(
|
101
|
+
:args => [ :test, 'arguments' ]
|
102
|
+
)
|
103
|
+
|
104
|
+
args = nil
|
105
|
+
|
106
|
+
pool.perform do |*_args|
|
107
|
+
args = _args
|
108
|
+
end
|
109
|
+
|
110
|
+
assert_eventually(1) do
|
111
|
+
args
|
112
|
+
end
|
113
|
+
|
114
|
+
assert_equal [ :test, 'arguments' ], args
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_with_recursion
|
118
|
+
pool = Thpool.new
|
119
|
+
|
120
|
+
times = 100
|
121
|
+
queued = 0
|
122
|
+
count = 0
|
123
|
+
|
124
|
+
times.times do |n|
|
125
|
+
pool.perform do
|
126
|
+
n.times do
|
127
|
+
queued += 1
|
128
|
+
pool.perform do |*args|
|
129
|
+
count += 1
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
assert_eventually(600) do
|
136
|
+
!pool.queue? and !pool.busy?
|
137
|
+
end
|
138
|
+
|
139
|
+
assert_equal (0..times - 1).inject(0) { |s, r| s + r }, queued
|
140
|
+
assert_equal queued, count
|
141
|
+
end
|
142
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thpool
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Scott Tadman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: jeweler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.8.4
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.8.4
|
46
|
+
description: Simple thread pool/worker system
|
47
|
+
email: scott@twg.ca
|
48
|
+
executables: []
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files:
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
files:
|
54
|
+
- .document
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE.txt
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- VERSION
|
60
|
+
- lib/thpool.rb
|
61
|
+
- lib/thpool/worker.rb
|
62
|
+
- test/helper.rb
|
63
|
+
- test/unit/test_thpool.rb
|
64
|
+
- test/unit/test_thpool_worker.rb
|
65
|
+
- test/unit/test_worker_pool.rb
|
66
|
+
homepage: http://github.com/twg/thpool
|
67
|
+
licenses:
|
68
|
+
- MIT
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 1.8.23
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Threaded Pool/Worker System
|
91
|
+
test_files: []
|