ActionPool 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ 0.2.3
2
+ - Better thread destroying when removed normally and forcibly
3
+ - Flushing threads to ensure proper joining
4
+ - Fixed splatting for ruby < 1.9
5
+ - Removed the useless logger wrapper
6
+ - Use a simple monitor
1
7
  0.2.2
2
8
  - Better thread management within the pool
3
9
  - Restart thread timeouts when modified
@@ -4,7 +4,7 @@ ActionPool is just a simple thread pool. It allows for various constraints and r
4
4
 
5
5
  === install (easy):
6
6
 
7
- gem install ActionPool
7
+ gem install actionpool
8
8
 
9
9
  === install (less easy):
10
10
 
@@ -17,6 +17,15 @@ ActionPool is just a simple thread pool. It allows for various constraints and r
17
17
 
18
18
  {rip}[http://hellorip.com/about.html] makes it easy to install directly from a github repository.
19
19
 
20
+ === Testing
21
+
22
+ ActionPool is currently tested on:
23
+
24
+ * Ruby 1.8.6-p383
25
+ * Ruby 1.8.7-p248
26
+ * Ruby 1.9.1-p376
27
+ * JRuby 1.4.0
28
+
20
29
  == Documentation
21
30
 
22
31
  {rdocs}[http://allgems.ruby-forum.com/gems/ActionPool/]
@@ -2,7 +2,7 @@ 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.2.2'
5
+ s.version = '0.2.3'
6
6
  s.summary = %q(Thread Pool)
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.files = Dir['**/*']
@@ -10,6 +10,7 @@ spec = Gem::Specification.new do |s|
10
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
+ s.add_dependency 'splib', '~> 1.4'
13
14
  s.homepage = %q(http://github.com/spox/actionpool)
14
15
  s.description = "The ActionPool is an easy to use thread pool for ruby."
15
16
  end
@@ -1,7 +1,10 @@
1
+ require 'rubygems'
1
2
  begin
2
3
  require 'fastthread'
3
4
  rescue LoadError
4
5
  # we don't care if it's available
5
6
  # just load it if it's around
6
7
  end
8
+ require 'splib'
9
+ Splib.load :Array, :Monitor
7
10
  require 'actionpool/Pool'
@@ -1,6 +1,6 @@
1
1
  require 'actionpool/Thread'
2
2
  require 'actionpool/Queue'
3
- require 'actionpool/LogHelper'
3
+ require 'logger'
4
4
  require 'thread'
5
5
 
6
6
  module ActionPool
@@ -17,10 +17,10 @@ module ActionPool
17
17
  # Creates a new pool
18
18
  def initialize(args={})
19
19
  raise ArgumentError.new('Hash required for initialization') unless args.is_a?(Hash)
20
- @logger = LogHelper.new(args[:logger])
20
+ @logger = args[:logger] && args[:logger].is_a?(Logger) ? args[:logger] : Logger.new(nil)
21
21
  @queue = ActionPool::Queue.new
22
22
  @threads = []
23
- @lock = Mutex.new
23
+ @lock = Splib::Monitor.new
24
24
  @thread_timeout = args[:t_to] ? args[:t_to] : 0
25
25
  @action_timeout = args[:a_to] ? args[:a_to] : 0
26
26
  @max_threads = args[:max_threads] ? args[:max_threads] : 100
@@ -48,36 +48,44 @@ module ActionPool
48
48
  fill_pool if @open
49
49
  end
50
50
 
51
- # args:: :force forces a new thread. :nowait will create a thread if threads are waiting
51
+ # args:: :force forces a new thread.
52
+ # :nowait will create a thread if threads are waiting
52
53
  # Create a new thread for pool.
53
- # Returns newly created thread of nil if pool is at maximum size
54
+ # Returns newly created thread or nil if pool is at maximum size
54
55
  def create_thread(*args)
55
56
  return if pool_closed?
56
57
  thread = nil
57
58
  @lock.synchronize do
58
59
  if(((size == working || args.include?(:nowait)) && @threads.size < @max_threads) || args.include?(:force))
59
- thread = ActionPool::Thread.new(:pool => self, :respond_thread => @respond_to, :a_timeout => @action_timeout, :t_timeout => @thread_timeout, :logger => @logger)
60
+ thread = ActionPool::Thread.new(:pool => self, :respond_thread => @respond_to, :a_timeout => @action_timeout,
61
+ :t_timeout => @thread_timeout, :logger => @logger, :autostart => false)
60
62
  @threads << thread
61
63
  end
62
64
  end
63
- return thread
65
+ thread.start if thread
66
+ thread
64
67
  end
65
68
 
66
69
  # Fills the pool with the minimum number of threads
67
70
  # Returns array of created threads
68
71
  def fill_pool
69
72
  threads = []
70
- @lock.synchronize do
71
- required = min - size
72
- if(required > 0)
73
- required.times do
74
- thread = ActionPool::Thread.new(:pool => self, :respond_thread => @respond_to, :a_timeout => @action_timeout, :t_timeout => @thread_timeout, :logger => @logger)
75
- @threads << thread
76
- threads << thread
73
+ if(@open)
74
+ @lock.synchronize do
75
+ required = min - size
76
+ if(required > 0)
77
+ required.times do
78
+ thread = ActionPool::Thread.new(:pool => self, :respond_thread => @respond_to,
79
+ :a_timeout => @action_timeout, :t_timeout => @thread_timeout, :logger => @logger,
80
+ :autostart => false)
81
+ @threads << thread
82
+ threads << thread
83
+ end
77
84
  end
78
85
  end
79
86
  end
80
- return threads
87
+ threads.each{|t|t.start}
88
+ threads
81
89
  end
82
90
 
83
91
  # force:: force immediate stop
@@ -92,6 +100,10 @@ module ActionPool
92
100
  while(t = @threads.pop) do
93
101
  t.stop(*args)
94
102
  end
103
+ unless(force)
104
+ flush
105
+ @threads.each{|t|t.join}
106
+ end
95
107
  nil
96
108
  end
97
109
 
@@ -193,12 +205,10 @@ module ActionPool
193
205
  def remove(t)
194
206
  raise ArgumentError.new('Expecting an ActionPool::Thread object') unless t.is_a?(ActionPool::Thread)
195
207
  t.stop
196
- if(@threads.include?(t))
197
- @threads.delete(t)
198
- return true
199
- else
200
- return false
201
- end
208
+ del = @threads.include?(t)
209
+ @threads.delete(t) if del
210
+ fill_pool
211
+ del
202
212
  end
203
213
 
204
214
  # Maximum number of seconds a thread
@@ -251,17 +261,20 @@ module ActionPool
251
261
  # the pool if existing threads have a long thread life waiting
252
262
  # for input.
253
263
  def flush
254
- lock = Mutex.new
255
- guard = ConditionVariable.new
256
- @threads.size.times{ queue{ lock.synchronize{ guard.wait(lock) } } }
257
- Thread.pass
264
+ mon = Splib::Monitor.new
265
+ @threads.size.times{ queue{ mon.wait } }
266
+ @queue.wait_empty
258
267
  sleep(0.01)
259
- lock.synchronize{ guard.broadcast }
268
+ mon.broadcast
260
269
  end
261
270
 
262
271
  # Returns current number of threads in the pool working
263
272
  def working
264
- @threads.find_all{|t|!t.waiting?}.size
273
+ @threads.select{|t|t.running?}.size
274
+ end
275
+
276
+ def thread_stats
277
+ @threads.map{|t|[t.object_id,t.status]}
265
278
  end
266
279
 
267
280
  private
@@ -7,47 +7,35 @@ module ActionPool
7
7
  def initialize
8
8
  super
9
9
  @wait = false
10
- @pause_lock = Mutex.new
11
- @empty_lock = Mutex.new
12
- @pause_guard = ConditionVariable.new
13
- @empty_guard = ConditionVariable.new
10
+ @pause_guard = Splib::Monitor.new
11
+ @empty_guard = Splib::Monitor.new
14
12
  end
15
13
  # Stop the queue from returning results to requesting
16
14
  # threads. Threads will wait for results until signalled
17
15
  def pause
18
- @pause_lock.synchronize{@wait = true}
16
+ @wait = true
19
17
  end
20
18
  # Allow the queue to return results. Any threads waiting
21
19
  # will have results given to them.
22
20
  def unpause
23
- @pause_lock.synchronize do
24
- @wait = false
25
- @pause_guard.broadcast
26
- end
21
+ @wait = false
22
+ @pause_guard.broadcast
27
23
  end
28
24
  # Check if queue needs to wait before returning
29
25
  def pop
30
- @pause_lock.synchronize do
31
- @pause_guard.wait(@pause_lock) if @wait
32
- end
26
+ @pause_guard.wait_while{ @wait }
33
27
  o = super
34
- @empty_lock.synchronize do
35
- @empty_guard.broadcast if empty?
36
- end
28
+ @empty_guard.broadcast if empty?
37
29
  return o
38
30
  end
39
31
  # Clear queue
40
32
  def clear
41
33
  super
42
- @empty_lock.synchronize do
43
- @empty_guard.broadcast
44
- end
34
+ @empty_guard.broadcast
45
35
  end
46
36
  # Park a thread here until queue is empty
47
37
  def wait_empty
48
- @empty_lock.synchronize do
49
- @empty_guard.wait(@empty_lock) if size > 0
50
- end
38
+ @empty_guard.wait_while{ size > 0 }
51
39
  end
52
40
  end
53
41
  end
@@ -13,6 +13,7 @@ module ActionPool
13
13
  # :a_timeout:: max time thread is allowed to work
14
14
  # :respond_thread:: thread to send execptions to
15
15
  # :logger:: LogHelper for logging messages
16
+ # :autostart:: Automatically start the thread
16
17
  # Create a new thread
17
18
  def initialize(args)
18
19
  raise ArgumentError.new('Hash required for initialization') unless args.is_a?(Hash)
@@ -22,24 +23,45 @@ module ActionPool
22
23
  @respond_to = args[:respond_thread]
23
24
  @thread_timeout = args[:t_timeout] ? args[:t_timeout].to_f : 0
24
25
  @action_timeout = args[:a_timeout] ? args[:a_timeout].to_f : 0
26
+ args[:autostart] = true unless args.has_key?(:autostart)
25
27
  @kill = false
26
- @logger = args[:logger].is_a?(LogHelper) ? args[:logger] : LogHelper.new(args[:logger])
27
- @lock = Mutex.new
28
- @thread = ::Thread.new{ start_thread }
28
+ @logger = args[:logger].is_a?(Logger) ? args[:logger] : Logger.new(nil)
29
+ @lock = Splib::Monitor.new
30
+ @action = nil
31
+ @thread = args[:autostart] ? ::Thread.new{ start_thread } : nil
29
32
  end
30
33
 
34
+ def start
35
+ @thread = ::Thread.new{ start_thread } if @thread.nil?
36
+ end
37
+
31
38
  # :force:: force the thread to stop
32
39
  # :wait:: wait for the thread to stop
33
40
  # Stop the thread
34
41
  def stop(*args)
35
42
  @kill = true
36
- @thread.raise Wakeup.new if args.include?(:force) || waiting?
43
+ if(args.include?(:force) || waiting?)
44
+ begin
45
+ @thread.raise Wakeup.new
46
+ rescue Wakeup
47
+ #ignore since we are the caller
48
+ end
49
+ sleep(0.01)
50
+ @thread.kill if @thread.alive?
51
+ end
37
52
  nil
38
53
  end
39
54
 
40
55
  # Currently waiting
41
56
  def waiting?
42
- @lock.synchronize{@status} == :wait
57
+ @action.nil?
58
+ # @status == :wait
59
+ end
60
+
61
+ # Currently running
62
+ def running?
63
+ !@action.nil?
64
+ # @status == :run
43
65
  end
44
66
 
45
67
  # Is the thread still alive
@@ -49,14 +71,21 @@ module ActionPool
49
71
 
50
72
  # Current thread status
51
73
  def status
52
- @lock.synchronize{ return @status }
74
+ @action
53
75
  end
54
-
55
- # arg:: :wait or :run
56
- # Set current status
57
- def status(arg)
58
- raise InvalidType.new('Status can only be set to :wait or :run') unless arg == :wait || arg == :run
59
- @lock.synchronize{ @status = arg }
76
+
77
+ # Join internal thread
78
+ def join
79
+ @thread.join(@action_timeout)
80
+ if(@thread.alive?)
81
+ @thread.kill
82
+ @thread.join
83
+ end
84
+ end
85
+
86
+ # Kill internal thread
87
+ def kill
88
+ @thread.kill
60
89
  end
61
90
 
62
91
  # Seconds thread will wait for input
@@ -96,20 +125,17 @@ module ActionPool
96
125
  begin
97
126
  @logger.info("New pool thread is starting (#{self})")
98
127
  until(@kill) do
99
- status(:wait)
100
128
  begin
101
- action = nil
129
+ @action = nil
102
130
  if(@pool.size > @pool.min && !@thread_timeout.zero?)
103
131
  Timeout::timeout(@thread_timeout) do
104
- action = @pool.action
132
+ @action = @pool.action
105
133
  end
106
134
  else
107
- action = @pool.action
135
+ @action = @pool.action
108
136
  end
109
- status(:run)
110
- run(action[0], action[1]) unless action.nil?
111
- status(:wait)
112
- rescue Timeout::Error => boom
137
+ run(@action[0], @action[1]) unless @action.nil?
138
+ rescue Timeout::Error
113
139
  @kill = true
114
140
  rescue Wakeup
115
141
  @logger.info("Thread #{::Thread.current} was woken up.")
@@ -131,7 +157,6 @@ module ActionPool
131
157
  ensure
132
158
  @logger.info("Pool thread is shutting down (#{self})")
133
159
  @pool.remove(self)
134
- @pool.create_thread
135
160
  end
136
161
  end
137
162
 
@@ -139,13 +164,14 @@ module ActionPool
139
164
  # args:: arguments to be passed to task
140
165
  # Run the task
141
166
  def run(action, args)
167
+ args = args.respond_to?(:fixed_flatten) ? args.fixed_flatten(1) : args.flatten(1)
142
168
  begin
143
169
  unless(@action_timeout.zero?)
144
170
  Timeout::timeout(@action_timeout) do
145
- action.call(*args[0])
171
+ action.call(*args)
146
172
  end
147
173
  else
148
- action.call(*args[0])
174
+ action.call(*args)
149
175
  end
150
176
  rescue Timeout::Error => boom
151
177
  @logger.warn("Pool thread reached max execution time for action: #{boom}")
@@ -28,6 +28,7 @@ class GeneralPoolTest < Test::Unit::TestCase
28
28
  jobs = [].fill(run,0,100)
29
29
  @pool.add_jobs(jobs)
30
30
  @pool.shutdown
31
+ sleep(0.01)
31
32
  assert_equal(100, a)
32
33
  @pool.shutdown(true)
33
34
  end
@@ -36,17 +37,24 @@ class GeneralPoolTest < Test::Unit::TestCase
36
37
  output = nil
37
38
  @pool << [lambda{|x| output = x}, [2]]
38
39
  assert(2, output)
40
+ output = nil
39
41
  @pool.add_jobs([[lambda{|x| output = x}, [3]]])
40
42
  assert(3, output)
43
+ output = nil
41
44
  @pool << [lambda{|x,y| output = x+y}, [1,2]]
42
45
  assert(3, output)
43
- output = []
44
- @pool.add_jobs([[lambda{|x,y| output << x + y}, [1,1]], [lambda{|x| output << x}, [3]]])
45
- assert(output.include?(2))
46
- assert(output.include?(3))
47
- @pool << [lambda{|x,y| output = [x,y]}, ['test', [1,2]]]
48
- assert_equal(output[0], 'test')
49
- assert(output[1].is_a?(Array))
46
+ output = nil
47
+ arr = []
48
+ @pool.add_jobs([[lambda{|x,y| arr << x + y}, [1,1]], [lambda{|x| arr << x}, [3]]])
49
+ ::Thread.pass
50
+ sleep(0.01)
51
+ assert(arr.include?(2))
52
+ assert(arr.include?(3))
53
+ arr.clear
54
+ @pool << [lambda{|x,y| arr = [x,y]}, ['test', [1,2]]]
55
+ sleep(0.01)
56
+ assert_equal('test', arr[0])
57
+ assert(arr[1].is_a?(Array))
50
58
  @pool.shutdown(true)
51
59
  end
52
60
  end
@@ -9,26 +9,39 @@ class TimeoutPoolTest < Test::Unit::TestCase
9
9
  @pool.shutdown(true)
10
10
  end
11
11
  def test_actiontimeout
12
- @pool.action_timeout = 0.01
12
+ @pool.action_timeout = 0.25
13
13
  assert_equal(10, @pool.size)
14
14
  stop = false
15
- @pool.add_jobs [].fill(lambda{loop{ 1+1 }}, 0, 20)
16
- ::Thread.pass
17
- assert(@pool.working > 10)
15
+ output = []
16
+ 20.times do
17
+ @pool.process do
18
+ until(stop) do
19
+ output << 1
20
+ sleep(0.1)
21
+ end
22
+ end
23
+ end
24
+ assert(@pool.working >= 10)
25
+ assert_equal(20, output.size)
26
+ sleep(0.11)
18
27
  stop = true
19
- sleep(0.5)
20
- assert(@pool.working == 0)
28
+ sleep(0.1)
29
+ assert_equal(0, @pool.working)
30
+ assert_equal(40, output.size)
21
31
  @pool.shutdown(true)
22
32
  end
23
33
  def test_threadtimeout
24
- @pool.thread_timeout = 0.01
34
+ @pool.thread_timeout = 0.05
25
35
  assert_equal(10, @pool.size)
26
- lock = Mutex.new
27
- guard = ConditionVariable.new
28
- @pool.add_jobs [].fill(lambda{ lock.synchronize{ guard.wait(lock) } }, 0, 20)
36
+ t = [].fill(lambda{
37
+ begin
38
+ sleep(0.1)
39
+ rescue
40
+ end }, 0, 20)
41
+ @pool.add_jobs(t)
29
42
  ::Thread.pass
30
- assert_equal(30, @pool.size)
31
- lock.synchronize{ guard.broadcast }
43
+ sleep(0.01)
44
+ assert(@pool.size >= 20)
32
45
  ::Thread.pass
33
46
  sleep(0.1)
34
47
  assert(10, @pool.size)
@@ -1,3 +1,5 @@
1
+ $LOAD_PATH.unshift(File.expand_path("#{__FILE__}/../../lib"))
2
+
1
3
  require 'test/unit'
2
4
  require 'actionpool'
3
5
 
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.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - spox
@@ -9,10 +9,19 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-11 00:00:00 -08:00
12
+ date: 2010-01-13 00:00:00 -08:00
13
13
  default_executable:
14
- dependencies: []
15
-
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: splib
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: "1.4"
24
+ version:
16
25
  description: The ActionPool is an easy to use thread pool for ruby.
17
26
  email: spox@modspox.com
18
27
  executables: []
@@ -36,12 +45,12 @@ files:
36
45
  - tests/run_tests.rb
37
46
  - lib/actionpool/Pool.rb
38
47
  - lib/actionpool/Thread.rb
39
- - lib/actionpool/LogHelper.rb
40
48
  - lib/actionpool/Queue.rb
41
49
  - lib/actionpool.rb
42
50
  - CHANGELOG
43
51
  - LICENSE
44
52
  - README.rdoc
53
+ - ActionPool-0.2.3.gem
45
54
  has_rdoc: true
46
55
  homepage: http://github.com/spox/actionpool
47
56
  licenses: []
@@ -1,25 +0,0 @@
1
- module ActionPool
2
- class LogHelper
3
-
4
- def initialize(logger=nil)
5
- require 'logger'
6
- @logger = logger
7
- end
8
-
9
- def info(m)
10
- @logger.info(m) unless @logger.nil?
11
- end
12
-
13
- def warn(m)
14
- @logger.warn(m) unless @logger.nil?
15
- end
16
-
17
- def fatal(m)
18
- @logger.fatal(m) unless @logger.nil?
19
- end
20
-
21
- def error(m)
22
- @logger.error(m) unless @logger.nil?
23
- end
24
- end
25
- end