thread_tools 0.25 → 0.26

Sign up to get free protection for your applications and to get access to all the features.
@@ -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