quack_concurrency 0.4.1 → 0.5.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/condition_variable.rb +116 -0
- data/lib/quack_concurrency/future.rb +43 -32
- data/lib/quack_concurrency/mutex.rb +121 -0
- data/lib/quack_concurrency/queue.rb +39 -39
- data/lib/quack_concurrency/reentrant_mutex.rb +92 -67
- data/lib/quack_concurrency/semaphore.rb +7 -7
- data/lib/quack_concurrency/uninterruptible_condition_variable.rb +79 -0
- data/lib/quack_concurrency/uninterruptible_sleeper.rb +73 -0
- data/lib/quack_concurrency/waiter.rb +42 -12
- data/lib/quack_concurrency/yielder.rb +35 -0
- data/lib/quack_concurrency.rb +8 -7
- data/spec/future_spec.rb +1 -34
- data/spec/queue_spec.rb +1 -34
- data/spec/reentrant_mutex_spec.rb +2 -33
- data/spec/semaphore_spec.rb +239 -272
- metadata +7 -4
- data/lib/quack_concurrency/concurrency_tool.rb +0 -28
- data/lib/quack_concurrency/name.rb +0 -33
@@ -2,113 +2,138 @@
|
|
2
2
|
|
3
3
|
|
4
4
|
module QuackConcurrency
|
5
|
-
class ReentrantMutex <
|
5
|
+
class ReentrantMutex < Mutex
|
6
6
|
|
7
7
|
# Creates a new {ReentrantMutex} concurrency tool.
|
8
|
-
# @param duck_types [Hash] hash of core Ruby classes to overload.
|
9
|
-
# If a +Hash+ is given, the keys +:condition_variable+ and +:mutex+ must be present.
|
10
8
|
# @return [ReentrantMutex]
|
11
|
-
def initialize
|
12
|
-
|
13
|
-
@condition_variable = classes[:condition_variable].new
|
14
|
-
@mutex = classes[:mutex].new
|
15
|
-
@owner = nil
|
9
|
+
def initialize
|
10
|
+
super
|
16
11
|
@lock_depth = 0
|
17
12
|
end
|
18
13
|
|
19
|
-
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
14
|
+
#@overload lock
|
15
|
+
# Obtains the lock or sleeps the current `Thread` until it is available.
|
16
|
+
# @return [void]
|
17
|
+
#@overload lock(&block)
|
18
|
+
# Obtains the lock, runs the block, then releases the lock when the block completes.
|
19
|
+
# @yield block to run with the lock
|
20
|
+
# @return [Object] result of the block
|
21
|
+
def lock(&block)
|
22
|
+
if block_given?
|
23
|
+
lock
|
24
|
+
start_depth = @lock_depth
|
25
|
+
start_owner = owner
|
26
|
+
begin
|
27
|
+
yield
|
28
|
+
ensure
|
29
|
+
unless @lock_depth == start_depth && owner == start_owner
|
30
|
+
raise Error, 'could not unlock reentrant mutex as its state has been modified'
|
31
|
+
end
|
32
|
+
unlock
|
33
|
+
end
|
34
|
+
else
|
35
|
+
super unless owned?
|
26
36
|
@lock_depth += 1
|
37
|
+
nil
|
27
38
|
end
|
28
|
-
nil
|
29
|
-
end
|
30
|
-
|
31
|
-
# Checks if this {ReentrantMutex} is locked by some thread.
|
32
|
-
# @return [Boolean]
|
33
|
-
def locked?
|
34
|
-
!!@owner
|
35
39
|
end
|
36
40
|
|
37
|
-
# Checks if this {ReentrantMutex} is locked by a
|
41
|
+
# Checks if this {ReentrantMutex} is locked by a Thread other than the caller.
|
38
42
|
# @return [Boolean]
|
39
43
|
def locked_out?
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
# Checks if this {ReentrantMutex} is locked by the calling thread.
|
44
|
-
# @return [Boolean]
|
45
|
-
def owned?
|
46
|
-
@owner == caller
|
44
|
+
# don't need a mutex because we know #owned? can't change during the call
|
45
|
+
locked? && !owned?
|
47
46
|
end
|
48
47
|
|
49
48
|
# Releases the lock and sleeps.
|
50
|
-
# When the calling
|
51
|
-
# @param timeout [Integer] seconds to sleep,
|
52
|
-
# @raise [Error] if this {ReentrantMutex} wasn't locked by the calling
|
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
|
53
52
|
# @return [void]
|
54
53
|
def sleep(timeout = nil)
|
55
|
-
unlock
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
@mutex.sleep(timeout)
|
54
|
+
raise Error, 'can not unlock reentrant mutex, it is not locked' unless locked?
|
55
|
+
raise Error, 'can not unlock reentrant mutex, caller is not the owner' unless owned?
|
56
|
+
base_depth do
|
57
|
+
super(timeout)
|
60
58
|
end
|
61
|
-
nil
|
62
|
-
ensure
|
63
|
-
lock unless owned?
|
64
59
|
end
|
65
60
|
|
66
61
|
# Obtains a lock, runs the block, and releases the lock when the block completes.
|
67
|
-
# @return
|
68
|
-
def synchronize
|
69
|
-
lock
|
70
|
-
start_depth = @lock_depth
|
71
|
-
start_owner = @owner
|
72
|
-
result = yield
|
73
|
-
result
|
74
|
-
ensure
|
75
|
-
unless @lock_depth == start_depth && @owner == start_owner
|
76
|
-
raise Error, 'could not unlock reentrant mutex as its state has been modified'
|
77
|
-
end
|
78
|
-
unlock
|
62
|
+
# @return [Object] value from yielded block
|
63
|
+
def synchronize(&block)
|
64
|
+
lock(&block)
|
79
65
|
end
|
80
66
|
|
67
|
+
alias parent_try_lock try_lock
|
68
|
+
private :parent_try_lock
|
81
69
|
# Attempts to obtain the lock and returns immediately.
|
82
70
|
# @return [Boolean] returns if the lock was granted
|
83
71
|
def try_lock
|
84
|
-
|
85
|
-
return false if @owner && @owner != caller
|
86
|
-
@owner = caller
|
72
|
+
if owned?
|
87
73
|
@lock_depth += 1
|
88
74
|
true
|
75
|
+
else
|
76
|
+
lock_successful = parent_try_lock
|
77
|
+
if lock_successful
|
78
|
+
@lock_depth += 1
|
79
|
+
true
|
80
|
+
else
|
81
|
+
false
|
82
|
+
end
|
89
83
|
end
|
90
84
|
end
|
91
85
|
|
92
86
|
# Releases the lock.
|
93
|
-
# @raise [Error] if {ReentrantMutex} wasn't locked by the calling
|
87
|
+
# @raise [Error] if {ReentrantMutex} wasn't locked by the calling Thread
|
94
88
|
# @return [void]
|
95
|
-
def unlock
|
96
|
-
|
97
|
-
|
98
|
-
|
89
|
+
def unlock(&block)
|
90
|
+
raise Error, 'can not unlock reentrant mutex, it is not locked' unless locked?
|
91
|
+
raise Error, 'can not unlock reentrant mutex, caller is not the owner' unless owned?
|
92
|
+
if block_given?
|
93
|
+
unlock
|
94
|
+
begin
|
95
|
+
yield
|
96
|
+
ensure
|
97
|
+
lock
|
98
|
+
end
|
99
|
+
else
|
99
100
|
@lock_depth -= 1
|
100
|
-
if @lock_depth == 0
|
101
|
-
|
102
|
-
|
101
|
+
super if @lock_depth == 0
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Releases the lock.
|
107
|
+
# @raise [Error] if {ReentrantMutex} wasn't locked by the calling Thread
|
108
|
+
# @return [void]
|
109
|
+
def unlock!(&block)
|
110
|
+
raise Error, 'can not unlock reentrant mutex, it is not locked' unless locked?
|
111
|
+
raise Error, 'can not unlock reentrant mutex, caller is not the owner' unless owned?
|
112
|
+
if block_given?
|
113
|
+
base_depth do
|
114
|
+
unlock
|
115
|
+
begin
|
116
|
+
yield
|
117
|
+
ensure
|
118
|
+
lock
|
119
|
+
end
|
103
120
|
end
|
121
|
+
else
|
122
|
+
@lock_depth = 0
|
123
|
+
super
|
124
|
+
nil
|
104
125
|
end
|
105
|
-
nil
|
106
126
|
end
|
107
127
|
|
108
128
|
private
|
109
129
|
|
110
|
-
|
111
|
-
|
130
|
+
# @api private
|
131
|
+
def base_depth(&block)
|
132
|
+
start_depth = @lock_depth
|
133
|
+
@lock_depth = 1
|
134
|
+
yield
|
135
|
+
ensure
|
136
|
+
@lock_depth = start_depth
|
112
137
|
end
|
113
138
|
|
114
139
|
end
|
@@ -1,21 +1,21 @@
|
|
1
|
+
# not ready yet
|
2
|
+
|
1
3
|
module QuackConcurrency
|
2
|
-
class Semaphore
|
4
|
+
class Semaphore
|
3
5
|
|
4
6
|
# Gets total permit count
|
5
7
|
# @return [Integer]
|
6
8
|
attr_reader :permit_count
|
7
9
|
|
8
10
|
# Creates a new {Semaphore} concurrency tool.
|
9
|
-
# @param duck_types [Hash] hash of core Ruby classes to overload.
|
10
|
-
# If a +Hash+ is given, the keys +:condition_variable+ and +:mutex+ must be present.
|
11
11
|
# @return [Semaphore]
|
12
|
-
def initialize(permit_count = 1
|
13
|
-
|
14
|
-
@condition_variable =
|
12
|
+
def initialize(permit_count = 1)
|
13
|
+
raise 'not ready yet'
|
14
|
+
@condition_variable = UninterruptibleConditionVariable.new
|
15
15
|
verify_permit_count(permit_count)
|
16
16
|
@permit_count = permit_count
|
17
17
|
@permits_used = 0
|
18
|
-
@mutex = ReentrantMutex.new
|
18
|
+
@mutex = ::ReentrantMutex.new
|
19
19
|
end
|
20
20
|
|
21
21
|
# Check if a permit is available to be released.
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module QuackConcurrency
|
2
|
+
class UninterruptibleConditionVariable
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@waiting_threads_sleepers = []
|
6
|
+
@mutex = ::Mutex.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def any_waiting_threads?
|
10
|
+
waiting_threads_count >= 1
|
11
|
+
end
|
12
|
+
|
13
|
+
def broadcast
|
14
|
+
@mutex.synchronize do
|
15
|
+
signal_next until @waiting_threads_sleepers.empty?
|
16
|
+
end
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def signal
|
21
|
+
@mutex.synchronize do
|
22
|
+
signal_next if @waiting_threads_sleepers.any?
|
23
|
+
end
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def wait(mutex = nil, timeout = nil)
|
28
|
+
validate_mutex(mutex)
|
29
|
+
if timeout != nil && !timeout.is_a?(Numeric)
|
30
|
+
raise ArgumentError, "'timeout' argument must be nil or a Numeric"
|
31
|
+
end
|
32
|
+
sleeper = UninterruptibleSleeper.for_current
|
33
|
+
@mutex.synchronize { @waiting_threads_sleepers.push(sleeper) }
|
34
|
+
if mutex
|
35
|
+
if mutex.respond_to?(:unlock!)
|
36
|
+
mutex.unlock! { sleep(sleeper, timeout) }
|
37
|
+
else
|
38
|
+
mutex.unlock
|
39
|
+
sleep(sleeper, timeout)
|
40
|
+
mutex.lock
|
41
|
+
end
|
42
|
+
else
|
43
|
+
sleep(sleeper, timeout)
|
44
|
+
end
|
45
|
+
@mutex.synchronize { @waiting_threads_sleepers.delete(sleeper) }
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def waiting_threads_count
|
50
|
+
@waiting_threads_sleepers.length
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
def signal_next
|
57
|
+
next_waiting_thread_sleeper = @waiting_threads_sleepers.shift
|
58
|
+
next_waiting_thread_sleeper.run_thread if next_waiting_thread_sleeper
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# @api private
|
63
|
+
def sleep(sleeper, duration)
|
64
|
+
if duration == nil || duration == Float::INFINITY
|
65
|
+
sleeper.stop_thread
|
66
|
+
else
|
67
|
+
sleeper.sleep_thread(timeout)
|
68
|
+
end
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def validate_mutex(mutex)
|
73
|
+
return if mutex == nil
|
74
|
+
return if mutex.respond_to?(:lock) && (mutex.respond_to?(:unlock) || mutex.respond_to?(:unlock!))
|
75
|
+
raise ArgumentError, "'mutex' must respond to 'lock' and ('unlock' or'unlock!')"
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module QuackConcurrency
|
2
|
+
class UninterruptibleSleeper
|
3
|
+
|
4
|
+
def self.for_current
|
5
|
+
new(Thread.current)
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(thread)
|
9
|
+
raise ArgumentError, "'thread' must be a Thread" unless thread.is_a?(Thread)
|
10
|
+
@thread = thread
|
11
|
+
@state = :running
|
12
|
+
@mutex = ::Mutex.new
|
13
|
+
@stop_called = false
|
14
|
+
@run_called = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_thread
|
18
|
+
@mutex.synchronize do
|
19
|
+
raise '#run_thread has already been called once' if @run_called
|
20
|
+
@run_called = true
|
21
|
+
return if @state == :running
|
22
|
+
Thread.pass until @state = :running || @thread.status == 'sleep'
|
23
|
+
@state = :running
|
24
|
+
@thread.run
|
25
|
+
end
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def sleep_thread(duration)
|
30
|
+
start_time = Time.now
|
31
|
+
stop_thread(timeout: duration)
|
32
|
+
time_elapsed = Time.now - start_time
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop_thread(timeout: nil)
|
36
|
+
raise 'can only stop current Thread' unless Thread.current == @thread
|
37
|
+
raise "'timeout' argument must be nil or a Numeric" if timeout != nil && !timeout.is_a?(Numeric)
|
38
|
+
raise '#stop_thread has already been called once' if @stop_called
|
39
|
+
@stop_called = true
|
40
|
+
target_end_time = Time.now + timeout if timeout
|
41
|
+
@mutex.synchronize do
|
42
|
+
return if @run_called
|
43
|
+
@state = :sleeping
|
44
|
+
@mutex.unlock
|
45
|
+
loop do
|
46
|
+
if timeout
|
47
|
+
time_left = target_end_time - Time.now
|
48
|
+
Kernel.sleep(time_left) if time_left > 0
|
49
|
+
else
|
50
|
+
Thread.stop
|
51
|
+
end
|
52
|
+
break if @state == :running || Time.now >= target_time
|
53
|
+
end
|
54
|
+
@state = :running
|
55
|
+
|
56
|
+
# we relock the mutex ensure #run_thread has finshed before #stop_thread
|
57
|
+
# if Thread#run is called by another part of the code at the same time as
|
58
|
+
# #run_thread is being called, we dont want the call to #run_thread
|
59
|
+
# to call Thread#run on a Thread has already resumed and stopped again
|
60
|
+
@mutex.lock
|
61
|
+
end
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# @api private
|
68
|
+
def current?
|
69
|
+
Thread.current == @thread
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -1,27 +1,57 @@
|
|
1
1
|
module QuackConcurrency
|
2
|
-
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
class Waiter
|
3
5
|
|
4
6
|
# Creates a new {Waiter} concurrency tool.
|
5
|
-
# @param duck_types [Hash] hash of core Ruby classes to overload.
|
6
|
-
# If a +Hash+ is given, the keys +:condition_variable+ and +:mutex+ must be present.
|
7
7
|
# @return [Waiter]
|
8
|
-
def initialize
|
9
|
-
@
|
8
|
+
def initialize
|
9
|
+
@condition_variable = UninterruptibleConditionVariable.new
|
10
|
+
@resume_all_forever = false
|
11
|
+
@mutex = ::Mutex.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def any_waiting_threads?
|
15
|
+
@condition_variable.any_waiting_threads?
|
16
|
+
end
|
17
|
+
|
18
|
+
# Resumes all current and future waiting Thread.
|
19
|
+
# @return [void]
|
20
|
+
def resume_all
|
21
|
+
@condition_variable.broadcast
|
22
|
+
nil
|
10
23
|
end
|
11
24
|
|
12
|
-
# Resumes
|
13
|
-
# @param value value to pass to waiting thread
|
25
|
+
# Resumes all current and future waiting Thread.
|
14
26
|
# @return [void]
|
15
|
-
def
|
16
|
-
@
|
27
|
+
def resume_all_forever
|
28
|
+
@mutex.synchronize do
|
29
|
+
@resume_all_forever = true
|
30
|
+
resume_all
|
31
|
+
end
|
17
32
|
nil
|
18
33
|
end
|
19
34
|
|
20
|
-
#
|
35
|
+
# Resumes next waiting Thread if one exists.
|
36
|
+
# @return [void]
|
37
|
+
def resume_one
|
38
|
+
@condition_variable.signal
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# Waits for another Thread to resume the calling Thread.
|
21
43
|
# @note Will block until resumed.
|
22
|
-
# @return
|
44
|
+
# @return [void]
|
23
45
|
def wait
|
24
|
-
@
|
46
|
+
@mutex.synchronize do
|
47
|
+
return if @resume_all_forever
|
48
|
+
@condition_variable.wait(@mutex)
|
49
|
+
end
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def waiting_threads_count
|
54
|
+
@condition_variable.waiting_threads_count
|
25
55
|
end
|
26
56
|
|
27
57
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# not ready yet
|
2
|
+
|
3
|
+
module Threadesque
|
4
|
+
class SafeYielder
|
5
|
+
|
6
|
+
def self.for_current
|
7
|
+
new(Thread.current)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(thread)
|
11
|
+
raise 'not ready yet'
|
12
|
+
@thread = thread
|
13
|
+
@state = :running
|
14
|
+
end
|
15
|
+
|
16
|
+
def resume
|
17
|
+
raise 'Thread is not sleeping' unless @state == :sleeping
|
18
|
+
:wait until @thread.status == 'sleep'
|
19
|
+
@state = :running
|
20
|
+
@thread.run
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def yield
|
25
|
+
raise 'can only stop current Thread' unless Thread.current == @thread
|
26
|
+
@state = :sleeping
|
27
|
+
loop do
|
28
|
+
Thread.stop
|
29
|
+
redo if @state == :sleeping
|
30
|
+
end
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/lib/quack_concurrency.rb
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'reentrant_mutex'
|
2
3
|
|
3
|
-
require 'quack_concurrency/
|
4
|
+
require 'quack_concurrency/condition_variable'
|
4
5
|
require 'quack_concurrency/error'
|
5
6
|
require 'quack_concurrency/future'
|
6
|
-
require 'quack_concurrency/name'
|
7
|
-
require 'quack_concurrency/queue'
|
8
|
-
require 'quack_concurrency/reentrant_mutex'
|
9
|
-
require 'quack_concurrency/semaphore'
|
10
|
-
require 'quack_concurrency/waiter'
|
11
7
|
require 'quack_concurrency/future/canceled'
|
12
8
|
require 'quack_concurrency/future/complete'
|
9
|
+
require 'quack_concurrency/mutex'
|
10
|
+
require 'quack_concurrency/queue'
|
13
11
|
require 'quack_concurrency/queue/error'
|
12
|
+
require 'quack_concurrency/reentrant_mutex'
|
14
13
|
require 'quack_concurrency/reentrant_mutex/error'
|
15
|
-
require 'quack_concurrency/
|
14
|
+
require 'quack_concurrency/uninterruptible_condition_variable'
|
15
|
+
require 'quack_concurrency/uninterruptible_sleeper'
|
16
|
+
require 'quack_concurrency/waiter'
|
16
17
|
|
17
18
|
|
18
19
|
# if you pass a duck type Hash to any of the concurrency tools it will force you to
|
data/spec/future_spec.rb
CHANGED
@@ -2,39 +2,6 @@ require 'quack_concurrency'
|
|
2
2
|
|
3
3
|
RSpec.describe QuackConcurrency::Future do
|
4
4
|
|
5
|
-
describe "::new" do
|
6
|
-
|
7
|
-
context "when called without a 'duck_types' argument" do
|
8
|
-
it "should create a new QuackConcurrency::Future" do
|
9
|
-
future = QuackConcurrency::Future.new
|
10
|
-
expect(future).to be_a(QuackConcurrency::Future)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
context "when called with 'condition_variable' and 'mutex' duck types" do
|
15
|
-
it "should create a new QuackConcurrency::Future" do
|
16
|
-
duck_types = {condition_variable: Class.new, mutex: Class.new}
|
17
|
-
future = QuackConcurrency::Future.new(duck_types: duck_types)
|
18
|
-
expect(future).to be_a(QuackConcurrency::Future)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
context "when called with only 'condition_variable' duck type" do
|
23
|
-
it "should raise ArgumentError" do
|
24
|
-
duck_types = {condition_variable: Class.new}
|
25
|
-
expect{ QuackConcurrency::Future.new(duck_types: duck_types) }.to raise_error(ArgumentError)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
context "when called with only 'mutex' duck type" do
|
30
|
-
it "should raise ArgumentError" do
|
31
|
-
duck_types = {mutex: Class.new}
|
32
|
-
expect{ QuackConcurrency::Future.new(duck_types: duck_types) }.to raise_error(ArgumentError)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
5
|
describe "#set" do
|
39
6
|
|
40
7
|
context "when called" do
|
@@ -97,7 +64,7 @@ RSpec.describe QuackConcurrency::Future do
|
|
97
64
|
future = QuackConcurrency::Future.new
|
98
65
|
thread = Thread.new do
|
99
66
|
sleep 1
|
100
|
-
future.set
|
67
|
+
future.set(1)
|
101
68
|
end
|
102
69
|
start_time = Time.now
|
103
70
|
expect(future.get).to eql 1
|
data/spec/queue_spec.rb
CHANGED
@@ -2,39 +2,6 @@ require 'quack_concurrency'
|
|
2
2
|
|
3
3
|
RSpec.describe QuackConcurrency::Queue do
|
4
4
|
|
5
|
-
describe "::new" do
|
6
|
-
|
7
|
-
context "when called without a 'duck_types' argument" do
|
8
|
-
it "should create a new QuackConcurrency::Queue" do
|
9
|
-
queue = QuackConcurrency::Queue.new
|
10
|
-
expect(queue).to be_a(QuackConcurrency::Queue)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
context "when called with 'condition_variable' and 'mutex' duck types" do
|
15
|
-
it "should create a new QuackConcurrency::Queue" do
|
16
|
-
duck_types = {condition_variable: Class.new, mutex: Class.new}
|
17
|
-
queue = QuackConcurrency::Queue.new(duck_types: duck_types)
|
18
|
-
expect(queue).to be_a(QuackConcurrency::Queue)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
context "when called with only 'condition_variable' duck type" do
|
23
|
-
it "should raise ArgumentError" do
|
24
|
-
duck_types = {condition_variable: Class.new}
|
25
|
-
expect{ QuackConcurrency::Queue.new(duck_types: duck_types) }.to raise_error(ArgumentError)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
context "when called with only 'mutex' duck type" do
|
30
|
-
it "should raise ArgumentError" do
|
31
|
-
duck_types = {mutex: Class.new}
|
32
|
-
expect{ QuackConcurrency::Queue.new(duck_types: duck_types) }.to raise_error(ArgumentError)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
5
|
describe "#push" do
|
39
6
|
|
40
7
|
context "when called many times when queue is not closed" do
|
@@ -52,7 +19,7 @@ RSpec.describe QuackConcurrency::Queue do
|
|
52
19
|
context "when #pop is called with non_block set to true" do
|
53
20
|
it "should raise Error" do
|
54
21
|
queue = QuackConcurrency::Queue.new
|
55
|
-
expect{ queue.pop(true) }.to raise_error(
|
22
|
+
expect{ queue.pop(true) }.to raise_error(ThreadError)
|
56
23
|
end
|
57
24
|
end
|
58
25
|
|
@@ -2,39 +2,6 @@ require 'quack_concurrency'
|
|
2
2
|
|
3
3
|
RSpec.describe QuackConcurrency::ReentrantMutex do
|
4
4
|
|
5
|
-
describe "::new" do
|
6
|
-
|
7
|
-
context "when called without a 'duck_types' argument" do
|
8
|
-
it "should create a new QuackConcurrency::ReentrantMutex" do
|
9
|
-
mutex = QuackConcurrency::ReentrantMutex.new
|
10
|
-
expect(mutex).to be_a(QuackConcurrency::ReentrantMutex)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
context "when called with 'condition_variable' and 'mutex' duck types" do
|
15
|
-
it "should create a new QuackConcurrency::ReentrantMutex" do
|
16
|
-
duck_types = {condition_variable: Class.new, mutex: Class.new}
|
17
|
-
mutex = QuackConcurrency::ReentrantMutex.new(duck_types: duck_types)
|
18
|
-
expect(mutex).to be_a(QuackConcurrency::ReentrantMutex)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
context "when called with only 'condition_variable' duck type" do
|
23
|
-
it "should raise ArgumentError" do
|
24
|
-
duck_types = {condition_variable: Class.new}
|
25
|
-
expect{ QuackConcurrency::ReentrantMutex.new(duck_types: duck_types) }.to raise_error(ArgumentError)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
context "when called with only 'mutex' duck type" do
|
30
|
-
it "should raise ArgumentError" do
|
31
|
-
duck_types = {mutex: Class.new}
|
32
|
-
expect{ QuackConcurrency::ReentrantMutex.new(duck_types: duck_types) }.to raise_error(ArgumentError)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
5
|
describe "#lock" do
|
39
6
|
|
40
7
|
context "when called for first time" do
|
@@ -164,6 +131,8 @@ RSpec.describe QuackConcurrency::ReentrantMutex do
|
|
164
131
|
it "should wait for that time" do
|
165
132
|
mutex = QuackConcurrency::ReentrantMutex.new
|
166
133
|
start_time = Time.now
|
134
|
+
#require 'pry'
|
135
|
+
#binding.pry
|
167
136
|
mutex.synchronize do
|
168
137
|
mutex.sleep(1)
|
169
138
|
end
|