quack_concurrency 0.5.4 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,23 @@
|
|
1
|
+
module QuackConcurrency
|
2
|
+
|
3
|
+
# {SafeConditionVariable} is similar to {ConditionVariable}.
|
4
|
+
#
|
5
|
+
# The key distinction is that every call to {#wait} can only be resumed via
|
6
|
+
# the {SafeConditionVariable} (not with +Thread#run+, +Thread#wakeup+, etc.)
|
7
|
+
class SafeConditionVariable < ConditionVariable
|
8
|
+
|
9
|
+
# #@!method wait
|
10
|
+
# Puts this thread to sleep until another thread resumes it via this {SafeConditionVariable}.
|
11
|
+
# @see ConditionVariable#wait
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# Returns a waitable object for current thread.
|
16
|
+
# @api private
|
17
|
+
# @return [Waitable]
|
18
|
+
def waitable_for_current_thread
|
19
|
+
Waitable.new(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module QuackConcurrency
|
2
|
+
class SafeConditionVariable
|
3
|
+
|
4
|
+
# @see ConditionVariable::Waitable
|
5
|
+
# Uses {SafeSleeper}s to ensure the thread can only be woken by this {SafeConditionVariable}.
|
6
|
+
class Waitable < ConditionVariable::Waitable
|
7
|
+
|
8
|
+
# Creates a new {Waitable}.
|
9
|
+
# @return [Waitable]
|
10
|
+
def initialize(condition_variable)
|
11
|
+
super(condition_variable)
|
12
|
+
@sleeper = SafeSleeper.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# @!method wait
|
16
|
+
# Can only be resumed via {#resume}.
|
17
|
+
# @see ConditionVariable#wait
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module QuackConcurrency
|
2
|
+
|
3
|
+
# A {SafeSleeper} can be used to safely sleep a thread or preemptively wake it.
|
4
|
+
#
|
5
|
+
# Unlike simply calling +Thread#sleep+, {#sleep} will ensure that only
|
6
|
+
# calling {#wake} on this {SafeSleeper} will wake the thread.
|
7
|
+
# Any call to +Thread#run+ directly, will be ignored.
|
8
|
+
# Threads are still be resumed if +Thread#raise+ is called which may cause
|
9
|
+
# problems so it should never be used.
|
10
|
+
# A thread can only be put to sleep and woken once for each {SafeSleeper}.
|
11
|
+
class SafeSleeper < Sleeper
|
12
|
+
|
13
|
+
# Creates a new {SafeSleeper} concurrency tool.
|
14
|
+
# @return [SafeSleeper]
|
15
|
+
def initialize
|
16
|
+
super
|
17
|
+
@state = :initial
|
18
|
+
end
|
19
|
+
|
20
|
+
# @see SafeSleeper#sleep
|
21
|
+
def sleep(timeout = nil)
|
22
|
+
timer do |start_time|
|
23
|
+
deadline = wake_deadline(start_time, timeout)
|
24
|
+
enforce_sleep_call_limit
|
25
|
+
@mutex.synchronize do
|
26
|
+
break if @state == :complete
|
27
|
+
@state == :sleep
|
28
|
+
wait(deadline)
|
29
|
+
ensure
|
30
|
+
@state = :complete
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @see SafeSleeper#wake
|
36
|
+
def wake
|
37
|
+
@mutex.synchronize do
|
38
|
+
enforce_wake_call_limit
|
39
|
+
@state = :complete
|
40
|
+
@condition_variable.signal
|
41
|
+
end
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Put this thread to sleep and wait for it to be woken.
|
48
|
+
# Will wake if {#wake} is called.
|
49
|
+
# If called with a +deadline+ it will wake when +deadline+ is reached.
|
50
|
+
# @api private
|
51
|
+
# @param deadline [nil,Time] maximum time to sleep, +nil+ for forever
|
52
|
+
# @raise [Exception] any exception raised by +ConditionVariable#wait+ (eg. interrupts, +ThreadError+)
|
53
|
+
# @return [void]
|
54
|
+
def wait(deadline)
|
55
|
+
loop do
|
56
|
+
if deadline
|
57
|
+
remaining = deadline - Time.now
|
58
|
+
@condition_variable.wait(@mutex, remaining) if remaining > 0
|
59
|
+
else
|
60
|
+
@condition_variable.wait(@mutex)
|
61
|
+
end
|
62
|
+
break if @state == :complete
|
63
|
+
break if deadline && Time.now >= deadline
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Calculate the desired time to wake up.
|
68
|
+
# @api private
|
69
|
+
# @param start_time [nil,Time] time when the thread is put to sleep
|
70
|
+
# @param timeout [Numeric] desired time to sleep in seconds, +nil+ for forever
|
71
|
+
# @raise [TypeError] if +start_time+ is not +nil+ or a +Numeric+
|
72
|
+
# @raise [ArgumentError] if +start_time+ is negative
|
73
|
+
# @return [Time]
|
74
|
+
def wake_deadline(start_time, timeout)
|
75
|
+
timeout = process_timeout(timeout)
|
76
|
+
deadline = start_time + timeout if timeout
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module QuackConcurrency
|
2
|
+
|
3
|
+
# A {Sleeper} can be used to preemptively wake a thread that will be put to sleep in the future.
|
4
|
+
#
|
5
|
+
# A thread can only be put to sleep and woken once for each {Sleeper}.
|
6
|
+
class Sleeper
|
7
|
+
|
8
|
+
# Creates a new {Sleeper} concurrency tool.
|
9
|
+
# @return [Sleeper]
|
10
|
+
def initialize
|
11
|
+
@state = :initial
|
12
|
+
@mutex = ::Mutex.new
|
13
|
+
@condition_variable = ::ConditionVariable.new
|
14
|
+
@sleep_called = false
|
15
|
+
@wake_called = false
|
16
|
+
end
|
17
|
+
|
18
|
+
# Puts this thread to sleep.
|
19
|
+
# Will be skipped if {#wake} has already been called.
|
20
|
+
# If called without a timeout it will sleep forever.
|
21
|
+
# It can only be called once.
|
22
|
+
# @param timeout [nil,Numeric] maximum time to sleep in seconds, +nil+ for forever
|
23
|
+
# @raise [TypeError] if +timeout+ is not +nil+ or +Numeric+
|
24
|
+
# @raise [ArgumentError] if +timeout+ is negative
|
25
|
+
# @raise [RuntimeError] if already called once
|
26
|
+
# @raise [Exception] any exception raised by +ConditionVariable#wait+ (eg. interrupts, +ThreadError+)
|
27
|
+
# @return [Float] duration of time the thread was asleep in seconds
|
28
|
+
def sleep(timeout = nil)
|
29
|
+
timeout = process_timeout(timeout)
|
30
|
+
enforce_sleep_call_limit
|
31
|
+
@mutex.synchronize do
|
32
|
+
timer do
|
33
|
+
@condition_variable.wait(@mutex, timeout) unless @wake_called
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Wake it's sleeping thread, if one exists.
|
39
|
+
# It can only be called once.
|
40
|
+
# @raise [RuntimeError] if already called once
|
41
|
+
# @return [void]
|
42
|
+
def wake
|
43
|
+
@mutex.synchronize do
|
44
|
+
enforce_wake_call_limit
|
45
|
+
@condition_variable.signal
|
46
|
+
end
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Ensure {#sleep} is not called more than once.
|
53
|
+
# Call this every time {#sleep} is called.
|
54
|
+
# @api private
|
55
|
+
# @raise [RuntimeError] if called more than once
|
56
|
+
# @return [void]
|
57
|
+
def enforce_sleep_call_limit
|
58
|
+
raise RuntimeError, '#sleep has already been called once' if @sleep_called
|
59
|
+
@sleep_called = true
|
60
|
+
end
|
61
|
+
|
62
|
+
# Ensure {#wake} is not called more than once.
|
63
|
+
# Call this every time {#wake} is called.
|
64
|
+
# @api private
|
65
|
+
# @raise [RuntimeError] if called more than once
|
66
|
+
# @return [void]
|
67
|
+
def enforce_wake_call_limit
|
68
|
+
raise RuntimeError, '#wake has already been called once' if @wake_called
|
69
|
+
@wake_called = true
|
70
|
+
end
|
71
|
+
|
72
|
+
# Calculate time elapsed when running a block.
|
73
|
+
# @api private
|
74
|
+
# @yield called while running timer
|
75
|
+
# @yieldparam start_time [Time]
|
76
|
+
# @raise [Exception] any exception raised in block
|
77
|
+
# @return [Float] time elapsed while running block
|
78
|
+
def timer(&block)
|
79
|
+
start_time = Time.now
|
80
|
+
yield(start_time)
|
81
|
+
time_elapsed = Time.now - start_time
|
82
|
+
end
|
83
|
+
|
84
|
+
# Validates a timeout value, converting to a acceptable value if necessary
|
85
|
+
# @api private
|
86
|
+
# @param timeout [nil,Numeric]
|
87
|
+
# @raise [TypeError] if +timeout+ is not +nil+ or +Numeric+
|
88
|
+
# @raise [ArgumentError] if +timeout+ is negative
|
89
|
+
# @return [nil,Numeric]
|
90
|
+
def process_timeout(timeout)
|
91
|
+
unless timeout == nil
|
92
|
+
raise TypeError, "'timeout' must be nil or a Numeric" unless timeout.is_a?(Numeric)
|
93
|
+
raise ArgumentError, "'timeout' must not be negative" if timeout.negative?
|
94
|
+
end
|
95
|
+
timeout = nil if timeout == Float::INFINITY
|
96
|
+
timeout
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -1,58 +1,67 @@
|
|
1
1
|
module QuackConcurrency
|
2
|
-
|
2
|
+
|
3
|
+
# {Waiter} is similar to {ConditionVariable}.
|
4
|
+
#
|
5
|
+
# A few differences include:
|
6
|
+
# * the ability to force any future request to {#wait} to return immediately
|
7
|
+
# * every call to {#wait} can only be resumed via the {Waiter}
|
8
|
+
# (not with +Thread#run+, +Thread#wakeup+, etc.)
|
9
|
+
# * {#wait} does not accept a mutex
|
10
|
+
# * some methods have been renamed to be more intuitive
|
3
11
|
# @api private
|
4
12
|
class Waiter
|
5
|
-
|
13
|
+
|
6
14
|
# Creates a new {Waiter} concurrency tool.
|
7
15
|
# @return [Waiter]
|
8
16
|
def initialize
|
9
|
-
@condition_variable =
|
10
|
-
@
|
17
|
+
@condition_variable = SafeConditionVariable.new
|
18
|
+
@resume_all_indefinitely = false
|
11
19
|
@mutex = ::Mutex.new
|
12
20
|
end
|
13
|
-
|
21
|
+
|
22
|
+
# Checks if any threads are waiting on it.
|
23
|
+
# @return [Boolean]
|
14
24
|
def any_waiting_threads?
|
15
25
|
@condition_variable.any_waiting_threads?
|
16
26
|
end
|
17
|
-
|
18
|
-
# Resumes all
|
27
|
+
|
28
|
+
# Resumes all threads waiting on it.
|
19
29
|
# @return [void]
|
20
30
|
def resume_all
|
21
31
|
@condition_variable.broadcast
|
22
|
-
nil
|
23
32
|
end
|
24
|
-
|
25
|
-
# Resumes all
|
33
|
+
|
34
|
+
# Resumes all threads waiting on it and will cause
|
35
|
+
# any future calls to {#wait} to return immediately.
|
26
36
|
# @return [void]
|
27
|
-
def
|
37
|
+
def resume_all_indefinitely
|
28
38
|
@mutex.synchronize do
|
29
|
-
@
|
39
|
+
@resume_all_indefinitely = true
|
30
40
|
resume_all
|
31
41
|
end
|
32
|
-
nil
|
33
42
|
end
|
34
|
-
|
35
|
-
# Resumes next waiting
|
43
|
+
|
44
|
+
# Resumes next thread waiting on it if one exists.
|
36
45
|
# @return [void]
|
37
|
-
def
|
46
|
+
def resume_next
|
38
47
|
@condition_variable.signal
|
39
|
-
nil
|
40
48
|
end
|
41
|
-
|
42
|
-
#
|
49
|
+
|
50
|
+
# Puts this thread to sleep until another thread resumes it via this {Waiter}.
|
43
51
|
# @note Will block until resumed.
|
44
52
|
# @return [void]
|
45
53
|
def wait
|
46
54
|
@mutex.synchronize do
|
47
|
-
return if @
|
55
|
+
return if @resume_all_indefinitely
|
48
56
|
@condition_variable.wait(@mutex)
|
49
57
|
end
|
50
|
-
nil
|
51
58
|
end
|
52
|
-
|
59
|
+
|
60
|
+
# Returns the number of threads waiting on it.
|
61
|
+
# @return [Integer]
|
53
62
|
def waiting_threads_count
|
54
63
|
@condition_variable.waiting_threads_count
|
55
64
|
end
|
56
|
-
|
65
|
+
|
57
66
|
end
|
58
67
|
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'quack_concurrency'
|
2
|
+
|
3
|
+
describe QuackConcurrency::ConditionVariable do
|
4
|
+
|
5
|
+
describe "::new" do
|
6
|
+
|
7
|
+
context "when called with no arguments" do
|
8
|
+
it "should return a ConditionVariable" do
|
9
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
10
|
+
expect(condition_variable).to be_a(QuackConcurrency::ConditionVariable)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#any_waiting_threads?" do
|
17
|
+
|
18
|
+
context "when called with waiting threads" do
|
19
|
+
it "should return true" do
|
20
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
21
|
+
mutex = Mutex.new
|
22
|
+
thread = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
|
23
|
+
sleep 1
|
24
|
+
expect(condition_variable.any_waiting_threads?).to be true
|
25
|
+
condition_variable.broadcast
|
26
|
+
thread.join
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when called with no waiting threads" do
|
31
|
+
it "should return false" do
|
32
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
33
|
+
expect(condition_variable.any_waiting_threads?).to be false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#broadcast" do
|
40
|
+
|
41
|
+
context "when called with waiting threads" do
|
42
|
+
it "should resume all threads currently waiting" do
|
43
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
44
|
+
mutex = ::Mutex.new
|
45
|
+
thread1 = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
|
46
|
+
thread2 = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
|
47
|
+
sleep 1
|
48
|
+
condition_variable.broadcast
|
49
|
+
sleep 1
|
50
|
+
expect(thread1.alive?).to be false
|
51
|
+
expect(thread2.alive?).to be false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when called with no waiting threads" do
|
56
|
+
it "should not raise an error" do
|
57
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
58
|
+
expect{ condition_variable.broadcast }.not_to raise_error
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#signal" do
|
65
|
+
|
66
|
+
context "when called with waiting threads" do
|
67
|
+
it "should resume the next thread currently waiting" do
|
68
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
69
|
+
mutex = Mutex.new
|
70
|
+
values = []
|
71
|
+
Thread.new { mutex.synchronize { condition_variable.wait(mutex); values << 1 } }
|
72
|
+
Thread.new { sleep 1; mutex.synchronize { condition_variable.wait(mutex); values << 2 } }
|
73
|
+
Thread.new { sleep 2; mutex.synchronize { condition_variable.wait(mutex); values << 3 } }
|
74
|
+
sleep 3
|
75
|
+
condition_variable.signal
|
76
|
+
condition_variable.signal
|
77
|
+
condition_variable.signal
|
78
|
+
sleep 1
|
79
|
+
expect(values).to eq [1, 2, 3]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when called with no waiting threads" do
|
84
|
+
it "should not raise an error" do
|
85
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
86
|
+
expect{ condition_variable.signal }.not_to raise_error
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#wait" do
|
93
|
+
|
94
|
+
context "when called without a timeout" do
|
95
|
+
it "should block until #broadcast or #signal are called" do
|
96
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
97
|
+
mutex = Mutex.new
|
98
|
+
thread1 = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
|
99
|
+
thread2 = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
|
100
|
+
sleep 1
|
101
|
+
expect(thread1.alive?).to be true
|
102
|
+
expect(thread2.alive?).to be true
|
103
|
+
condition_variable.broadcast
|
104
|
+
sleep 1
|
105
|
+
expect(thread1.alive?).to be false
|
106
|
+
expect(thread2.alive?).to be false
|
107
|
+
end
|
108
|
+
context "and before Thread#run" do
|
109
|
+
it "should return after Thread#run is called" do
|
110
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
111
|
+
mutex = Mutex.new
|
112
|
+
thread = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
|
113
|
+
sleep 1
|
114
|
+
thread.run
|
115
|
+
sleep 1
|
116
|
+
expect(thread.alive?).to be false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "when called with a timeout" do
|
122
|
+
context "of nil" do
|
123
|
+
it "should return only after #broadcast or #signal are called" do
|
124
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
125
|
+
mutex = Mutex.new
|
126
|
+
thread = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
|
127
|
+
sleep 1
|
128
|
+
expect(thread.alive?).to be true
|
129
|
+
condition_variable.broadcast
|
130
|
+
sleep 1
|
131
|
+
expect(thread.alive?).to be false
|
132
|
+
end
|
133
|
+
end
|
134
|
+
context "of Float::INFINITY" do
|
135
|
+
it "should return only after #broadcast or #signal are called" do
|
136
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
137
|
+
mutex = Mutex.new
|
138
|
+
thread = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
|
139
|
+
sleep 1
|
140
|
+
expect(thread.alive?).to be true
|
141
|
+
condition_variable.broadcast
|
142
|
+
sleep 1
|
143
|
+
expect(thread.alive?).to be false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
context "of non Numeric value" do
|
147
|
+
it "should raise TypeError" do
|
148
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
149
|
+
mutex = Mutex.new
|
150
|
+
expect{ mutex.synchronize { condition_variable.wait(mutex, '1') } }.to raise_error(TypeError)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
context "of negative Numeric value" do
|
154
|
+
it "should raise ArgumentError" do
|
155
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
156
|
+
mutex = Mutex.new
|
157
|
+
expect{ mutex.synchronize { condition_variable.wait(mutex, -1) } }.to raise_error(ArgumentError)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
context "of positive Integer" do
|
161
|
+
it "should block until timeout reached" do
|
162
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
163
|
+
mutex = Mutex.new
|
164
|
+
thread = Thread.new { mutex.synchronize { condition_variable.wait(mutex, 2) } }
|
165
|
+
sleep 1
|
166
|
+
expect(thread.alive?).to be true
|
167
|
+
sleep 2
|
168
|
+
expect(thread.alive?).to be false
|
169
|
+
end
|
170
|
+
end
|
171
|
+
context "and Thread#run is called before timeout is reached" do
|
172
|
+
it "should return after Thread#run is called" do
|
173
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
174
|
+
mutex = Mutex.new
|
175
|
+
thread = Thread.new { mutex.synchronize { condition_variable.wait(mutex, 3) } }
|
176
|
+
sleep 1
|
177
|
+
thread.run
|
178
|
+
sleep 1
|
179
|
+
expect(thread.alive?).to be false
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "#waiting_threads_count" do
|
187
|
+
|
188
|
+
context "when called" do
|
189
|
+
it "should return a Integer" do
|
190
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
191
|
+
expect(condition_variable.waiting_threads_count).to be_a(Integer)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context "when called with no waiting threads" do
|
196
|
+
it "should return 0" do
|
197
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
198
|
+
expect(condition_variable.waiting_threads_count).to eq(0)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context "when called with one waiting thread" do
|
203
|
+
it "should return 1" do
|
204
|
+
condition_variable = QuackConcurrency::ConditionVariable.new
|
205
|
+
mutex = Mutex.new
|
206
|
+
thread = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
|
207
|
+
sleep 1
|
208
|
+
expect(condition_variable.waiting_threads_count).to eq(1)
|
209
|
+
condition_variable.broadcast
|
210
|
+
thread.join
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|