quack_concurrency 0.4.1 → 0.5.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/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
|