quack_concurrency 0.5.4 → 0.6.0
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/quack_concurrency.rb +6 -13
- data/lib/quack_concurrency/condition_variable.rb +91 -85
- data/lib/quack_concurrency/condition_variable/waitable.rb +108 -0
- data/lib/quack_concurrency/error.rb +1 -1
- data/lib/quack_concurrency/future.rb +31 -30
- data/lib/quack_concurrency/future/canceled.rb +1 -0
- data/lib/quack_concurrency/future/complete.rb +1 -0
- data/lib/quack_concurrency/mutex.rb +140 -38
- data/lib/quack_concurrency/queue.rb +32 -28
- data/lib/quack_concurrency/reentrant_mutex.rb +64 -76
- data/lib/quack_concurrency/safe_condition_variable.rb +23 -0
- data/lib/quack_concurrency/safe_condition_variable/waitable.rb +21 -0
- data/lib/quack_concurrency/safe_sleeper.rb +80 -0
- data/lib/quack_concurrency/sleeper.rb +100 -0
- data/lib/quack_concurrency/waiter.rb +32 -23
- data/spec/condition_variable_spec.rb +216 -0
- data/spec/future_spec.rb +145 -79
- data/spec/mutex_spec.rb +441 -0
- data/spec/queue_spec.rb +217 -77
- data/spec/reentrant_mutex_spec.rb +394 -99
- data/spec/safe_condition_variable_spec.rb +115 -0
- data/spec/safe_sleeper_spec.rb +197 -0
- data/spec/sleeper.rb +197 -0
- data/spec/waiter_spec.rb +181 -0
- metadata +16 -14
- data/lib/quack_concurrency/queue/error.rb +0 -6
- data/lib/quack_concurrency/reentrant_mutex/error.rb +0 -6
- data/lib/quack_concurrency/semaphore.rb +0 -139
- data/lib/quack_concurrency/semaphore/error.rb +0 -6
- data/lib/quack_concurrency/uninterruptible_condition_variable.rb +0 -94
- data/lib/quack_concurrency/uninterruptible_sleeper.rb +0 -81
- data/lib/quack_concurrency/yielder.rb +0 -35
- data/spec/semaphore_spec.rb +0 -244
@@ -1,26 +1,31 @@
|
|
1
1
|
module QuackConcurrency
|
2
|
-
|
3
|
-
#
|
2
|
+
|
3
|
+
# {Mutex} is similar to +::Mutex+.
|
4
|
+
#
|
5
|
+
# A few differences include:
|
6
|
+
# * {#lock} supports passing a block and behaves like +::Mutex#synchronize+
|
7
|
+
# * {#unlock} supports passing a block
|
4
8
|
class Mutex
|
5
|
-
|
9
|
+
|
6
10
|
# Creates a new {Mutex} concurrency tool.
|
7
11
|
# @return [Mutex]
|
8
12
|
def initialize
|
13
|
+
@condition_variable = SafeConditionVariable.new
|
9
14
|
@mutex = ::Mutex.new
|
10
|
-
@condition_variable = UninterruptibleConditionVariable.new
|
11
15
|
@owner = nil
|
12
16
|
end
|
13
|
-
|
14
|
-
# @raise [ThreadError] if current
|
17
|
+
|
18
|
+
# @raise [ThreadError] if current thread is already locking it
|
15
19
|
#@overload lock
|
16
|
-
# Obtains the lock or sleeps the current
|
20
|
+
# Obtains the lock or sleeps the current thread until it is available.
|
17
21
|
# @return [void]
|
18
22
|
#@overload lock(&block)
|
19
23
|
# Obtains the lock, runs the block, then releases the lock when the block completes.
|
20
|
-
# @
|
24
|
+
# @raise [Exception] any exception raised in block
|
25
|
+
# @yield block to run while holding the lock
|
21
26
|
# @return [Object] result of the block
|
22
27
|
def lock(&block)
|
23
|
-
raise ThreadError, '
|
28
|
+
raise ThreadError, 'Attempt to lock a mutex which is already locked by this thread' if owned?
|
24
29
|
if block_given?
|
25
30
|
lock
|
26
31
|
begin
|
@@ -36,40 +41,63 @@ module QuackConcurrency
|
|
36
41
|
nil
|
37
42
|
end
|
38
43
|
end
|
39
|
-
|
44
|
+
|
45
|
+
# Checks if it is locked by a thread.
|
46
|
+
# @return [Boolean]
|
40
47
|
def locked?
|
41
48
|
!!@owner
|
42
49
|
end
|
43
|
-
|
50
|
+
|
51
|
+
# Checks if it is locked by another thread.
|
52
|
+
# @return [Boolean]
|
53
|
+
def locked_out?
|
54
|
+
# don't need a mutex because we know #owned? can't change during the call
|
55
|
+
locked? && !owned?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Checks if it is locked by current thread.
|
59
|
+
# @return [Boolean]
|
44
60
|
def owned?
|
45
61
|
@owner == caller
|
46
62
|
end
|
47
|
-
|
63
|
+
|
64
|
+
# Returns the thread locking it if one exists.
|
65
|
+
# @return [nil,Thread] the locking +Thread+ if one exists, otherwise +nil+
|
48
66
|
def owner
|
49
67
|
@owner
|
50
68
|
end
|
51
|
-
|
69
|
+
|
70
|
+
# Releases the lock and puts this thread to sleep.
|
71
|
+
# @param timeout [nil, Numeric] time to sleep in seconds or +nil+ to sleep forever
|
72
|
+
# @raise [TypeError] if +timeout+ is not +nil+ or +Numeric+
|
73
|
+
# @raise [ArgumentError] if +timeout+ is not positive
|
74
|
+
# @return [Integer] elapsed time sleeping
|
52
75
|
def sleep(timeout = nil)
|
53
|
-
|
54
|
-
raise ArgumentError, "'timeout' argument must be nil or a Numeric"
|
55
|
-
end
|
76
|
+
validate_timeout(timeout)
|
56
77
|
unlock do
|
57
|
-
if timeout
|
58
|
-
elapsed_time =
|
78
|
+
if timeout == nil || timeout == Float::INFINITY
|
79
|
+
elapsed_time = (timer { Thread.stop }).round
|
59
80
|
else
|
60
|
-
elapsed_time = Kernel.sleep
|
81
|
+
elapsed_time = Kernel.sleep(timeout)
|
61
82
|
end
|
62
83
|
end
|
63
84
|
end
|
64
|
-
|
85
|
+
|
86
|
+
# Obtains the lock or blocks until the lock is available.
|
87
|
+
# @raise [ThreadError] if block not given
|
88
|
+
# @raise [ThreadError] if current thread is already locking it
|
89
|
+
# @raise [Exception] any exception raised in block
|
90
|
+
# @return [Object] value return from block
|
65
91
|
def synchronize(&block)
|
92
|
+
raise ThreadError, 'must be called with a block' unless block_given?
|
66
93
|
lock(&block)
|
67
94
|
end
|
68
|
-
|
69
|
-
# Attempts to obtain the lock and
|
95
|
+
|
96
|
+
# Attempts to obtain the lock and return immediately.
|
97
|
+
# @raise [ThreadError] if current thread is already locking it
|
70
98
|
# @return [Boolean] returns if the lock was granted
|
71
99
|
def try_lock
|
72
|
-
raise ThreadError, '
|
100
|
+
raise ThreadError, 'Attempt to lock a mutex which is already locked by this thread' if owned?
|
73
101
|
@mutex.synchronize do
|
74
102
|
if locked?
|
75
103
|
false
|
@@ -79,25 +107,33 @@ module QuackConcurrency
|
|
79
107
|
end
|
80
108
|
end
|
81
109
|
end
|
82
|
-
|
110
|
+
|
111
|
+
# @raise [ThreadError] if current thread is not locking it
|
112
|
+
#@overload unlock
|
113
|
+
# Releases the lock
|
114
|
+
# @return [void]
|
115
|
+
#@overload unlock(&block)
|
116
|
+
# Releases the lock, runs the block, then reacquires the lock when available,
|
117
|
+
# blocking if necessary.
|
118
|
+
# @raise [Exception] any exception raised in block
|
119
|
+
# @yield block to run while releasing the lock
|
120
|
+
# @return [Object] result of the block
|
83
121
|
def unlock(&block)
|
84
122
|
if block_given?
|
85
|
-
|
86
|
-
begin
|
87
|
-
yield
|
88
|
-
ensure
|
89
|
-
lock
|
90
|
-
end
|
123
|
+
temporarily_release(&block)
|
91
124
|
else
|
92
125
|
@mutex.synchronize do
|
93
|
-
|
94
|
-
raise ThreadError, 'current Thread is not locking the Mutex' unless owned?
|
126
|
+
ensure_can_unlock
|
95
127
|
if @condition_variable.any_waiting_threads?
|
96
128
|
@condition_variable.signal
|
97
129
|
|
98
130
|
# we do this to avoid a bug
|
99
|
-
# consider
|
100
|
-
#
|
131
|
+
# consider this problem, imagine we have three threads:
|
132
|
+
# * A: this thread
|
133
|
+
# * B: has previously called #lock and is waiting on the @condition_variable
|
134
|
+
# * C: enters #lock after A has released the lock but before B has reacquired it
|
135
|
+
# is this scenario the threads may end up executing not in the chronological order
|
136
|
+
# that they entered #lock
|
101
137
|
@owner = true
|
102
138
|
else
|
103
139
|
@owner = nil
|
@@ -106,16 +142,82 @@ module QuackConcurrency
|
|
106
142
|
nil
|
107
143
|
end
|
108
144
|
end
|
109
|
-
|
145
|
+
|
146
|
+
# Returns the number of threads currently waiting on it.
|
147
|
+
# @return [Integer]
|
110
148
|
def waiting_threads_count
|
111
149
|
@condition_variable.waiting_threads_count
|
112
150
|
end
|
113
|
-
|
151
|
+
|
114
152
|
private
|
115
|
-
|
153
|
+
|
154
|
+
# Returns the current thread.
|
155
|
+
# @return [Thread]
|
116
156
|
def caller
|
117
157
|
Thread.current
|
118
158
|
end
|
119
|
-
|
159
|
+
|
160
|
+
# Ensure it can be unlocked
|
161
|
+
# @raise [ThreadError] if it is not locked by the calling thread
|
162
|
+
def ensure_can_unlock
|
163
|
+
raise ThreadError, 'Attempt to unlock a ReentrantMutex which is not locked' unless locked?
|
164
|
+
raise ThreadError, 'Attempt to unlock a ReentrantMutex which is locked by another thread' unless owned?
|
165
|
+
end
|
166
|
+
|
167
|
+
# Try to immediately lock it.
|
168
|
+
# @api private
|
169
|
+
# @raise [ThreadError] if another thread is locking it
|
170
|
+
# @return [void]
|
171
|
+
def lock_immediately
|
172
|
+
unless try_lock
|
173
|
+
raise ThreadError, 'Attempt to lock a mutex which is locked by another thread'
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Temporarily unlocks it while a block is run.
|
178
|
+
# If an error is raised in the block the it will try to be immediately relocked
|
179
|
+
# before passing the error up. If unsuccessful, a +ThreadError+ will be raised to
|
180
|
+
# imitate the core's behavior.
|
181
|
+
# @api private
|
182
|
+
# @raise [ThreadError] if relock unsuccessful after an error
|
183
|
+
# @raise [ArgumentError] if no block given
|
184
|
+
# @return [void]
|
185
|
+
def temporarily_release(&block)
|
186
|
+
raise ArgumentError, 'no block given' unless block_given?
|
187
|
+
unlock
|
188
|
+
begin
|
189
|
+
return_value = yield
|
190
|
+
lock
|
191
|
+
rescue Exception
|
192
|
+
lock_immediately
|
193
|
+
raise
|
194
|
+
end
|
195
|
+
return_value
|
196
|
+
end
|
197
|
+
|
198
|
+
# Calculate time elapsed when running block.
|
199
|
+
# @api private
|
200
|
+
# @yield called while running timer
|
201
|
+
# @yieldparam start_time [Time]
|
202
|
+
# @raise [Exception] any exception raised in block
|
203
|
+
# @return [Float] time elapsed while running block
|
204
|
+
def timer(&block)
|
205
|
+
start_time = Time.now
|
206
|
+
yield(start_time)
|
207
|
+
time_elapsed = Time.now - start_time
|
208
|
+
end
|
209
|
+
|
210
|
+
# Validates a timeout value
|
211
|
+
# @api private
|
212
|
+
# @raise [TypeError] if {timeout} is not +nil+ or +Numeric+
|
213
|
+
# @raise [ArgumentError] if {timeout} is not positive
|
214
|
+
# @return [void]
|
215
|
+
def validate_timeout(timeout)
|
216
|
+
unless timeout == nil
|
217
|
+
raise TypeError, "'timeout' must be nil or a Numeric" unless timeout.is_a?(Numeric)
|
218
|
+
raise ArgumentError, "'timeout' must not be negative" if timeout.negative?
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
120
222
|
end
|
121
223
|
end
|
@@ -1,31 +1,34 @@
|
|
1
1
|
module QuackConcurrency
|
2
|
-
|
3
|
-
#
|
2
|
+
|
3
|
+
# This is a duck type for +::Thread::Queue+.
|
4
|
+
# It is intended to be a drop in replacement for it's core counterpart.
|
5
|
+
# Valuable if +::Thread::Queue+ has not been implemented.
|
4
6
|
class Queue
|
5
|
-
|
7
|
+
|
6
8
|
# Creates a new {Queue} concurrency tool.
|
7
9
|
# @return [Queue]
|
8
10
|
def initialize
|
11
|
+
@closed = false
|
12
|
+
@items = []
|
9
13
|
@mutex = ::Mutex.new
|
10
14
|
@pop_mutex = Mutex.new
|
11
15
|
@waiter = Waiter.new
|
12
|
-
@items = []
|
13
|
-
@closed = false
|
14
16
|
end
|
15
|
-
|
16
|
-
# Removes all objects from
|
17
|
+
|
18
|
+
# Removes all objects from it.
|
17
19
|
# @return [self]
|
18
20
|
def clear
|
19
21
|
@mutex.synchronize { @items.clear }
|
20
22
|
self
|
21
23
|
end
|
22
|
-
|
23
|
-
# Closes
|
24
|
+
|
25
|
+
# Closes it.
|
26
|
+
# Once closed, it cannot be re-opened.
|
24
27
|
# After the call to close completes, the following are true:
|
25
|
-
# * {#closed?} will return
|
28
|
+
# * {#closed?} will return +true+.
|
26
29
|
# * {#close} will be ignored.
|
27
30
|
# * {#push} will raise an exception.
|
28
|
-
# * until empty, calling {#pop} will return an object from
|
31
|
+
# * until empty, calling {#pop} will return an object from it as usual.
|
29
32
|
# @return [self]
|
30
33
|
def close
|
31
34
|
@mutex.synchronize do
|
@@ -35,37 +38,38 @@ module QuackConcurrency
|
|
35
38
|
end
|
36
39
|
self
|
37
40
|
end
|
38
|
-
|
39
|
-
# Checks if
|
41
|
+
|
42
|
+
# Checks if it is closed.
|
40
43
|
# @return [Boolean]
|
41
44
|
def closed?
|
42
45
|
@closed
|
43
46
|
end
|
44
|
-
|
45
|
-
# Checks if
|
47
|
+
|
48
|
+
# Checks if it is empty.
|
46
49
|
# @return [Boolean]
|
47
50
|
def empty?
|
48
51
|
@items.empty?
|
49
52
|
end
|
50
|
-
|
51
|
-
# Returns the length of
|
53
|
+
|
54
|
+
# Returns the length of it.
|
52
55
|
# @return [Integer]
|
53
56
|
def length
|
54
57
|
@items.length
|
55
58
|
end
|
56
59
|
alias_method :size, :length
|
57
|
-
|
58
|
-
# Returns the number of threads waiting on
|
60
|
+
|
61
|
+
# Returns the number of threads waiting on it.
|
59
62
|
# @return [Integer]
|
60
63
|
def num_waiting
|
61
64
|
@pop_mutex.waiting_threads_count + @waiter.waiting_threads_count
|
62
65
|
end
|
63
|
-
|
64
|
-
# Retrieves item from
|
65
|
-
# @note If
|
66
|
-
#
|
67
|
-
# @raise
|
66
|
+
|
67
|
+
# Retrieves an item from it.
|
68
|
+
# @note If it is empty, the method will block until an item is available.
|
69
|
+
# If +non_block+ is +true+, a +ThreadError+ will be raised.
|
70
|
+
# @raise [ThreadError] if it is empty and +non_block+ is +true+
|
68
71
|
# @param non_block [Boolean]
|
72
|
+
# @return [Object]
|
69
73
|
def pop(non_block = false)
|
70
74
|
@pop_mutex.lock do
|
71
75
|
@mutex.synchronize do
|
@@ -83,20 +87,20 @@ module QuackConcurrency
|
|
83
87
|
end
|
84
88
|
alias_method :deq, :pop
|
85
89
|
alias_method :shift, :pop
|
86
|
-
|
87
|
-
# Pushes the given object to
|
90
|
+
|
91
|
+
# Pushes the given object to it.
|
88
92
|
# @param item [Object]
|
89
93
|
# @return [self]
|
90
94
|
def push(item = nil)
|
91
95
|
@mutex.synchronize do
|
92
96
|
raise ClosedQueueError if closed?
|
93
97
|
@items.push(item)
|
94
|
-
@waiter.
|
98
|
+
@waiter.resume_next
|
95
99
|
end
|
96
100
|
self
|
97
101
|
end
|
98
102
|
alias_method :<<, :push
|
99
103
|
alias_method :enq, :push
|
100
|
-
|
104
|
+
|
101
105
|
end
|
102
106
|
end
|
@@ -2,32 +2,41 @@
|
|
2
2
|
|
3
3
|
|
4
4
|
module QuackConcurrency
|
5
|
+
|
6
|
+
# {ReentrantMutex}s are similar to {Mutex}s with with the key distinction being
|
7
|
+
# that a thread can call lock on a {Mutex} that it has already locked.
|
5
8
|
class ReentrantMutex < Mutex
|
6
|
-
|
9
|
+
|
7
10
|
# Creates a new {ReentrantMutex} concurrency tool.
|
8
11
|
# @return [ReentrantMutex]
|
9
12
|
def initialize
|
10
13
|
super
|
11
14
|
@lock_depth = 0
|
12
15
|
end
|
13
|
-
|
16
|
+
|
14
17
|
#@overload lock
|
15
|
-
# Obtains
|
18
|
+
# Obtains a lock, blocking until available.
|
19
|
+
# It will acquire a lock even if one is already held.
|
16
20
|
# @return [void]
|
17
21
|
#@overload lock(&block)
|
18
|
-
# Obtains
|
22
|
+
# Obtains a lock, runs the block, then releases a lock.
|
23
|
+
# It will block until a lock is available.
|
24
|
+
# It will acquire a lock even if one is already held.
|
25
|
+
# @raise [ThreadError] if not locked by the calling thread when unlocking
|
26
|
+
# @raise [ThreadError] if not holding the same lock count when unlocking
|
27
|
+
# @raise [Exception] any exception raised in block
|
19
28
|
# @yield block to run with the lock
|
20
29
|
# @return [Object] result of the block
|
21
30
|
def lock(&block)
|
22
31
|
if block_given?
|
23
32
|
lock
|
24
33
|
start_depth = @lock_depth
|
25
|
-
start_owner = owner
|
26
34
|
begin
|
27
35
|
yield
|
28
36
|
ensure
|
29
|
-
|
30
|
-
|
37
|
+
ensure_can_unlock
|
38
|
+
unless @lock_depth == start_depth
|
39
|
+
raise ThreadError, 'Attempt to unlock a ReentrantMutex whose lock depth has been changed since locking it'
|
31
40
|
end
|
32
41
|
unlock
|
33
42
|
end
|
@@ -37,105 +46,84 @@ module QuackConcurrency
|
|
37
46
|
nil
|
38
47
|
end
|
39
48
|
end
|
40
|
-
|
41
|
-
#
|
42
|
-
# @return [Boolean]
|
43
|
-
def locked_out?
|
44
|
-
# don't need a mutex because we know #owned? can't change during the call
|
45
|
-
locked? && !owned?
|
46
|
-
end
|
47
|
-
|
48
|
-
# Releases the lock and sleeps.
|
49
|
-
# When the calling Thread is next woken up, it will attempt to reacquire the lock.
|
50
|
-
# @param timeout [Integer] seconds to sleep, `nil` will sleep forever
|
51
|
-
# @raise [Error] if this {ReentrantMutex} wasn't locked by the calling Thread
|
52
|
-
# @return [void]
|
49
|
+
|
50
|
+
# @see Mutex#sleep
|
53
51
|
def sleep(timeout = nil)
|
54
|
-
|
55
|
-
raise Error, 'can not unlock reentrant mutex, caller is not the owner' unless owned?
|
52
|
+
ensure_can_unlock
|
56
53
|
base_depth do
|
57
54
|
super(timeout)
|
58
55
|
end
|
59
56
|
end
|
60
|
-
|
61
|
-
# Obtains a lock, runs the block, and releases the lock when the block completes.
|
62
|
-
# @return [Object] value from yielded block
|
63
|
-
def synchronize(&block)
|
64
|
-
lock(&block)
|
65
|
-
end
|
66
|
-
|
67
|
-
alias parent_try_lock try_lock
|
68
|
-
private :parent_try_lock
|
57
|
+
|
69
58
|
# Attempts to obtain the lock and returns immediately.
|
70
59
|
# @return [Boolean] returns if the lock was granted
|
71
60
|
def try_lock
|
72
|
-
if owned?
|
61
|
+
if owned? || super
|
73
62
|
@lock_depth += 1
|
74
63
|
true
|
75
64
|
else
|
76
|
-
|
77
|
-
if lock_successful
|
78
|
-
@lock_depth += 1
|
79
|
-
true
|
80
|
-
else
|
81
|
-
false
|
82
|
-
end
|
65
|
+
false
|
83
66
|
end
|
84
67
|
end
|
85
|
-
|
86
|
-
|
87
|
-
#
|
88
|
-
#
|
68
|
+
|
69
|
+
#@overload unlock
|
70
|
+
# Releases a lock.
|
71
|
+
# @return [void]
|
72
|
+
#@overload unlock(&block)
|
73
|
+
# Releases a lock, runs the block, then reacquires the lock when available,
|
74
|
+
# blocking if necessary.
|
75
|
+
# @raise [Exception] any exception raised in block
|
76
|
+
# @raise [ThreadError] if relock unsuccessful after an error
|
77
|
+
# @yield block to run while releasing the lock
|
78
|
+
# @return [Object] result of the block
|
79
|
+
# @raise [ThreadError] if it is not locked by this thread
|
89
80
|
def unlock(&block)
|
90
|
-
|
91
|
-
raise Error, 'can not unlock reentrant mutex, caller is not the owner' unless owned?
|
81
|
+
ensure_can_unlock
|
92
82
|
if block_given?
|
93
|
-
|
94
|
-
begin
|
95
|
-
yield
|
96
|
-
ensure
|
97
|
-
lock
|
98
|
-
end
|
83
|
+
temporarily_release(&block)
|
99
84
|
else
|
100
85
|
@lock_depth -= 1
|
101
86
|
super if @lock_depth == 0
|
102
87
|
nil
|
103
88
|
end
|
104
89
|
end
|
105
|
-
|
106
|
-
# Releases the lock
|
107
|
-
#
|
108
|
-
# @
|
90
|
+
|
91
|
+
# Releases all lock, runs the block, then reacquires the same lock count when available,
|
92
|
+
# blocking if necessary.
|
93
|
+
# @raise [ArgumentError] if no block given
|
94
|
+
# @raise [ThreadError] if this thread does not hold any locks
|
95
|
+
# @raise [Exception] any exception raised in block
|
96
|
+
# @yield block to run while locks have been released
|
97
|
+
# @return [Object] result of the block
|
109
98
|
def unlock!(&block)
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
base_depth do
|
114
|
-
unlock
|
115
|
-
begin
|
116
|
-
yield
|
117
|
-
ensure
|
118
|
-
lock
|
119
|
-
end
|
120
|
-
end
|
121
|
-
else
|
122
|
-
@lock_depth = 0
|
123
|
-
super
|
124
|
-
nil
|
99
|
+
ensure_can_unlock
|
100
|
+
base_depth do
|
101
|
+
temporarily_release(&block)
|
125
102
|
end
|
126
103
|
end
|
127
|
-
|
104
|
+
|
128
105
|
private
|
129
|
-
|
106
|
+
|
107
|
+
# Releases all but one lock, runs the block, then reacquires the released lock count when available,
|
108
|
+
# blocking if necessary.
|
130
109
|
# @api private
|
110
|
+
# @raise [Exception] any exception raised in block
|
111
|
+
# @return [Object] result of the block
|
131
112
|
def base_depth(&block)
|
132
113
|
start_depth = @lock_depth
|
133
114
|
@lock_depth = 1
|
134
|
-
yield
|
135
|
-
ensure
|
115
|
+
return_value = yield
|
136
116
|
@lock_depth = start_depth
|
117
|
+
return_value
|
137
118
|
end
|
138
|
-
|
119
|
+
|
120
|
+
# Ensure it can be unlocked
|
121
|
+
# @raise [ThreadError] if it is not locked by this thread
|
122
|
+
# @return [void]
|
123
|
+
def ensure_can_unlock
|
124
|
+
raise ThreadError, 'Attempt to unlock a ReentrantMutex which is not locked' unless locked?
|
125
|
+
raise ThreadError, 'Attempt to unlock a ReentrantMutex which is locked by another thread' unless owned?
|
126
|
+
end
|
127
|
+
|
139
128
|
end
|
140
129
|
end
|
141
|
-
|