ActionPool 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/ActionPool-0.0.1.gem CHANGED
Binary file
data/ActionPool-0.1.0.gem CHANGED
Binary file
File without changes
data/CHANGELOG ADDED
@@ -0,0 +1,6 @@
1
+ 0.2.0
2
+ - Added argument support for passed tasks (thanks simonmenke)
3
+ - Faster pool resizing
4
+ - Smarter thread creation to limit unneeded creation
5
+ - Allow floats for timeouts to provide better control
6
+ - Test verified support for 1.8.6, 1.8.7, 1.9.1 and JRuby 1.4.0RC1
data/README.rdoc CHANGED
@@ -1,6 +1,6 @@
1
1
  == ActionPool
2
2
 
3
- ActionPool is just a simple thread pool. It allows for various contraints and resizing in a pretty easy and unobtrusive manner. You can set limits on how long tasks are worked on, as well as on the life of a thread. For things that like to use lots threads, it can be helpful to reuse threads instead of constantly recreating them.
3
+ ActionPool is just a simple thread pool. It allows for various constraints and resizing in a pretty easy and unobtrusive manner. You can set limits on how long tasks are worked on, as well as on the life of a thread. For things that like to use lots threads, it can be helpful to reuse threads instead of constantly recreating them.
4
4
 
5
5
  === install (easy):
6
6
 
@@ -121,7 +121,7 @@ Which results in:
121
121
  Thread: #<Thread:0x86c1080>
122
122
  Thread pool woke me up: Wakeup main thread
123
123
 
124
- Our pool starts with a single thread that is occupied by the sleeping task waiting to raise an exception. As we begin to add new tasks, the pool grows to accommidate the growing number of tasks, until it reaches the maximum threshold of 3. At that point, the pool simply processes the tasks until the task list is empty.
124
+ Our pool starts with a single thread that is occupied by the sleeping task waiting to raise an exception. As we begin to add new tasks, the pool grows to accommodate the growing number of tasks, until it reaches the maximum threshold of 3. At that point, the pool simply processes the tasks until the task list is empty.
125
125
 
126
126
  The pool also has the ability to limit the amount of time a thread spends working on a given task. By default, a thread will work on a given task until the task is completed, or the pool is shutdown. However, as the following example shows, it is very easy to limit this time to avoid the pool being bogged down on long running tasks:
127
127
 
@@ -158,16 +158,33 @@ If you have a number of tasks you would like to schedule at once, it is easy wit
158
158
  lock = Mutex.new
159
159
  tasks = [].fill(lambda{ lock.synchronize{ a += 1 } }, 0..19)
160
160
  pool.add_jobs(tasks)
161
- sleep(0.5)
161
+ pool.shutdown
162
162
  puts "Result: #{a}"
163
163
 
164
164
  Results:
165
165
 
166
166
  Result: 20
167
167
 
168
- === Footer
168
+ Passing arguments to tasks is now available as well:
169
169
 
170
- I hope this library is useful. If you find any bugs, or need any help, you can find me on DALnet and Freenode.
170
+ require 'actionpool'
171
+
172
+ pool = ActionPool::Pool.new
173
+ string = 'Hello world'
174
+ puts "Original: #{string}. ID: #{string.object_id}"
175
+ pool << [lambda{|var| puts "Passed: #{var}. ID: #{var.object_id}"}, [string.dup]]
176
+ pool << [lambda{|a,b| puts "Passed: #{a} | #{b}. ID: #{a.object_id} | #{b.object_id}"}, [string, string.dup]]
177
+ pool.shutdown
178
+
179
+ Results:
180
+
181
+ Original: Hello world. ID: 70651630
182
+ Passed: Hello world. ID: 70651250
183
+ Passed: Hello world | Hello world. ID: 70651630 | 70651100
184
+
185
+ == Last remarks
186
+
187
+ If you find any bugs, please report them through {github}[http://github.com/spox/actionpool/issues]. If you are in need of any help, you can generally find me on DALnet and Freenode.
171
188
 
172
189
  == License
173
190
 
data/actionpool.gemspec CHANGED
@@ -2,12 +2,12 @@ spec = Gem::Specification.new do |s|
2
2
  s.name = 'ActionPool'
3
3
  s.author = %q(spox)
4
4
  s.email = %q(spox@modspox.com)
5
- s.version = '0.1.0'
5
+ s.version = '0.2.0'
6
6
  s.summary = %q(Thread Pool)
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.files = Dir['**/*']
9
9
  s.rdoc_options = %w(--title ActionPool --main README.rdoc --line-numbers)
10
- s.extra_rdoc_files = %w(README.rdoc)
10
+ s.extra_rdoc_files = %w(README.rdoc CHANGELOG)
11
11
  s.require_paths = %w(lib)
12
12
  s.required_ruby_version = '>= 1.8.6'
13
13
  s.homepage = %q(http://github.com/spox/actionpool)
@@ -20,4 +20,4 @@ spec = Gem::Specification.new do |s|
20
20
  end
21
21
  end
22
22
  s.description = description[1..-1].join(" ")
23
- end
23
+ end
data/lib/actionpool.rb CHANGED
@@ -1 +1,7 @@
1
+ begin
2
+ require 'fastthread'
3
+ rescue LoadError
4
+ # we don't care if it's available
5
+ # just load it if it's around
6
+ end
1
7
  require 'actionpool/Pool'
@@ -1,9 +1,8 @@
1
- require 'logger'
2
-
3
1
  module ActionPool
4
2
  class LogHelper
5
3
 
6
4
  def initialize(logger=nil)
5
+ require 'logger'
7
6
  @logger = logger
8
7
  end
9
8
 
@@ -17,50 +17,69 @@ module ActionPool
17
17
  @logger = LogHelper.new(args[:logger])
18
18
  @queue = ActionPool::Queue.new
19
19
  @threads = []
20
- @thread_timeout = args[:t_to] ? args[:t_to] : 60
21
- @action_timeout = args[:a_to] ? args[:a_to] : nil
20
+ @lock = Mutex.new
21
+ @thread_timeout = args[:t_to] ? args[:t_to] : 0
22
+ @action_timeout = args[:a_to] ? args[:a_to] : 0
22
23
  @min_threads = args[:min_threads] ? args[:min_threads] : 10
23
24
  @max_threads = args[:max_threads] ? args[:max_threads] : 100
24
25
  @respond_to = ::Thread.current
25
- @min_threads.times{create_thread}
26
+ create_thread
26
27
  end
27
28
 
28
29
  # force:: force creation of a new thread
29
30
  # Create a new thread for pool. Returns newly created ActionPool::Thread or
30
31
  # nil if pool has reached maximum threads
31
32
  def create_thread(force=false)
32
- return nil unless @threads.size < @max_threads || force
33
- @logger.info('Pool is creating a new thread')
34
- pt = ActionPool::Thread.new(:pool => self, :respond_thread => @respond_to, :a_timeout => @action_timeout, :t_timeout => @thread_timeout, :logger => @logger)
35
- @threads << pt
33
+ pt = nil
34
+ @lock.synchronize do
35
+ if(@threads.size < @max_threads || force)
36
+ @logger.info('Pool is creating a new thread')
37
+ (min - size > 0 ? min - size : 1).times do |i|
38
+ pt = ActionPool::Thread.new(:pool => self, :respond_thread => @respond_to, :a_timeout => @action_timeout, :t_timeout => @thread_timeout, :logger => @logger)
39
+ @threads << pt
40
+ end
41
+ else
42
+ @logger.info('Pool is at maximum size. Not creating new thread')
43
+ end
44
+ end
36
45
  return pt
37
46
  end
38
47
 
39
48
  # force:: force immediate stop
40
49
  # Stop the pool
41
50
  def shutdown(force=false)
51
+ args = [:wait]
52
+ args += [:force] if force
42
53
  @logger.info("Pool is now shutting down #{force ? 'using force' : ''}")
43
- @threads.each{|t|t.stop(force)}
44
- until(size < 1) do
45
- @queue << lambda{}
46
- sleep(0.1)
54
+ @queue.wait_empty
55
+ while(t = @threads.pop) do
56
+ t.stop(*args)
47
57
  end
48
58
  nil
49
59
  end
50
60
 
51
- # action:: proc to be executed
61
+ # action:: proc to be executed or array of [proc, [*args]]
52
62
  # Add a new proc/lambda to be executed (alias for queue)
53
63
  def <<(action)
54
- queue(action)
64
+ case action
65
+ when Proc
66
+ queue(action)
67
+ when Array
68
+ raise ArgumentError.new('Actions to be processed by the pool must be a proc/lambda or [proc/lambda, [*args]]') unless action.size == 2 and action[0].is_a?(Proc) and action[1].is_a?(Array)
69
+ queue(*action.flatten)
70
+ else
71
+ raise ArgumentError.new('Actions to be processed by the pool must be a proc/lambda or [proc/lambda, [*args]]')
72
+ end
55
73
  nil
56
74
  end
57
75
 
58
76
  # action:: proc to be executed
59
77
  # Add a new proc/lambda to be executed
60
- def queue(action)
78
+ def queue(action, *args)
61
79
  raise ArgumentError.new('Expecting block') unless action.is_a?(Proc)
62
- @queue << action
63
- create_thread if @queue.length > 0 && @queue.num_waiting < 1 # only start a new thread if we need it
80
+ @queue << [action, args]
81
+ ::Thread.pass
82
+ create_thread if @queue.num_waiting < 1 # only start a new thread if we need it
64
83
  end
65
84
 
66
85
  # jobs:: Array of proc/lambdas
@@ -70,11 +89,18 @@ module ActionPool
70
89
  @queue.pause
71
90
  begin
72
91
  jobs.each do |job|
73
- raise ArgumentError.new('Jobs to be processed by the pool must be a proc/lambda') unless job.is_a?(Proc)
74
- @queue << job
92
+ case job
93
+ when Proc
94
+ @queue << [job, []]
95
+ when Array
96
+ raise ArgumentError.new('Jobs to be processed by the pool must be a proc/lambda or [proc/lambda, [*args]]') unless job.size == 2 and job[0].is_a?(Proc) and job[1].is_a?(Array)
97
+ @queue << job
98
+ else
99
+ raise ArgumentError.new('Jobs to be processed by the pool must be a proc/lambda or [proc/lambda, [*args]]')
100
+ end
75
101
  end
76
102
  ensure
77
- while(create_thread)do;end; # make sure we get our pool population up
103
+ create_thread
78
104
  @queue.unpause
79
105
  end
80
106
  true
@@ -82,8 +108,8 @@ module ActionPool
82
108
 
83
109
  # block:: block to process
84
110
  # Adds a block to be processed
85
- def process(&block)
86
- queue(block)
111
+ def process(*args, &block)
112
+ queue(block, *args)
87
113
  nil
88
114
  end
89
115
 
@@ -108,6 +134,7 @@ module ActionPool
108
134
  m = m.to_i
109
135
  raise ArgumentError.new('Maximum value must be greater than 0') unless m > 0
110
136
  @max_threads = m
137
+ resize if m < size
111
138
  m
112
139
  end
113
140
 
@@ -117,13 +144,14 @@ module ActionPool
117
144
  m = m.to_i
118
145
  raise ArgumentError.new("Minimum value must be greater than 0 and less than or equal to maximum (#{max})") unless m > 0 && m <= max
119
146
  @min_threads = m
120
- resize if m < size
121
147
  m
122
148
  end
123
149
 
124
150
  # t:: ActionPool::Thread to remove
125
151
  # Removes a thread from the pool
126
152
  def remove(t)
153
+ raise ArgumentError.new('Expecting an ActionPool::Thread object') unless t.is_a?(ActionPool::Thread)
154
+ t.stop
127
155
  if(@threads.include?(t))
128
156
  @threads.delete(t)
129
157
  return true
@@ -150,9 +178,10 @@ module ActionPool
150
178
  # t:: timeout in seconds (nil for infinite)
151
179
  # Set maximum allowed time thead may idle in pool
152
180
  def thread_timeout=(t)
153
- t = to_i unless t.nil?
154
- raise ArgumentError.new('Value must be great than zero or nil') unless t.nil? || t > 0
181
+ t = t.to_f
182
+ raise ArgumentError.new('Value must be great than zero or nil') unless t > 0
155
183
  @thread_timeout = t
184
+ @threads.each{|thread|thread.thread_timeout = t}
156
185
  t
157
186
  end
158
187
 
@@ -160,9 +189,10 @@ module ActionPool
160
189
  # Set maximum allowed time thread may work
161
190
  # on a given action
162
191
  def action_timeout=(t)
163
- t = to_i unless t.nil?
164
- raise ArgumentError.new('Value must be great than zero or nil') unless t.nil? || t > 0
192
+ t = t.to_f
193
+ raise ArgumentError.new('Value must be great than zero or nil') unless t > 0
165
194
  @action_timeout = t
195
+ @threads.each{|thread|thread.action_timeout = t}
166
196
  t
167
197
  end
168
198
 
@@ -176,14 +206,30 @@ module ActionPool
176
206
  @queue.size
177
207
  end
178
208
 
209
+ # Flush the thread pool. Mainly used for forcibly resizing
210
+ # the pool if existing threads have a long thread life waiting
211
+ # for input.
212
+ def flush
213
+ lock = Mutex.new
214
+ guard = ConditionVariable.new
215
+ @threads.size.times{ queue{ lock.synchronize{ guard.wait(lock) } } }
216
+ Thread.pass
217
+ sleep(0.01)
218
+ lock.synchronize{ guard.broadcast }
219
+ end
220
+
179
221
  private
180
-
222
+
223
+ # Resize the pool
181
224
  def resize
182
- @logger.info("Pool is being resized to stated minimum: #{min}")
183
- size - min.times do
184
- t = @threads.shift
225
+ @logger.info("Pool is being resized to stated maximum: #{max}")
226
+ until(size <= max) do
227
+ t = nil
228
+ t = @threads.find{|t|t.waiting?}
229
+ t = @threads.shift unless t
185
230
  t.stop
186
231
  end
232
+ flush
187
233
  nil
188
234
  end
189
235
  end
@@ -9,6 +9,8 @@ module ActionPool
9
9
  @wait = false
10
10
  @lock = Mutex.new
11
11
  @guard = ConditionVariable.new
12
+ @elock = Mutex.new
13
+ @eguard = ConditionVariable.new
12
14
  end
13
15
  # Stop the queue from returning results to requesting
14
16
  # threads. Threads will wait for results until signalled
@@ -19,12 +21,18 @@ module ActionPool
19
21
  # will have results given to them.
20
22
  def unpause
21
23
  @wait = false
22
- @lock.synchronize{ @guard.signal }
24
+ @lock.synchronize{ @guard.broadcast }
23
25
  end
24
26
  # Check if queue needs to wait before returning
25
27
  def pop
26
28
  @lock.synchronize{ @guard.wait(@lock) } if @wait
27
- super
29
+ o = super
30
+ @elock.synchronize{ @eguard.broadcast } if empty?
31
+ return o
32
+ end
33
+ # Park a thread here until queue is empty
34
+ def wait_empty
35
+ @elock.synchronize{ @eguard.wait(@elock) } if size > 0
28
36
  end
29
37
  end
30
38
  end
@@ -1,6 +1,7 @@
1
1
  require 'timeout'
2
2
 
3
3
  module ActionPool
4
+
4
5
  class Thread
5
6
  # :pool:: pool thread is associated with
6
7
  # :t_timeout:: max time a thread is allowed to wait for action
@@ -14,23 +15,74 @@ module ActionPool
14
15
  raise ArgumentError.new('ActionPool::Thread requries thread to respond') unless args[:respond_thread]
15
16
  @pool = args[:pool]
16
17
  @respond_to = args[:respond_thread]
17
- @thread_timeout = args[:t_timeout] ? args[:t_timeout] : 0
18
- @action_timeout = args[:a_timeout] ? args[:a_timeout] : 0
18
+ @thread_timeout = args[:t_timeout] ? args[:t_timeout].to_f : 0
19
+ @action_timeout = args[:a_timeout] ? args[:a_timeout].to_f : 0
19
20
  @kill = false
20
21
  @logger = args[:logger].is_a?(LogHelper) ? args[:logger] : LogHelper.new(args[:logger])
21
22
  @thread = ::Thread.new{ start_thread }
22
23
  end
23
24
 
24
- # force:: force the thread to stop
25
+ # :force:: force the thread to stop
26
+ # :wait:: wait for the thread to stop
25
27
  # Stop the thread
26
- def stop(force=false)
28
+ def stop(*args)
27
29
  @kill = true
28
- @thread.kill if force
30
+ @thread.kill if args.include?(:force) || waiting?
31
+ # killing the thread if it is waiting? is a simple race condition
32
+ # in that, if the thread has just finished a task, but hasn't
33
+ # dropped by the queue line yet, we will see it as not waiting. we
34
+ # can counter that by waiting just a little bit longer than the allowed
35
+ # time to work on an action, and kill the thread if it is still around
36
+ if(args.include?(:wait))
37
+ unless(@thread.join(@action_timeout + 0.01))
38
+ @thread.kill
39
+ @thread.join
40
+ end
41
+ end
29
42
  nil
30
43
  end
31
44
 
45
+ # Is the thread still alive
46
+ def alive?
47
+ @thread.alive?
48
+ end
49
+
50
+ # Is the thread currently waiting for input
51
+ def waiting?
52
+ @thread.status == 'sleep'
53
+ end
54
+
55
+ # Seconds thread will wait for input
56
+ def thread_timeout
57
+ @thread_timeout
58
+ end
59
+
60
+ # Seconds thread will spend working on a given task
61
+ def action_timeout
62
+ @action_timeout
63
+ end
64
+
65
+ # t:: seconds to wait for input (floats allow for values 0 < t < 1)
66
+ # Set the maximum amount of time to wait for a task
67
+ def thread_timeout=(t)
68
+ t = t.to_f
69
+ raise ArgumentError.new('Value must be great than zero or nil') unless t > 0
70
+ @thread_timeout = t
71
+ t
72
+ end
73
+
74
+ # t:: seconds to work on a task (floats allow for values 0 < t < 1)
75
+ # Set the maximum amount of time to work on a given task
76
+ def action_timeout=(t)
77
+ t = t.to_f
78
+ raise ArgumentError.new('Value must be great than zero or nil') unless t > 0
79
+ @action_timeout = t
80
+ t
81
+ end
82
+
32
83
  private
33
84
 
85
+ # Start our thread
34
86
  def start_thread
35
87
  begin
36
88
  @logger.info("New pool thread is starting (#{self})")
@@ -44,7 +96,7 @@ module ActionPool
44
96
  else
45
97
  action = @pool.action
46
98
  end
47
- run(action) unless action.nil?
99
+ run(*action) unless action.nil?
48
100
  rescue Timeout::Error => boom
49
101
  @kill = true
50
102
  rescue Exception => boom
@@ -61,14 +113,17 @@ module ActionPool
61
113
  end
62
114
  end
63
115
 
64
- def run(action)
116
+ # action:: task to be run
117
+ # args:: arguments to be passed to task
118
+ # Run the task
119
+ def run(action, args)
65
120
  begin
66
121
  if(@action_timeout > 0)
67
122
  Timeout::timeout(@action_timeout) do
68
- action.call
123
+ action.call(*args)
69
124
  end
70
125
  else
71
- action.call
126
+ action.call(*args)
72
127
  end
73
128
  rescue Timeout::Error => boom
74
129
  @logger.warn("Pool thread reached max execution time for action: #{boom}")
@@ -0,0 +1,42 @@
1
+ require 'actionpool'
2
+ require 'test/unit'
3
+
4
+ class GeneralPoolTest < Test::Unit::TestCase
5
+ def setup
6
+ @pool = ActionPool::Pool.new
7
+ end
8
+ def test_numbers
9
+ assert_equal(10, @pool.size)
10
+ assert_equal(10, @pool.min)
11
+ assert_equal(100, @pool.max)
12
+ assert_equal(0, @pool.action_timeout)
13
+ assert_equal(0, @pool.thread_timeout)
14
+ assert_equal(0, @pool.action_size)
15
+ end
16
+ def test_output
17
+ a = 0
18
+ lock = Mutex.new
19
+ run = lambda{ lock.synchronize{ a += 1 } }
20
+ 100.times{ @pool << run }
21
+ @pool.shutdown
22
+ assert_equal(100, a)
23
+ a = 0
24
+ jobs = [].fill(run,0,100)
25
+ @pool.add_jobs(jobs)
26
+ @pool.shutdown
27
+ assert_equal(100, a)
28
+ @pool.shutdown(true)
29
+ end
30
+ def test_args
31
+ output = nil
32
+ @pool << [lambda{|x| output = x}, [2]]
33
+ assert(2, output)
34
+ @pool.add_jobs([[lambda{|x| output = x}, [3]]])
35
+ assert(3, output)
36
+ output = []
37
+ @pool.add_jobs([[lambda{|x,y| output << x + y}, [1,1]], [lambda{|x| output << x}, [3]]])
38
+ assert(output.include?(2))
39
+ assert(output.include?(3))
40
+ @pool.shutdown(true)
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ require 'actionpool'
2
+ require 'test/unit'
3
+
4
+ class GrowPoolTest < Test::Unit::TestCase
5
+ def setup
6
+ @pool = ActionPool::Pool.new
7
+ end
8
+ def test_grow
9
+ jobs = [].fill(lambda{}, 0..99)
10
+ @pool.add_jobs(jobs)
11
+ assert(@pool.size > 10)
12
+ @pool.shutdown(true)
13
+ end
14
+ def test_max
15
+ @pool.create_thread(true) until @pool.size > @pool.max
16
+ assert_nil(@pool.create_thread)
17
+ assert(@pool.create_thread(true))
18
+ @pool.shutdown(true)
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ require 'actionpool'
2
+ require 'test/unit'
3
+
4
+ class NoGrowPoolTest < Test::Unit::TestCase
5
+ def setup
6
+ @pool = ActionPool::Pool.new
7
+ end
8
+ def test_nogrow
9
+ 5.times{ @pool << lambda{} }
10
+ assert_equal(10, @pool.size)
11
+ end
12
+ end
@@ -0,0 +1,34 @@
1
+ require 'actionpool'
2
+ require 'test/unit'
3
+
4
+ class QueueTest < Test::Unit::TestCase
5
+ def setup
6
+ @queue = ActionPool::Queue.new
7
+ end
8
+ def test_pop
9
+ 3.times{|i|@queue << i}
10
+ 3.times{|i|assert(i, @queue.pop)}
11
+ assert(@queue.empty?)
12
+ end
13
+ def test_pause
14
+ 3.times{|i|@queue << i}
15
+ @queue.pause
16
+ output = []
17
+ 3.times{Thread.new{output << @queue.pop}}
18
+ assert(output.empty?)
19
+ assert_equal(3, @queue.size)
20
+ @queue.unpause
21
+ sleep(1)
22
+ assert(@queue.empty?)
23
+ assert_equal(3, output.size)
24
+ 3.times{|i|assert(output.include?(i))}
25
+ @queue << 1
26
+ output = nil
27
+ Thread.new{@queue.wait_empty; output = true}
28
+ assert_nil(output)
29
+ @queue.pop
30
+ Thread.pass
31
+ sleep(0.01)
32
+ assert(output)
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ require 'actionpool'
2
+ require 'test/unit'
3
+
4
+ class ResizePoolTest < Test::Unit::TestCase
5
+ def setup
6
+ @pool = ActionPool::Pool.new
7
+ end
8
+ def test_resize
9
+ stop = false
10
+ 20.times{ @pool << lambda{ a = 0; a += 1 until stop || a > 9999999999 } }
11
+ assert(@pool.size > 10)
12
+ stop = true
13
+ @pool.shutdown(true)
14
+ @pool.max = 10
15
+ assert_equal(10, @pool.max)
16
+ assert_equal(0, @pool.size)
17
+ stop = false
18
+ 20.times{ @pool << lambda{ a = 0; a += 1 until stop || a > 9999999999 } }
19
+ assert_equal(10, @pool.size)
20
+ stop = true
21
+ @pool.shutdown(true)
22
+ @pool.max = 20
23
+ stop = false
24
+ 30.times{ @pool << lambda{ a = 0; a += 1 until stop || a > 9999999999 } }
25
+ stop = true
26
+ assert(@pool.size > 10)
27
+ @pool.shutdown(true)
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ require 'actionpool'
2
+ require 'test/unit'
3
+
4
+ class ShutdownPoolTest < Test::Unit::TestCase
5
+ def setup
6
+ @pool = ActionPool::Pool.new
7
+ end
8
+ def test_shutdown
9
+ assert_equal(10, @pool.size)
10
+ @pool.shutdown
11
+ sleep(0.5)
12
+ assert_equal(0, @pool.size)
13
+ @pool << lambda{}
14
+ assert_equal(10, @pool.size)
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ require 'actionpool'
2
+ require 'test/unit'
3
+
4
+ class ThreadTest < Test::Unit::TestCase
5
+ def setup
6
+ @pool = ActionPool::Pool.new(:min_threads => 1, :max_threads => 1)
7
+ @thread = ActionPool::Thread.new(:pool => @pool, :respond_thread => self, :t_timeout => 60, :a_timeout => 0)
8
+ end
9
+ def test_thread
10
+ sleep(0.01)
11
+ assert(@thread.waiting?)
12
+ assert_equal(60, @thread.thread_timeout)
13
+ assert_equal(0, @thread.action_timeout)
14
+ assert(@thread.alive?)
15
+ stop = false
16
+ 10.times{ @pool << lambda{ a = 0; a += 1 until stop || a > 9999999999 } }
17
+ assert(!@thread.waiting?)
18
+ @thread.stop(:force)
19
+ sleep(0.01)
20
+ assert(!@thread.alive?)
21
+ stop = true
22
+ @pool.shutdown(true)
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ require 'actionpool'
2
+ require 'test/unit'
3
+
4
+ class TimeoutPoolTest < Test::Unit::TestCase
5
+ def setup
6
+ @pool = ActionPool::Pool.new
7
+ end
8
+ def test_threadtimeout
9
+ @pool.thread_timeout = 0.01
10
+ assert_equal(10, @pool.size)
11
+ stop = false
12
+ 20.times{ @pool << lambda{ a = 0; a += 1 until stop || a > 9999999999 } }
13
+ assert(@pool.size > 10)
14
+ stop = true
15
+ sleep(2)
16
+ assert(@pool.size <= 10)
17
+ @pool.shutdown(true)
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ require 'test/unit'
2
+ require 'actionpool'
3
+
4
+ Dir.new("#{File.dirname(__FILE__)}/cases").each{|f|
5
+ require "#{File.dirname(__FILE__)}/cases/#{f}" if f[-2..f.size] == 'rb'
6
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ActionPool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - spox
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-13 00:00:00 -07:00
12
+ date: 2009-10-19 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -21,20 +21,32 @@ extensions: []
21
21
 
22
22
  extra_rdoc_files:
23
23
  - README.rdoc
24
+ - CHANGELOG
24
25
  files:
25
- - ActionPool-0.0.1.gem
26
- - README.rdoc
27
26
  - lib
28
27
  - lib/actionpool
29
28
  - lib/actionpool/LogHelper.rb
30
- - lib/actionpool/Thread.rb
31
29
  - lib/actionpool/Pool.rb
30
+ - lib/actionpool/Thread.rb
32
31
  - lib/actionpool/Queue.rb
33
32
  - lib/actionpool.rb
33
+ - ActionPool-0.2.0.gem
34
+ - tests
35
+ - tests/cases
36
+ - tests/cases/shutdown.rb
37
+ - tests/cases/nogrow.rb
38
+ - tests/cases/general.rb
39
+ - tests/cases/thread.rb
40
+ - tests/cases/grow.rb
41
+ - tests/cases/timeouts.rb
42
+ - tests/cases/resize.rb
43
+ - tests/cases/queue.rb
44
+ - tests/run_tests.rb
45
+ - CHANGELOG
34
46
  - ActionPool-0.1.0.gem
35
47
  - LICENSE
36
- - test2.rb
37
- - test.rb
48
+ - README.rdoc
49
+ - ActionPool-0.0.1.gem
38
50
  - actionpool.gemspec
39
51
  has_rdoc: false
40
52
  homepage: http://github.com/spox/actionpool
data/test.rb DELETED
@@ -1,10 +0,0 @@
1
- require 'actionpool'
2
-
3
- pool = ActionPool::Pool.new
4
- a = 0
5
- lock = Mutex.new
6
- tasks = [].fill(lambda{ lock.synchronize{ a += 1} }, 0..19)
7
- puts "Size: #{tasks.size}"
8
- pool.add_jobs(tasks)
9
- sleep(0.5)
10
- puts "Result: #{a}"
data/test2.rb DELETED
@@ -1,18 +0,0 @@
1
- require 'actionpool'
2
-
3
- pool = ActionPool::Pool.new(:min_threads => 1, :max_threads => 3)
4
- pool.process do
5
- sleep(10)
6
- raise 'Wakeup main thread'
7
- end
8
- 20.times do
9
- pool.process do
10
- puts "Thread: #{Thread.current}"
11
- sleep(rand(0.0))
12
- end
13
- end
14
- begin
15
- sleep
16
- rescue Exception => e
17
- puts "Thread pool woke me up: #{e}"
18
- end