thread_tools 0.25 → 0.26

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.
@@ -3,27 +3,40 @@
3
3
  #
4
4
  # Mutex
5
5
  #
6
- # This class will make things slower, expect more contentions than normal
7
- # I derived this mutex because I needed to check contention levels and trace
8
- # owner changes
6
+ # This class will make things slower, expect more contentions than normal, so
7
+ # don't use to protect small blocks or tight loops
8
+ #
9
+ # I derived this mutex because I needed to check contention levels, trace
10
+ # ownership changes and catch simple lock inversions
11
+ #
12
+ # Use .inspect method to get a small report
9
13
 
10
14
  require 'thread'
11
15
 
12
16
 
13
17
  module ThreadTools
14
18
 
19
+ class EMutexOrder < Exception
20
+ end
21
+
15
22
  class DebugMutex < Mutex
23
+ attr_reader :out_of_order_locks
16
24
  attr_reader :contentions
17
25
  attr_reader :owner
18
26
 
19
27
  def initialize
20
28
  @contentions = 0
29
+ @out_of_order_locks = 0
21
30
  end
22
31
 
23
32
  def lock
24
33
  unless try_lock
25
34
  super
26
35
  @owner = Thread.current
36
+ if @owner[:locks].nil?
37
+ @owner[:locks] = []
38
+ end
39
+ @owner[:locks] << self
27
40
  end
28
41
  self
29
42
  end
@@ -34,12 +47,28 @@ module ThreadTools
34
47
  @contentions += 1
35
48
  else
36
49
  @owner = Thread.current
50
+ if @owner[:locks].nil?
51
+ @owner[:locks] = []
52
+ end
53
+ @owner[:locks] << self
37
54
  end
38
55
  ret
39
56
  end
40
57
 
58
+ # throws EMutexOrder, mutex remains locked in this case
41
59
  def unlock
42
- @owner = nil
60
+ if (!@owner.nil?)
61
+ if (@owner[:locks].last == self)
62
+ @owner[:locks].pop
63
+ else
64
+ if @owner[:locks].delete(self)
65
+ @out_of_order_locks += 1
66
+ raise EMutexOrder.new("Expected #{@owner[:locks].last}")
67
+ end
68
+ # if called again let it pass
69
+ end
70
+ @owner = nil
71
+ end
43
72
  super
44
73
  end
45
74
 
@@ -51,6 +80,17 @@ module ThreadTools
51
80
  unlock
52
81
  end
53
82
  end
83
+
84
+ def inspect
85
+ "Owner #{@owner}, Contentions #{@contentions}, Out of order acquisitions #{@out_of_order_locks}"
86
+ end
87
+
88
+ # just for fun
89
+ def self.unlock_all(thread)
90
+ thread[:locks].reverse_each do |l|
91
+ l.unlock
92
+ end
93
+ end
54
94
  end
55
95
 
56
96
  end
@@ -13,7 +13,7 @@ require File.expand_path(File.dirname(__FILE__)+'/threadpool')
13
13
  module Mongrel
14
14
  class HttpServer
15
15
 
16
- def run(_pool_size = 100)
16
+ def run(_pool_size = 50)
17
17
  trap("TERM") { stop } # trap "kill"
18
18
 
19
19
  @thread_pool = ThreadTools::ThreadPool.new(_pool_size, @workers)
@@ -20,19 +20,23 @@ module ThreadTools
20
20
  end
21
21
 
22
22
  def acquire
23
+ ret = 0
23
24
  @mtx.synchronize do
24
25
  @cv.wait(@mtx) until @count > 0
25
26
  @count -= 1
27
+ ret = @count
26
28
  end
27
- @count
29
+ ret
28
30
  end
29
31
 
30
32
  def release
33
+ ret = 0
31
34
  @mtx.synchronize do
32
35
  @count += 1
36
+ ret = @count
33
37
  @cv.signal
34
38
  end
35
- @count
39
+ ret
36
40
  end
37
41
  end
38
42
 
@@ -25,6 +25,8 @@ module ThreadTools
25
25
  attr_accessor :kill_worker_on_exception
26
26
  # number of worker threads
27
27
  attr_reader :size
28
+ # if set to true a new worker is created if the pool is empty
29
+ attr_accessor :create_on_spawn
28
30
 
29
31
  # _size should be at least 1
30
32
  def initialize(_size, _thr_group = nil)
@@ -34,6 +36,7 @@ module ThreadTools
34
36
  @pool_cv = ConditionVariable.new
35
37
  @pool = []
36
38
  @thr_grp = _thr_group
39
+ @create_on_spawn = false
37
40
  _size = 1 if _size < 1
38
41
  _size.times { create_worker }
39
42
  end
@@ -76,6 +79,10 @@ module ThreadTools
76
79
  def spawn(*args, &block)
77
80
  thr = nil
78
81
  @pool_mtx.synchronize do
82
+ # creates a new worker thread if pool is empty and flag is set
83
+ if (@create_on_spawn && @pool.empty?)
84
+ create_worker
85
+ end
79
86
  # wait here until a worker is available
80
87
  @pool_cv.wait(@pool_mtx) until !(thr = @pool.shift).nil?
81
88
  thr[:jobs] << { :args => args, :block => block }
@@ -98,4 +105,4 @@ module ThreadTools
98
105
  end
99
106
  end
100
107
 
101
- end
108
+ end
@@ -44,4 +44,17 @@ class DebugMutexTest < Test::Unit::TestCase
44
44
  # owner is nil
45
45
  assert_nil(mtx.owner)
46
46
  end
47
+
48
+ def test_lock_ordering
49
+ mtxA = ThreadTools::DebugMutex.new
50
+ mtxB = ThreadTools::DebugMutex.new
51
+
52
+ mtxA.lock
53
+ mtxB.lock
54
+ assert_raise ThreadTools::EMutexOrder do
55
+ mtxA.unlock # wrong unlock order
56
+ end
57
+ # ensure correct ordering
58
+ ThreadTools::DebugMutex.unlock_all(Thread.current)
59
+ end
47
60
  end
data/thread_tools.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{thread_tools}
3
- s.version = '0.25'
3
+ s.version = '0.26'
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.25"
4
+ version: "0.26"
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-05 00:00:00 +02:00
12
+ date: 2009-10-06 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies: []
15
15