thread_tools 0.27 → 0.28

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.
@@ -31,6 +31,11 @@ require 'thread'
31
31
  module ThreadTools
32
32
 
33
33
  class EMutexOrder < Exception
34
+ attr_reader :mutex
35
+ def initialize(mutex, str)
36
+ @mutex = mutex
37
+ super(str)
38
+ end
34
39
  end
35
40
 
36
41
  class DebugMutex < Mutex
@@ -80,7 +85,7 @@ module ThreadTools
80
85
  else
81
86
  if @owner[:locks].delete(self)
82
87
  @out_of_order_locks += 1
83
- raise EMutexOrder.new("Expected #{@owner[:locks].last}")
88
+ raise EMutexOrder.new(self, "Expected #{@owner[:locks].last}")
84
89
  end
85
90
  # if called again let it pass
86
91
  end
@@ -33,6 +33,7 @@ module ThreadTools
33
33
  # if set to true a new worker is created if the pool is empty
34
34
  attr_accessor :create_on_spawn
35
35
 
36
+
36
37
  # *_size* should be at least 1, *_thr_group* (optional) thread group
37
38
  def initialize(_size, _thr_group = nil)
38
39
  @kill_worker_on_exception = false
@@ -40,7 +41,8 @@ module ThreadTools
40
41
  @pool_mtx = Mutex.new
41
42
  @pool_cv = ConditionVariable.new
42
43
  @pool = []
43
- @thr_grp = _thr_group
44
+ @dummy_grp = ThreadGroup.new
45
+ @busy_grp = _thr_group.nil? ? ThreadGroup.new : _thr_group
44
46
  @create_on_spawn = false
45
47
  _size = 1 if _size < 1
46
48
  _size.times { create_worker }
@@ -48,35 +50,31 @@ module ThreadTools
48
50
 
49
51
  def create_worker
50
52
  Thread.new do
51
- thr = Thread.current
52
- @pool_mtx.synchronize do
53
- @size += 1
54
- if (!@thr_grp.nil?)
55
- @thr_grp.add(thr)
56
- end
57
- end
58
- thr[:jobs] = [] # XXX array not really necessary
59
- thr[:sem] = Semaphore.new(0)
60
- loop do
61
- @pool_mtx.synchronize do
62
- @pool << thr # puts this thread in the pool
63
- @pool_cv.signal
64
- end
65
- thr[:sem].acquire # wait here for jobs to become available
66
- job = thr[:jobs].shift # pop out a job
67
- if (job.nil? || job[:block].nil?)
68
- break # exit thread if job or block is nil
69
- end
70
- begin
71
- job[:block].call(*job[:args]) # call block
72
- rescue
73
- if (@kill_worker_on_exception)
74
- break # exit thread on exception
53
+ @pool_mtx.synchronize do @size += 1 end
54
+ begin
55
+ thr = Thread.current
56
+ thr[:jobs] = [] # XXX array not really necessary
57
+ thr[:sem] = Semaphore.new(0)
58
+ loop do
59
+ @pool_mtx.synchronize do
60
+ @pool << thr # puts this thread in the pool
61
+ @pool_cv.signal
62
+ end
63
+ thr[:sem].acquire # wait here for jobs to become available
64
+ job = thr[:jobs].shift # pop out a job
65
+ if (job.nil? || job[:block].nil?)
66
+ break # exit thread if job or block is nil
67
+ end
68
+ begin
69
+ @busy_grp.add(thr) # adds the thread
70
+ job[:block].call(*job[:args]) # call block
71
+ @dummy_grp.add(thr) # removes from the previous group
72
+ rescue
73
+ break if @kill_worker_on_exception # exit thread on exception
75
74
  end
76
75
  end
77
- end
78
- @pool_mtx.synchronize do
79
- @size -= 1
76
+ ensure
77
+ @pool_mtx.synchronize do @size -= 1 end
80
78
  end
81
79
  end
82
80
  end
@@ -85,9 +83,7 @@ module ThreadTools
85
83
  thr = nil
86
84
  @pool_mtx.synchronize do
87
85
  # creates a new worker thread if pool is empty and flag is set
88
- if (@create_on_spawn && @pool.empty?)
89
- create_worker
90
- end
86
+ create_worker if @create_on_spawn && @pool.empty?
91
87
  # wait here until a worker is available
92
88
  @pool_cv.wait(@pool_mtx) until !(thr = @pool.shift).nil?
93
89
  thr[:jobs] << { :args => args, :block => block }
@@ -105,10 +101,18 @@ module ThreadTools
105
101
  thr[:jobs].clear # clear any pending job
106
102
  thr[:jobs] << nil # queue a nil job
107
103
  thr[:sem].release
108
- if (_sync)
109
- thr.join # wait here for the thread to die
110
- end
104
+ thr.join if _sync # wait here for the thread to die
111
105
  end
106
+ busy_list = @busy_grp.list
107
+ busy_list.each do |athr|
108
+ athr.raise('shutdown') # XXX we could .kill instead
109
+ athr.join if _sync # wait here for the thread to die
110
+ end
111
+ end
112
+
113
+ # returns the number of busy workers
114
+ def busy_size
115
+ @busy_grp.list.size
112
116
  end
113
117
  end
114
118
 
@@ -6,36 +6,53 @@ class ThreadPoolTest < Test::Unit::TestCase
6
6
  def setup
7
7
  super
8
8
  @tpool = ThreadTools::ThreadPool.new(2)
9
- @tpool.kill_worker_on_exception = true
10
9
  end
11
10
 
12
- def test_thread_spawn
11
+ def test_spawn
13
12
  uniq_threads = {}
14
13
  sum_orig = 0
15
- sum_lock = Mutex.new
16
14
  sum_thr = 0
17
- 4.times {|i|
15
+ sum_lock = Mutex.new
16
+ 4.times do |i|
18
17
  sum_orig += i
19
- @tpool.spawn(i) {|ti|
20
- sum_lock.synchronize do
21
- sum_thr += ti
22
- end
18
+ @tpool.spawn(i) do |j|
19
+ sum_lock.synchronize { sum_thr += j }
23
20
  uniq_threads[Thread.current] = true
24
- if (ti == 3)
25
- # this exception should kill this worker
26
- raise "oups"
27
- end
28
- }
29
- }
21
+ end
22
+ end
30
23
  sleep 0.1
31
- # 2 workers - 1 from raised exceptions = 1
32
- assert_equal(@tpool.size, 1)
33
- @tpool.shutdown
34
24
  # same sum
35
25
  assert_equal(sum_orig, sum_thr)
36
26
  # number of different threads
37
27
  assert_equal(uniq_threads.size, 2)
28
+ end
29
+
30
+ def test_raise
31
+ @tpool.kill_worker_on_exception = true
32
+ 2.times do |i|
33
+ @tpool.spawn(i+1) do |j|
34
+ raise 'oups' if j == 2
35
+ end
36
+ end
37
+ sleep 0.1
38
+ # 2 workers - 1 from raised exceptions = 1
39
+ assert_equal(@tpool.size, 1)
40
+ end
41
+
42
+ def test_shutdown
43
+ # initial number of threads
44
+ assert_equal(@tpool.size, 2)
45
+ @tpool.shutdown
38
46
  # after shutdown all threads are dead
39
47
  assert_equal(@tpool.size, 0)
40
48
  end
49
+
50
+ def test_spawn_create
51
+ # kill all workers first
52
+ @tpool.shutdown
53
+ @tpool.create_on_spawn = true
54
+ @tpool.spawn {}
55
+ # a new worker was created
56
+ assert_equal(@tpool.size, 1)
57
+ end
41
58
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{thread_tools}
3
- s.version = '0.27'
3
+ s.version = '0.28'
4
4
  s.summary = %q{Utilities for threaded apps}
5
5
  s.platform = Gem::Platform::RUBY
6
6
  s.email = %q{daniel@tralamazza.com}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thread_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.27"
4
+ version: "0.28"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Tralamazza
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-06 00:00:00 +02:00
12
+ date: 2009-10-14 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies: []
15
15