thread_storm 0.4.0 → 0.5.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/CHANGELOG +5 -0
- data/README.rdoc +31 -31
- data/VERSION +1 -1
- data/lib/thread_storm/active_support.rb +17 -0
- data/lib/thread_storm/execution.rb +10 -8
- data/lib/thread_storm/sentinel.rb +18 -0
- data/lib/thread_storm/worker.rb +27 -14
- data/lib/thread_storm.rb +65 -25
- data/test/test_thread_storm.rb +110 -64
- data/thread_storm.gemspec +3 -3
- metadata +5 -5
- data/lib/thread_storm/queue.rb +0 -45
data/CHANGELOG
CHANGED
data/README.rdoc
CHANGED
@@ -11,22 +11,22 @@ Simple thread pool with a few advanced features.
|
|
11
11
|
|
12
12
|
== Example
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
storm = ThreadStorm.new :size => 2
|
15
|
+
storm.execute{ sleep(0.01); "a" }
|
16
|
+
storm.execute{ sleep(0.01); "b" }
|
17
|
+
storm.execute{ sleep(0.01); "c" }
|
18
|
+
storm.join # Should return in about 0.02 seconds... ;)
|
19
|
+
storm.values # ["a", "b", "c"]
|
20
20
|
|
21
21
|
== Execution state
|
22
22
|
|
23
23
|
You can query the state of an execution.
|
24
24
|
|
25
|
-
|
26
|
-
execution =
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
storm = ThreadStorm.new :size => 2
|
26
|
+
execution = storm.execute{ sleep(0.01); "a" }
|
27
|
+
storm.execute{ sleep(0.01); "b" }
|
28
|
+
storm.execute{ sleep(0.01); "c" }
|
29
|
+
storm.join
|
30
30
|
execution.started? # true
|
31
31
|
execution.finished? # true
|
32
32
|
execution.timed_out? # false
|
@@ -37,24 +37,24 @@ You can query the state of an execution.
|
|
37
37
|
|
38
38
|
You can restrict how long executions are allowed to run for.
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
40
|
+
storm = ThreadStorm.new :size => 2, :timeout => 0.02, :default_value => "failed"
|
41
|
+
storm.execute{ sleep(0.01); "a" }
|
42
|
+
storm.execute{ sleep(0.03); "b" }
|
43
|
+
storm.execute{ sleep(0.01); "c" }
|
44
|
+
storm.join
|
45
|
+
storm.executions[1].started? # true
|
46
|
+
storm.executions[1].finished? # true
|
47
|
+
storm.executions[1].timed_out? # true
|
48
|
+
storm.executions[1].duration # ~0.02
|
49
|
+
storm.executions[1].value # "failed"
|
50
50
|
|
51
51
|
== Error handling
|
52
52
|
|
53
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
54
|
|
55
|
-
|
56
|
-
execution =
|
57
|
-
|
55
|
+
storm = ThreadStorm.new :size => 2, :reraise => false, :default_value => "failure"
|
56
|
+
execution = storm.execute{ raise("busted"); "a" }
|
57
|
+
storm.join
|
58
58
|
execution.value # "failure"
|
59
59
|
execution.exception # RuntimeError: busted
|
60
60
|
|
@@ -64,27 +64,27 @@ ThreadStorm#join blocks until all pending executions are done running. It does
|
|
64
64
|
|
65
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
66
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
67
|
+
storm = ThreadStorm.new do |s|
|
68
|
+
s.execute{ "a" }
|
69
|
+
s.execute{ "b" }
|
70
|
+
s.execute{ "c" }
|
71
71
|
end
|
72
72
|
# At this point, #join and #shutdown have been called.
|
73
|
-
|
73
|
+
storm.values # ["a", "b", "c"]
|
74
74
|
|
75
75
|
== Configurable timeout method
|
76
76
|
|
77
77
|
<tt>Timeout.timeout</tt> is unreliable in MRI 1.8.x. To address this, you can have ThreadStorm use an alternative implementation.
|
78
78
|
|
79
79
|
require "system_timer"
|
80
|
-
|
80
|
+
storm = ThreadStorm.new :timeout_method => SystemTimer.method(:timeout) do
|
81
81
|
...
|
82
82
|
end
|
83
83
|
|
84
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
85
|
|
86
86
|
require "system_timer"
|
87
|
-
|
87
|
+
storm = ThreadStorm.new :timeout_method => Proc.new{ |seconds, &block| SystemTimer.timeout(seconds, &block) }
|
88
88
|
...
|
89
89
|
end
|
90
90
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
@@ -1,5 +1,22 @@
|
|
1
1
|
# Things I miss from active_support.
|
2
2
|
|
3
|
+
class Array #:nodoc:
|
4
|
+
|
5
|
+
def separate
|
6
|
+
selected = []
|
7
|
+
rejected = []
|
8
|
+
each do |item|
|
9
|
+
if yield(item)
|
10
|
+
selected << item
|
11
|
+
else
|
12
|
+
rejected << item
|
13
|
+
end
|
14
|
+
end
|
15
|
+
[selected, rejected]
|
16
|
+
end unless method_defined?(:separate)
|
17
|
+
|
18
|
+
end
|
19
|
+
|
3
20
|
class Hash #:nodoc:
|
4
21
|
|
5
22
|
def symbolize_keys
|
@@ -1,20 +1,22 @@
|
|
1
|
+
require "monitor"
|
2
|
+
|
1
3
|
class ThreadStorm
|
2
4
|
# Encapsulates a unit of work to be sent to the thread pool.
|
3
5
|
class Execution
|
4
6
|
attr_writer :value, :exception #:nodoc:
|
5
7
|
attr_reader :args, :block, :thread #:nodoc:
|
6
8
|
|
7
|
-
def initialize(args, &block) #:nodoc:
|
9
|
+
def initialize(args, default_value, &block) #:nodoc:
|
8
10
|
@args = args
|
11
|
+
@value = default_value
|
9
12
|
@block = block
|
10
13
|
@start_time = nil
|
11
14
|
@finish_time = nil
|
12
|
-
@value = nil
|
13
15
|
@exception = nil
|
14
16
|
@timed_out = false
|
15
17
|
@thread = nil
|
16
|
-
@
|
17
|
-
@
|
18
|
+
@lock = Monitor.new
|
19
|
+
@cond = @lock.new_cond
|
18
20
|
end
|
19
21
|
|
20
22
|
def start! #:nodoc:
|
@@ -33,9 +35,9 @@ class ThreadStorm
|
|
33
35
|
end
|
34
36
|
|
35
37
|
def finish! #:nodoc:
|
36
|
-
@
|
38
|
+
@lock.synchronize do
|
37
39
|
@finish_time = Time.now
|
38
|
-
@
|
40
|
+
@cond.signal
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
@@ -70,8 +72,8 @@ class ThreadStorm
|
|
70
72
|
|
71
73
|
# Block until this execution has finished running.
|
72
74
|
def join
|
73
|
-
@
|
74
|
-
@
|
75
|
+
@lock.synchronize do
|
76
|
+
@cond.wait_until{ finished? }
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "monitor"
|
2
|
+
|
3
|
+
class ThreadStorm
|
4
|
+
class Sentinel
|
5
|
+
attr_reader :e_cond, :p_cond
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@lock = Monitor.new
|
9
|
+
@e_cond = @lock.new_cond # execute condition
|
10
|
+
@p_cond = @lock.new_cond # pop condition
|
11
|
+
end
|
12
|
+
|
13
|
+
def synchronize
|
14
|
+
@lock.synchronize{ yield(@e_cond, @p_cond) }
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/lib/thread_storm/worker.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
class ThreadStorm
|
2
2
|
class Worker #:nodoc:
|
3
|
-
attr_reader :thread
|
3
|
+
attr_reader :thread, :execution
|
4
4
|
|
5
5
|
# Takes the threadsafe queue and options from the thread pool.
|
6
|
-
def initialize(queue, options)
|
7
|
-
@queue
|
8
|
-
@
|
9
|
-
@
|
6
|
+
def initialize(queue, sentinel, options)
|
7
|
+
@queue = queue
|
8
|
+
@sentinel = sentinel
|
9
|
+
@options = options
|
10
|
+
@execution = nil # Current execution we're working on.
|
11
|
+
@thread = Thread.new(self){ |me| me.run }
|
10
12
|
end
|
11
13
|
|
12
14
|
def timeout
|
@@ -24,17 +26,29 @@ class ThreadStorm
|
|
24
26
|
|
25
27
|
# Pop an execution off the queue and process it, or pass off control to a different thread.
|
26
28
|
def pop_and_process_execution
|
27
|
-
|
29
|
+
@sentinel.synchronize do |e_cond, p_cond|
|
30
|
+
# Become idle and signal that we're idle.
|
31
|
+
@execution = nil
|
32
|
+
e_cond.signal
|
33
|
+
|
34
|
+
# Give up the lock and wait until there is work to do.
|
35
|
+
p_cond.wait_while{ @queue.empty? }
|
36
|
+
|
37
|
+
# Get the work to do (implicitly becoming busy).
|
38
|
+
@execution = @queue.pop
|
39
|
+
end
|
40
|
+
|
41
|
+
process_execution_with_timeout unless die?
|
28
42
|
end
|
29
43
|
|
30
44
|
# Process the execution, handling timeouts and exceptions.
|
31
|
-
def process_execution_with_timeout
|
45
|
+
def process_execution_with_timeout
|
32
46
|
execution.start!
|
33
47
|
begin
|
34
48
|
if timeout
|
35
|
-
timeout_method.call(timeout){ process_execution
|
49
|
+
timeout_method.call(timeout){ process_execution }
|
36
50
|
else
|
37
|
-
process_execution
|
51
|
+
process_execution
|
38
52
|
end
|
39
53
|
rescue Timeout::Error => e
|
40
54
|
execution.timed_out!
|
@@ -46,18 +60,17 @@ class ThreadStorm
|
|
46
60
|
end
|
47
61
|
|
48
62
|
# Seriously, process the execution.
|
49
|
-
def process_execution
|
63
|
+
def process_execution
|
50
64
|
execution.value = execution.block.call(*execution.args)
|
51
65
|
end
|
52
66
|
|
53
|
-
|
54
|
-
|
55
|
-
@die = true
|
67
|
+
def busy?
|
68
|
+
!!@execution and not die?
|
56
69
|
end
|
57
70
|
|
58
71
|
# True if this worker's thread should die.
|
59
72
|
def die?
|
60
|
-
|
73
|
+
@execution == :die
|
61
74
|
end
|
62
75
|
|
63
76
|
end
|
data/lib/thread_storm.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require "thread"
|
2
2
|
require "timeout"
|
3
3
|
require "thread_storm/active_support"
|
4
|
-
require "thread_storm/
|
4
|
+
require "thread_storm/sentinel"
|
5
5
|
require "thread_storm/execution"
|
6
6
|
require "thread_storm/worker"
|
7
7
|
|
8
8
|
class ThreadStorm
|
9
9
|
|
10
|
-
# Array of executions in order as defined by calls to ThreadStorm#execute.
|
10
|
+
# Array of executions in order as they are defined by calls to ThreadStorm#execute.
|
11
11
|
attr_reader :executions
|
12
12
|
|
13
13
|
# Valid options are
|
@@ -16,16 +16,18 @@ class ThreadStorm
|
|
16
16
|
# :timeout_method => An object that implements something like Timeout.timeout via #call. Default is Timeout.method(:timeout).
|
17
17
|
# :default_value => Value of an execution if it times out or errors. Default is nil.
|
18
18
|
# :reraise => True if you want exceptions reraised when ThreadStorm#join is called. Default is true.
|
19
|
+
# :execute_blocks => True if you want #execute to block until there is an available thread. Default is false.
|
19
20
|
def initialize(options = {})
|
20
21
|
@options = options.option_merge :size => 2,
|
21
22
|
:timeout => nil,
|
22
23
|
:timeout_method => Timeout.method(:timeout),
|
23
24
|
:default_value => nil,
|
24
|
-
:reraise => true
|
25
|
-
|
25
|
+
:reraise => true,
|
26
|
+
:execute_blocks => false
|
27
|
+
@sentinel = Sentinel.new
|
28
|
+
@queue = []
|
26
29
|
@executions = []
|
27
|
-
@workers = (1..@options[:size]).collect{ Worker.new(@queue, @options) }
|
28
|
-
@start_time = Time.now
|
30
|
+
@workers = (1..@options[:size]).collect{ Worker.new(@queue, @sentinel, @options) }
|
29
31
|
if block_given?
|
30
32
|
yield(self)
|
31
33
|
join
|
@@ -33,25 +35,20 @@ class ThreadStorm
|
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
|
-
def size
|
38
|
+
def size #:nodoc:
|
37
39
|
@options[:size]
|
38
40
|
end
|
39
41
|
|
40
|
-
|
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.
|
42
|
+
# Creates an execution and schedules it to be run by the thread pool.
|
49
43
|
# Return value is a ThreadStorm::Execution.
|
50
44
|
def execute(*args, &block)
|
51
|
-
Execution.new(args, &block).tap do |execution|
|
52
|
-
|
53
|
-
|
54
|
-
|
45
|
+
Execution.new(args, default_value, &block).tap do |execution|
|
46
|
+
@sentinel.synchronize do |e_cond, p_cond|
|
47
|
+
e_cond.wait_while{ all_workers_busy? } if execute_blocks?
|
48
|
+
@queue << execution
|
49
|
+
@executions << execution
|
50
|
+
p_cond.signal
|
51
|
+
end
|
55
52
|
end
|
56
53
|
end
|
57
54
|
|
@@ -66,22 +63,65 @@ class ThreadStorm
|
|
66
63
|
|
67
64
|
# Calls ThreadStorm#join, then collects the values of each execution.
|
68
65
|
def values
|
69
|
-
join
|
70
|
-
@executions.collect{ |execution| execution.value }
|
66
|
+
join and @executions.collect{ |execution| execution.value }
|
71
67
|
end
|
72
68
|
|
73
69
|
# Signals the worker threads to terminate immediately (ignoring any pending
|
74
70
|
# executions) and blocks until they do.
|
75
71
|
def shutdown
|
76
|
-
@
|
77
|
-
|
72
|
+
@sentinel.synchronize do |e_cond, p_cond|
|
73
|
+
@queue.replace([:die] * size)
|
74
|
+
p_cond.broadcast
|
75
|
+
end
|
78
76
|
@workers.each{ |worker| worker.thread.join }
|
79
77
|
true
|
80
78
|
end
|
81
79
|
|
82
|
-
# Returns
|
80
|
+
# Returns workers that are currently running executions.
|
81
|
+
def busy_workers
|
82
|
+
@workers.select{ |worker| worker.busy? }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns an array of Ruby threads in the pool.
|
83
86
|
def threads
|
84
87
|
@workers.collect{ |worker| worker.thread }
|
85
88
|
end
|
86
89
|
|
90
|
+
# Removes executions stored at ThreadStorm#executions. You can selectively remove
|
91
|
+
# them by passing in a block or a symbol. The following two lines are equivalent.
|
92
|
+
# storm.clear_executions(:finished?)
|
93
|
+
# storm.clear_executions{ |e| e.finished? }
|
94
|
+
# Because of the nature of threading, the following code could happen:
|
95
|
+
# storm.clear_executions(:finished?)
|
96
|
+
# storm.executions.any?{ |e| e.finished? }
|
97
|
+
# Some executions could have finished between the two calls.
|
98
|
+
def clear_executions(method_name = nil, &block)
|
99
|
+
cleared, @executions = @executions.separate do |execution|
|
100
|
+
if block_given?
|
101
|
+
yield(execution)
|
102
|
+
else
|
103
|
+
execution.send(method_name)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
cleared
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def default_value #:nodoc:
|
112
|
+
@options[:default_value]
|
113
|
+
end
|
114
|
+
|
115
|
+
def reraise? #:nodoc:
|
116
|
+
@options[:reraise]
|
117
|
+
end
|
118
|
+
|
119
|
+
def execute_blocks? #:nodoc:
|
120
|
+
@options[:execute_blocks]
|
121
|
+
end
|
122
|
+
|
123
|
+
def all_workers_busy? #:nodoc:
|
124
|
+
@workers.all?{ |worker| worker.busy? }
|
125
|
+
end
|
126
|
+
|
87
127
|
end
|
data/test/test_thread_storm.rb
CHANGED
@@ -3,40 +3,40 @@ require 'helper'
|
|
3
3
|
class TestThreadStorm < Test::Unit::TestCase
|
4
4
|
|
5
5
|
def test_no_concurrency
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
assert_equal %w[one two three],
|
11
|
-
assert_all_threads_worked(
|
6
|
+
storm = ThreadStorm.new :size => 1
|
7
|
+
storm.execute{ sleep(0.01); "one" }
|
8
|
+
storm.execute{ sleep(0.01); "two" }
|
9
|
+
storm.execute{ sleep(0.01); "three" }
|
10
|
+
assert_equal %w[one two three], storm.values
|
11
|
+
assert_all_threads_worked(storm)
|
12
12
|
end
|
13
13
|
|
14
14
|
def test_partial_concurrency
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
assert_equal %w[one two three],
|
20
|
-
assert_all_threads_worked(
|
15
|
+
storm = ThreadStorm.new :size => 2
|
16
|
+
storm.execute{ sleep(0.01); "one" }
|
17
|
+
storm.execute{ sleep(0.01); "two" }
|
18
|
+
storm.execute{ sleep(0.01); "three" }
|
19
|
+
assert_equal %w[one two three], storm.values
|
20
|
+
assert_all_threads_worked(storm)
|
21
21
|
end
|
22
22
|
|
23
23
|
def test_full_concurrency
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
assert_equal %w[one two three],
|
29
|
-
assert_all_threads_worked(
|
24
|
+
storm = ThreadStorm.new :size => 3
|
25
|
+
storm.execute{ sleep(0.01); "one" }
|
26
|
+
storm.execute{ sleep(0.01); "two" }
|
27
|
+
storm.execute{ sleep(0.01); "three" }
|
28
|
+
assert_equal %w[one two three], storm.values
|
29
|
+
assert_all_threads_worked(storm)
|
30
30
|
end
|
31
31
|
|
32
32
|
def test_timeout_no_concurrency
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
assert_equal ["one", nil, "three"],
|
38
|
-
assert
|
39
|
-
assert_all_threads_worked(
|
33
|
+
storm = ThreadStorm.new :size => 1, :timeout => 0.015
|
34
|
+
storm.execute{ sleep(0.01); "one" }
|
35
|
+
storm.execute{ sleep(0.02); "two" }
|
36
|
+
storm.execute{ sleep(0.01); "three" }
|
37
|
+
assert_equal ["one", nil, "three"], storm.values
|
38
|
+
assert storm.executions[1].timed_out?
|
39
|
+
assert_all_threads_worked(storm)
|
40
40
|
end
|
41
41
|
|
42
42
|
# Tricky...
|
@@ -44,73 +44,119 @@ class TestThreadStorm < Test::Unit::TestCase
|
|
44
44
|
# 2 0.015s ------
|
45
45
|
# 3 0.01s ----
|
46
46
|
def test_timeout_partial_concurrency
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
assert_equal ["one", nil, "three"],
|
52
|
-
assert
|
53
|
-
assert_all_threads_worked(
|
47
|
+
storm = ThreadStorm.new :size => 2, :timeout => 0.015
|
48
|
+
storm.execute{ sleep(0.01); "one" }
|
49
|
+
storm.execute{ sleep(0.02); "two" }
|
50
|
+
storm.execute{ sleep(0.01); "three" }
|
51
|
+
assert_equal ["one", nil, "three"], storm.values
|
52
|
+
assert storm.executions[1].timed_out?
|
53
|
+
assert_all_threads_worked(storm)
|
54
54
|
end
|
55
55
|
|
56
56
|
def test_timeout_full_concurrency
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
assert_equal ["one", nil, "three"],
|
62
|
-
assert
|
63
|
-
assert_all_threads_worked(
|
57
|
+
storm = ThreadStorm.new :size => 3, :timeout => 0.015
|
58
|
+
storm.execute{ sleep(0.01); "one" }
|
59
|
+
storm.execute{ sleep(0.02); "two" }
|
60
|
+
storm.execute{ sleep(0.01); "three" }
|
61
|
+
assert_equal ["one", nil, "three"], storm.values
|
62
|
+
assert storm.executions[1].timed_out?
|
63
|
+
assert_all_threads_worked(storm)
|
64
64
|
end
|
65
65
|
|
66
66
|
def test_timeout_with_default_value
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
assert_equal ["one", "timed out", "three"],
|
72
|
-
assert
|
73
|
-
assert_all_threads_worked(
|
67
|
+
storm = ThreadStorm.new :size => 1, :timeout => 0.015, :default_value => "timed out"
|
68
|
+
storm.execute{ sleep(0.01); "one" }
|
69
|
+
storm.execute{ sleep(0.02); "two" }
|
70
|
+
storm.execute{ sleep(0.01); "three" }
|
71
|
+
assert_equal ["one", "timed out", "three"], storm.values
|
72
|
+
assert storm.executions[1].timed_out?
|
73
|
+
assert_all_threads_worked(storm)
|
74
74
|
end
|
75
75
|
|
76
76
|
def test_shutdown
|
77
77
|
original_thread_count = Thread.list.length
|
78
78
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
79
|
+
storm = ThreadStorm.new :size => 3
|
80
|
+
storm.execute{ sleep(0.01); "one" }
|
81
|
+
storm.execute{ sleep(0.01); "two" }
|
82
|
+
storm.execute{ sleep(0.01); "three" }
|
83
|
+
storm.join
|
84
84
|
|
85
85
|
assert_equal original_thread_count + 3, Thread.list.length
|
86
|
-
|
86
|
+
storm.shutdown
|
87
87
|
assert_equal original_thread_count, Thread.list.length
|
88
88
|
end
|
89
89
|
|
90
90
|
def test_shutdown_before_pop
|
91
|
-
|
92
|
-
|
91
|
+
storm = ThreadStorm.new :size => 3
|
92
|
+
storm.shutdown
|
93
93
|
end
|
94
94
|
|
95
95
|
def test_args
|
96
|
-
|
96
|
+
storm = ThreadStorm.new :size => 2
|
97
97
|
%w[one two three four five].each do |word|
|
98
|
-
|
98
|
+
storm.execute(word){ |w| sleep(0.01); w }
|
99
99
|
end
|
100
|
-
|
101
|
-
assert_equal %w[one two three four five],
|
100
|
+
storm.join
|
101
|
+
assert_equal %w[one two three four five], storm.values
|
102
102
|
end
|
103
103
|
|
104
104
|
def test_new_with_block
|
105
105
|
thread_count = Thread.list.length
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
106
|
+
storm = ThreadStorm.new :size => 1 do |storm|
|
107
|
+
storm.execute{ sleep(0.01); "one" }
|
108
|
+
storm.execute{ sleep(0.01); "two" }
|
109
|
+
storm.execute{ sleep(0.01); "three" }
|
110
110
|
end
|
111
111
|
assert_equal thread_count, Thread.list.length
|
112
|
-
assert_equal %w[one two three],
|
113
|
-
assert_all_threads_worked(
|
112
|
+
assert_equal %w[one two three], storm.values
|
113
|
+
assert_all_threads_worked(storm)
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_execute_blocks
|
117
|
+
t1 = Thread.new do
|
118
|
+
storm = ThreadStorm.new :size => 1, :execute_blocks => true
|
119
|
+
storm.execute{ sleep }
|
120
|
+
storm.execute{ nil }
|
121
|
+
end
|
122
|
+
t2 = Thread.new do
|
123
|
+
storm = ThreadStorm.new :size => 1, :execute_blocks => false
|
124
|
+
storm.execute{ sleep }
|
125
|
+
storm.execute{ nil }
|
126
|
+
end
|
127
|
+
sleep(0.1) # How else??
|
128
|
+
assert_equal "sleep", t1.status
|
129
|
+
assert_equal false, t2.status
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_clear_executions
|
133
|
+
storm = ThreadStorm.new :size => 3
|
134
|
+
storm.execute{ sleep }
|
135
|
+
storm.execute{ sleep(0.1) }
|
136
|
+
storm.execute{ sleep(0.1) }
|
137
|
+
sleep(0.2) # Ugh another test based on sleeping.
|
138
|
+
finished = storm.clear_executions(:finished?)
|
139
|
+
assert_equal 2, finished.length
|
140
|
+
assert_equal 1, storm.executions.length
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_execution_blocks_again
|
144
|
+
storm = ThreadStorm.new :size => 10, :execute_blocks => true
|
145
|
+
20.times{ storm.execute{ sleep(rand) } }
|
146
|
+
storm.join
|
147
|
+
storm.shutdown
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_for_deadlocks
|
151
|
+
ThreadStorm.new :size => 10, :execute_blocks => true do |storm|
|
152
|
+
20.times do
|
153
|
+
storm.execute do
|
154
|
+
ThreadStorm.new :size => 10, :timeout => 0.5 do |storm2|
|
155
|
+
20.times{ storm2.execute{ sleep(rand) } }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
114
160
|
end
|
115
161
|
|
116
162
|
end
|
data/thread_storm.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{thread_storm}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.5.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Christopher J. Bottaro"]
|
12
|
-
s.date = %q{2010-06-
|
12
|
+
s.date = %q{2010-06-21}
|
13
13
|
s.description = %q{Simple thread pool with timeouts, default values, error handling, state tracking and unit tests.}
|
14
14
|
s.email = %q{cjbottaro@alumni.cs.utexas.edu}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -27,7 +27,7 @@ Gem::Specification.new do |s|
|
|
27
27
|
"lib/thread_storm.rb",
|
28
28
|
"lib/thread_storm/active_support.rb",
|
29
29
|
"lib/thread_storm/execution.rb",
|
30
|
-
"lib/thread_storm/
|
30
|
+
"lib/thread_storm/sentinel.rb",
|
31
31
|
"lib/thread_storm/worker.rb",
|
32
32
|
"test/helper.rb",
|
33
33
|
"test/test_thread_storm.rb",
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thread_storm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 5
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.5.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Christopher J. Bottaro
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-06-
|
18
|
+
date: 2010-06-21 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -39,7 +39,7 @@ files:
|
|
39
39
|
- lib/thread_storm.rb
|
40
40
|
- lib/thread_storm/active_support.rb
|
41
41
|
- lib/thread_storm/execution.rb
|
42
|
-
- lib/thread_storm/
|
42
|
+
- lib/thread_storm/sentinel.rb
|
43
43
|
- lib/thread_storm/worker.rb
|
44
44
|
- test/helper.rb
|
45
45
|
- test/test_thread_storm.rb
|
data/lib/thread_storm/queue.rb
DELETED
@@ -1,45 +0,0 @@
|
|
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
|