ActionPool 0.2.2 → 0.2.3

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 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