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
@@ -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
|