thread_storm 0.4.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/.gitignore +21 -0
- data/CHANGELOG +20 -0
- data/LICENSE +20 -0
- data/README.rdoc +93 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/thread_storm/active_support.rb +26 -0
- data/lib/thread_storm/execution.rb +89 -0
- data/lib/thread_storm/queue.rb +45 -0
- data/lib/thread_storm/worker.rb +64 -0
- data/lib/thread_storm.rb +87 -0
- data/test/helper.rb +19 -0
- data/test/test_thread_storm.rb +116 -0
- data/thread_storm.gemspec +56 -0
- metadata +83 -0
data/.document
ADDED
data/.gitignore
ADDED
data/CHANGELOG
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
0.4.0
|
2
|
+
- Renamed to thread_storm... ugh.
|
3
|
+
- Simplified the shutdown process by using my own thread safe queue.
|
4
|
+
- Removed timing based tests.
|
5
|
+
- Much more efficient ThreadStorm#join.
|
6
|
+
- Added Execution#join.
|
7
|
+
- Configurable Timeout implementation.
|
8
|
+
|
9
|
+
0.3.0
|
10
|
+
- PoolParty#new now takes an optional block.
|
11
|
+
|
12
|
+
0.2.0
|
13
|
+
- Renamed to pool_party.
|
14
|
+
- Fixed PoolParty#shutdown for real.
|
15
|
+
|
16
|
+
0.1.1
|
17
|
+
- Fixed ThreadPool#shutdown.
|
18
|
+
|
19
|
+
0.1.0
|
20
|
+
- Initial version
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Christopher J. Bottaro
|
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.rdoc
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
= thread_storm
|
2
|
+
|
3
|
+
Simple thread pool with a few advanced features.
|
4
|
+
|
5
|
+
== Features
|
6
|
+
|
7
|
+
* execution state querying
|
8
|
+
* timeouts and configurable timeout implementation
|
9
|
+
* graceful error handling
|
10
|
+
* unit tests
|
11
|
+
|
12
|
+
== Example
|
13
|
+
|
14
|
+
pool = ThreadStorm.new :size => 2
|
15
|
+
pool.execute{ sleep(0.01); "a" }
|
16
|
+
pool.execute{ sleep(0.01); "b" }
|
17
|
+
pool.execute{ sleep(0.01); "c" }
|
18
|
+
pool.join # Should return in about 0.02 seconds... ;)
|
19
|
+
pool.values # ["a", "b", "c"]
|
20
|
+
|
21
|
+
== Execution state
|
22
|
+
|
23
|
+
You can query the state of an execution.
|
24
|
+
|
25
|
+
pool = ThreadStorm.new :size => 2
|
26
|
+
execution = pool.execute{ sleep(0.01); "a" }
|
27
|
+
pool.execute{ sleep(0.01); "b" }
|
28
|
+
pool.execute{ sleep(0.01); "c" }
|
29
|
+
pool.join
|
30
|
+
execution.started? # true
|
31
|
+
execution.finished? # true
|
32
|
+
execution.timed_out? # false
|
33
|
+
execution.duration # ~0.01
|
34
|
+
execution.value # "a"
|
35
|
+
|
36
|
+
== Timeouts
|
37
|
+
|
38
|
+
You can restrict how long executions are allowed to run for.
|
39
|
+
|
40
|
+
pool = ThreadStorm.new :size => 2, :timeout => 0.02, :default_value => "failed"
|
41
|
+
pool.execute{ sleep(0.01); "a" }
|
42
|
+
pool.execute{ sleep(0.03); "b" }
|
43
|
+
pool.execute{ sleep(0.01); "c" }
|
44
|
+
pool.join
|
45
|
+
pool.executions[1].started? # true
|
46
|
+
pool.executions[1].finished? # true
|
47
|
+
pool.executions[1].timed_out? # true
|
48
|
+
pool.executions[1].duration # ~0.02
|
49
|
+
pool.executions[1].value # "failed"
|
50
|
+
|
51
|
+
== Error handling
|
52
|
+
|
53
|
+
If an execution causes an exception, it will be reraised when ThreadStorm#join (or any other method that calls ThreadStorm#join) is called, unless you pass <tt>:reraise => false</tt> to ThreadStorm#new. The exception is stored in ThreadStorm::Execution#exception.
|
54
|
+
|
55
|
+
pool = ThreadStorm.new :size => 2, :reraise => false, :default_value => "failure"
|
56
|
+
execution = pool.execute{ raise("busted"); "a" }
|
57
|
+
pool.join
|
58
|
+
execution.value # "failure"
|
59
|
+
execution.exception # RuntimeError: busted
|
60
|
+
|
61
|
+
== Joining vs shutting down
|
62
|
+
|
63
|
+
ThreadStorm#join blocks until all pending executions are done running. It does not actually kill the thread storm's worker threads (incase you want to do more work). ThreadStorm#shutdown actually kills the worker threads.
|
64
|
+
|
65
|
+
Sometimes it can be a pain to remember to call #shutdown, so as a convenience, you can pass a block to ThreadStorm#new and #join and #shutdown will be called for you.
|
66
|
+
|
67
|
+
party = ThreadStorm.new do |p|
|
68
|
+
p.execute{ "a" }
|
69
|
+
p.execute{ "b" }
|
70
|
+
p.execute{ "c" }
|
71
|
+
end
|
72
|
+
# At this point, #join and #shutdown have been called.
|
73
|
+
party.values # ["a", "b", "c"]
|
74
|
+
|
75
|
+
== Configurable timeout method
|
76
|
+
|
77
|
+
<tt>Timeout.timeout</tt> is unreliable in MRI 1.8.x. To address this, you can have ThreadStorm use an alternative implementation.
|
78
|
+
|
79
|
+
require "system_timer"
|
80
|
+
party = ThreadStorm.new :timeout_method => SystemTimer.method(:timeout) do
|
81
|
+
...
|
82
|
+
end
|
83
|
+
|
84
|
+
The <tt>:timeout_method</tt> option takes any callable object (i.e. <tt>responds_to?(:call)</tt>) that implements something similar to <tt>Timeout.timeout</tt> (i.e. takes the same arguments and raises <tt>Timeout::Error</tt>).
|
85
|
+
|
86
|
+
require "system_timer"
|
87
|
+
party = ThreadStorm.new :timeout_method => Proc.new{ |seconds, &block| SystemTimer.timeout(seconds, &block) }
|
88
|
+
...
|
89
|
+
end
|
90
|
+
|
91
|
+
== Copyright
|
92
|
+
|
93
|
+
Copyright (c) 2010 Christopher J. Bottaro. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "thread_storm"
|
8
|
+
gem.summary = %Q{Simple thread pool with a few advanced features.}
|
9
|
+
gem.description = %Q{Simple thread pool with timeouts, default values, error handling, state tracking and unit tests.}
|
10
|
+
gem.email = "cjbottaro@alumni.cs.utexas.edu"
|
11
|
+
gem.homepage = "http://github.com/cjbottaro/thread_storm"
|
12
|
+
gem.authors = ["Christopher J. Bottaro"]
|
13
|
+
#gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/test_*.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "thread_storm #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.0
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Things I miss from active_support.
|
2
|
+
|
3
|
+
class Hash #:nodoc:
|
4
|
+
|
5
|
+
def symbolize_keys
|
6
|
+
inject({}){ |memo, (k, v)| memo[k.to_sym] = v; memo }
|
7
|
+
end unless method_defined?(:symbolize_keys)
|
8
|
+
|
9
|
+
def reverse_merge(other)
|
10
|
+
other.merge(self)
|
11
|
+
end unless method_defined?(:reverse_merge)
|
12
|
+
|
13
|
+
def option_merge(options)
|
14
|
+
symbolize_keys.reverse_merge(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class Object #:nodoc:
|
20
|
+
|
21
|
+
def tap
|
22
|
+
yield(self)
|
23
|
+
self
|
24
|
+
end unless method_defined?(:tap)
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
class ThreadStorm
|
2
|
+
# Encapsulates a unit of work to be sent to the thread pool.
|
3
|
+
class Execution
|
4
|
+
attr_writer :value, :exception #:nodoc:
|
5
|
+
attr_reader :args, :block, :thread #:nodoc:
|
6
|
+
|
7
|
+
def initialize(args, &block) #:nodoc:
|
8
|
+
@args = args
|
9
|
+
@block = block
|
10
|
+
@start_time = nil
|
11
|
+
@finish_time = nil
|
12
|
+
@value = nil
|
13
|
+
@exception = nil
|
14
|
+
@timed_out = false
|
15
|
+
@thread = nil
|
16
|
+
@mutex = Mutex.new
|
17
|
+
@join = ConditionVariable.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def start! #:nodoc:
|
21
|
+
@thread = Thread.current
|
22
|
+
@start_time = Time.now
|
23
|
+
end
|
24
|
+
|
25
|
+
# True if this execution has started running.
|
26
|
+
def started?
|
27
|
+
!!start_time
|
28
|
+
end
|
29
|
+
|
30
|
+
# When this execution began to run.
|
31
|
+
def start_time
|
32
|
+
@start_time
|
33
|
+
end
|
34
|
+
|
35
|
+
def finish! #:nodoc:
|
36
|
+
@mutex.synchronize do
|
37
|
+
@finish_time = Time.now
|
38
|
+
@join.signal
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# True if this execution has finished running.
|
43
|
+
def finished?
|
44
|
+
!!finish_time
|
45
|
+
end
|
46
|
+
|
47
|
+
# When this execution finished running (either cleanly or with error).
|
48
|
+
def finish_time
|
49
|
+
@finish_time
|
50
|
+
end
|
51
|
+
|
52
|
+
# How long this this execution ran for (i.e. finish_time - start_time)
|
53
|
+
# or if it hasn't finished, how long it has been running for.
|
54
|
+
def duration
|
55
|
+
if finished?
|
56
|
+
finish_time - start_time
|
57
|
+
else
|
58
|
+
Time.now - start_time
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def timed_out! #:nodoc:
|
63
|
+
@timed_out = true
|
64
|
+
end
|
65
|
+
|
66
|
+
# True if the execution went over the timeout limit.
|
67
|
+
def timed_out?
|
68
|
+
!!@timed_out
|
69
|
+
end
|
70
|
+
|
71
|
+
# Block until this execution has finished running.
|
72
|
+
def join
|
73
|
+
@mutex.synchronize do
|
74
|
+
@join.wait(@mutex) unless finished?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# If this execution finished with an exception, it is stored here.
|
79
|
+
def exception
|
80
|
+
@exception
|
81
|
+
end
|
82
|
+
|
83
|
+
# The value returned by the execution's code block.
|
84
|
+
def value
|
85
|
+
@value
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
class ThreadStorm
|
4
|
+
class Queue #:nodoc:
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@lock = Mutex.new
|
8
|
+
@cond = ConditionVariable.new
|
9
|
+
@die = false
|
10
|
+
@queue = []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Pushes a value on the queue and wakes up the next thread waiting on #pop.
|
14
|
+
def push(value)
|
15
|
+
@lock.synchronize do
|
16
|
+
@queue.push(value)
|
17
|
+
@cond.signal # Wake up next thread waiting on #pop.
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Pops a value of the queue. Blocks if the queue is empty.
|
22
|
+
def pop
|
23
|
+
@lock.synchronize do
|
24
|
+
@cond.wait(@lock) if @queue.empty? and not die?
|
25
|
+
@queue.pop
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Clears the queue. Any calls to #pop will immediately return with nil.
|
30
|
+
def die!
|
31
|
+
@lock.synchronize do
|
32
|
+
@die = true
|
33
|
+
@queue.clear
|
34
|
+
@cond.broadcast # Wake up any threads waiting on #pop.
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def die?
|
41
|
+
!!@die
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class ThreadStorm
|
2
|
+
class Worker #:nodoc:
|
3
|
+
attr_reader :thread
|
4
|
+
|
5
|
+
# Takes the threadsafe queue and options from the thread pool.
|
6
|
+
def initialize(queue, options)
|
7
|
+
@queue = queue
|
8
|
+
@options = options
|
9
|
+
@thread = Thread.new(self){ |me| me.run }
|
10
|
+
end
|
11
|
+
|
12
|
+
def timeout
|
13
|
+
@timeout ||= @options[:timeout]
|
14
|
+
end
|
15
|
+
|
16
|
+
def timeout_method
|
17
|
+
@timeout_method ||= @options[:timeout_method]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Pop executions and process them until we're signaled to die.
|
21
|
+
def run
|
22
|
+
pop_and_process_execution while not die?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Pop an execution off the queue and process it, or pass off control to a different thread.
|
26
|
+
def pop_and_process_execution
|
27
|
+
execution = @queue.pop and process_execution_with_timeout(execution)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Process the execution, handling timeouts and exceptions.
|
31
|
+
def process_execution_with_timeout(execution)
|
32
|
+
execution.start!
|
33
|
+
begin
|
34
|
+
if timeout
|
35
|
+
timeout_method.call(timeout){ process_execution(execution) }
|
36
|
+
else
|
37
|
+
process_execution(execution)
|
38
|
+
end
|
39
|
+
rescue Timeout::Error => e
|
40
|
+
execution.timed_out!
|
41
|
+
rescue Exception => e
|
42
|
+
execution.exception = e
|
43
|
+
ensure
|
44
|
+
execution.finish!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Seriously, process the execution.
|
49
|
+
def process_execution(execution)
|
50
|
+
execution.value = execution.block.call(*execution.args)
|
51
|
+
end
|
52
|
+
|
53
|
+
# So the thread pool can signal this worker's thread to end.
|
54
|
+
def die!
|
55
|
+
@die = true
|
56
|
+
end
|
57
|
+
|
58
|
+
# True if this worker's thread should die.
|
59
|
+
def die?
|
60
|
+
!!@die
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
data/lib/thread_storm.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require "thread"
|
2
|
+
require "timeout"
|
3
|
+
require "thread_storm/active_support"
|
4
|
+
require "thread_storm/queue"
|
5
|
+
require "thread_storm/execution"
|
6
|
+
require "thread_storm/worker"
|
7
|
+
|
8
|
+
class ThreadStorm
|
9
|
+
|
10
|
+
# Array of executions in order as defined by calls to ThreadStorm#execute.
|
11
|
+
attr_reader :executions
|
12
|
+
|
13
|
+
# Valid options are
|
14
|
+
# :size => How many threads to spawn. Default is 2.
|
15
|
+
# :timeout => Max time an execution is allowed to run before terminating it. Default is nil (no timeout).
|
16
|
+
# :timeout_method => An object that implements something like Timeout.timeout via #call. Default is Timeout.method(:timeout).
|
17
|
+
# :default_value => Value of an execution if it times out or errors. Default is nil.
|
18
|
+
# :reraise => True if you want exceptions reraised when ThreadStorm#join is called. Default is true.
|
19
|
+
def initialize(options = {})
|
20
|
+
@options = options.option_merge :size => 2,
|
21
|
+
:timeout => nil,
|
22
|
+
:timeout_method => Timeout.method(:timeout),
|
23
|
+
:default_value => nil,
|
24
|
+
:reraise => true
|
25
|
+
@queue = Queue.new # This is threadsafe.
|
26
|
+
@executions = []
|
27
|
+
@workers = (1..@options[:size]).collect{ Worker.new(@queue, @options) }
|
28
|
+
@start_time = Time.now
|
29
|
+
if block_given?
|
30
|
+
yield(self)
|
31
|
+
join
|
32
|
+
shutdown
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def size
|
37
|
+
@options[:size]
|
38
|
+
end
|
39
|
+
|
40
|
+
def default_value
|
41
|
+
@options[:default_value]
|
42
|
+
end
|
43
|
+
|
44
|
+
def reraise?
|
45
|
+
@options[:reraise]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create and execution and schedules it to be run by the thread pool.
|
49
|
+
# Return value is a ThreadStorm::Execution.
|
50
|
+
def execute(*args, &block)
|
51
|
+
Execution.new(args, &block).tap do |execution|
|
52
|
+
execution.value = default_value
|
53
|
+
@executions << execution
|
54
|
+
@queue.push(execution)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Block until all pending executions are finished running.
|
59
|
+
# Reraises any exceptions caused by executions unless <tt>:reraise => false</tt> was passed to ThreadStorm#new.
|
60
|
+
def join
|
61
|
+
@executions.each do |execution|
|
62
|
+
execution.join
|
63
|
+
raise execution.exception if execution.exception and reraise?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Calls ThreadStorm#join, then collects the values of each execution.
|
68
|
+
def values
|
69
|
+
join
|
70
|
+
@executions.collect{ |execution| execution.value }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Signals the worker threads to terminate immediately (ignoring any pending
|
74
|
+
# executions) and blocks until they do.
|
75
|
+
def shutdown
|
76
|
+
@workers.each{ |worker| worker.die! }
|
77
|
+
@queue.die!
|
78
|
+
@workers.each{ |worker| worker.thread.join }
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns an array of threads in the pool.
|
83
|
+
def threads
|
84
|
+
@workers.collect{ |worker| worker.thread }
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
|
+
require 'thread_storm'
|
8
|
+
|
9
|
+
class Test::Unit::TestCase
|
10
|
+
|
11
|
+
def assert_in_delta(expected, actual, delta)
|
12
|
+
assert (expected - actual).abs < delta, "#{actual} is not within #{delta} of #{expected}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def assert_all_threads_worked(pool)
|
16
|
+
assert_equal pool.threads.to_set, pool.executions.collect{ |e| e.thread }.to_set
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestThreadStorm < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_no_concurrency
|
6
|
+
pool = ThreadStorm.new :size => 1
|
7
|
+
pool.execute{ sleep(0.01); "one" }
|
8
|
+
pool.execute{ sleep(0.01); "two" }
|
9
|
+
pool.execute{ sleep(0.01); "three" }
|
10
|
+
assert_equal %w[one two three], pool.values
|
11
|
+
assert_all_threads_worked(pool)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_partial_concurrency
|
15
|
+
pool = ThreadStorm.new :size => 2
|
16
|
+
pool.execute{ sleep(0.01); "one" }
|
17
|
+
pool.execute{ sleep(0.01); "two" }
|
18
|
+
pool.execute{ sleep(0.01); "three" }
|
19
|
+
assert_equal %w[one two three], pool.values
|
20
|
+
assert_all_threads_worked(pool)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_full_concurrency
|
24
|
+
pool = ThreadStorm.new :size => 3
|
25
|
+
pool.execute{ sleep(0.01); "one" }
|
26
|
+
pool.execute{ sleep(0.01); "two" }
|
27
|
+
pool.execute{ sleep(0.01); "three" }
|
28
|
+
assert_equal %w[one two three], pool.values
|
29
|
+
assert_all_threads_worked(pool)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_timeout_no_concurrency
|
33
|
+
pool = ThreadStorm.new :size => 1, :timeout => 0.015
|
34
|
+
pool.execute{ sleep(0.01); "one" }
|
35
|
+
pool.execute{ sleep(0.02); "two" }
|
36
|
+
pool.execute{ sleep(0.01); "three" }
|
37
|
+
assert_equal ["one", nil, "three"], pool.values
|
38
|
+
assert pool.executions[1].timed_out?
|
39
|
+
assert_all_threads_worked(pool)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Tricky...
|
43
|
+
# 1 0.01s ----
|
44
|
+
# 2 0.015s ------
|
45
|
+
# 3 0.01s ----
|
46
|
+
def test_timeout_partial_concurrency
|
47
|
+
pool = ThreadStorm.new :size => 2, :timeout => 0.015
|
48
|
+
pool.execute{ sleep(0.01); "one" }
|
49
|
+
pool.execute{ sleep(0.02); "two" }
|
50
|
+
pool.execute{ sleep(0.01); "three" }
|
51
|
+
assert_equal ["one", nil, "three"], pool.values
|
52
|
+
assert pool.executions[1].timed_out?
|
53
|
+
assert_all_threads_worked(pool)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_timeout_full_concurrency
|
57
|
+
pool = ThreadStorm.new :size => 3, :timeout => 0.015
|
58
|
+
pool.execute{ sleep(0.01); "one" }
|
59
|
+
pool.execute{ sleep(0.02); "two" }
|
60
|
+
pool.execute{ sleep(0.01); "three" }
|
61
|
+
assert_equal ["one", nil, "three"], pool.values
|
62
|
+
assert pool.executions[1].timed_out?
|
63
|
+
assert_all_threads_worked(pool)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_timeout_with_default_value
|
67
|
+
pool = ThreadStorm.new :size => 1, :timeout => 0.015, :default_value => "timed out"
|
68
|
+
pool.execute{ sleep(0.01); "one" }
|
69
|
+
pool.execute{ sleep(0.02); "two" }
|
70
|
+
pool.execute{ sleep(0.01); "three" }
|
71
|
+
assert_equal ["one", "timed out", "three"], pool.values
|
72
|
+
assert pool.executions[1].timed_out?
|
73
|
+
assert_all_threads_worked(pool)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_shutdown
|
77
|
+
original_thread_count = Thread.list.length
|
78
|
+
|
79
|
+
pool = ThreadStorm.new :size => 3
|
80
|
+
pool.execute{ sleep(0.01); "one" }
|
81
|
+
pool.execute{ sleep(0.01); "two" }
|
82
|
+
pool.execute{ sleep(0.01); "three" }
|
83
|
+
pool.join
|
84
|
+
|
85
|
+
assert_equal original_thread_count + 3, Thread.list.length
|
86
|
+
pool.shutdown
|
87
|
+
assert_equal original_thread_count, Thread.list.length
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_shutdown_before_pop
|
91
|
+
pool = ThreadStorm.new :size => 3
|
92
|
+
pool.shutdown
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_args
|
96
|
+
pool = ThreadStorm.new :size => 2
|
97
|
+
%w[one two three four five].each do |word|
|
98
|
+
pool.execute(word){ |w| sleep(0.01); w }
|
99
|
+
end
|
100
|
+
pool.join
|
101
|
+
assert_equal %w[one two three four five], pool.values
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_new_with_block
|
105
|
+
thread_count = Thread.list.length
|
106
|
+
pool = ThreadStorm.new :size => 1 do |party|
|
107
|
+
party.execute{ sleep(0.01); "one" }
|
108
|
+
party.execute{ sleep(0.01); "two" }
|
109
|
+
party.execute{ sleep(0.01); "three" }
|
110
|
+
end
|
111
|
+
assert_equal thread_count, Thread.list.length
|
112
|
+
assert_equal %w[one two three], pool.values
|
113
|
+
assert_all_threads_worked(pool)
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{thread_storm}
|
8
|
+
s.version = "0.4.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Christopher J. Bottaro"]
|
12
|
+
s.date = %q{2010-06-07}
|
13
|
+
s.description = %q{Simple thread pool with timeouts, default values, error handling, state tracking and unit tests.}
|
14
|
+
s.email = %q{cjbottaro@alumni.cs.utexas.edu}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"CHANGELOG",
|
23
|
+
"LICENSE",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"lib/thread_storm.rb",
|
28
|
+
"lib/thread_storm/active_support.rb",
|
29
|
+
"lib/thread_storm/execution.rb",
|
30
|
+
"lib/thread_storm/queue.rb",
|
31
|
+
"lib/thread_storm/worker.rb",
|
32
|
+
"test/helper.rb",
|
33
|
+
"test/test_thread_storm.rb",
|
34
|
+
"thread_storm.gemspec"
|
35
|
+
]
|
36
|
+
s.homepage = %q{http://github.com/cjbottaro/thread_storm}
|
37
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.rubygems_version = %q{1.3.7}
|
40
|
+
s.summary = %q{Simple thread pool with a few advanced features.}
|
41
|
+
s.test_files = [
|
42
|
+
"test/helper.rb",
|
43
|
+
"test/test_thread_storm.rb"
|
44
|
+
]
|
45
|
+
|
46
|
+
if s.respond_to? :specification_version then
|
47
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
48
|
+
s.specification_version = 3
|
49
|
+
|
50
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
51
|
+
else
|
52
|
+
end
|
53
|
+
else
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thread_storm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Christopher J. Bottaro
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-06-07 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Simple thread pool with timeouts, default values, error handling, state tracking and unit tests.
|
23
|
+
email: cjbottaro@alumni.cs.utexas.edu
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- LICENSE
|
30
|
+
- README.rdoc
|
31
|
+
files:
|
32
|
+
- .document
|
33
|
+
- .gitignore
|
34
|
+
- CHANGELOG
|
35
|
+
- LICENSE
|
36
|
+
- README.rdoc
|
37
|
+
- Rakefile
|
38
|
+
- VERSION
|
39
|
+
- lib/thread_storm.rb
|
40
|
+
- lib/thread_storm/active_support.rb
|
41
|
+
- lib/thread_storm/execution.rb
|
42
|
+
- lib/thread_storm/queue.rb
|
43
|
+
- lib/thread_storm/worker.rb
|
44
|
+
- test/helper.rb
|
45
|
+
- test/test_thread_storm.rb
|
46
|
+
- thread_storm.gemspec
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/cjbottaro/thread_storm
|
49
|
+
licenses: []
|
50
|
+
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options:
|
53
|
+
- --charset=UTF-8
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.3.7
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Simple thread pool with a few advanced features.
|
81
|
+
test_files:
|
82
|
+
- test/helper.rb
|
83
|
+
- test/test_thread_storm.rb
|