quack_concurrency 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/quack_concurrency/concurrency_tool.rb +26 -0
- data/lib/quack_concurrency/error.rb +5 -0
- data/lib/quack_concurrency/future/canceled.rb +6 -0
- data/lib/quack_concurrency/future/complete.rb +6 -0
- data/lib/quack_concurrency/future.rb +11 -12
- data/lib/quack_concurrency/name.rb +31 -0
- data/lib/quack_concurrency/queue.rb +5 -6
- data/lib/quack_concurrency/reentrant_mutex/error.rb +6 -0
- data/lib/quack_concurrency/reentrant_mutex.rb +21 -23
- data/lib/quack_concurrency/semaphore/error.rb +6 -0
- data/lib/quack_concurrency/semaphore.rb +55 -31
- data/lib/quack_concurrency/waiter.rb +2 -2
- data/lib/quack_concurrency.rb +16 -0
- data/spec/future_spec.rb +145 -0
- data/spec/queue_spec.rb +122 -21
- data/spec/reentrant_mutex_spec.rb +146 -19
- data/spec/semaphore_spec.rb +261 -11
- metadata +14 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 164e219c34134cf836d5a4169272a1f6185d832790ae69c29ad4dd4496a2af0f
|
4
|
+
data.tar.gz: e4f01597b27e39da0e1d916228abe30764b5b8b30a3d68d90fabea53635a5d10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94ffaae9048528509d569619625ce1288ac16de3015f70fd92f08f52309ee3c15f91b07f1d1eb5bb5fb3b4ab6edd5022cd919c84f9a98672b04adb2e91d40686
|
7
|
+
data.tar.gz: 26da3552b5b6b17d4f60f0427f7fa9a1b081edeab9df71cd7760c236e5edb69bad1a491d3185aa91e79329a688279ac21062fc0882566f927f1cb9b35850dc6e
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module QuackConcurrency
|
2
|
+
class ConcurrencyTool
|
3
|
+
|
4
|
+
def setup_duck_types(supplied_classes)
|
5
|
+
resultant_classes = {}
|
6
|
+
required_classes = [:condition_variable, :mutex]
|
7
|
+
required_classes = required_classes.map { |name| Name.new(name.to_s) }
|
8
|
+
if supplied_classes
|
9
|
+
raise ArgumentError, "'supplied_classes' must be Hash" unless supplied_classes.is_a?(Hash)
|
10
|
+
supplied_classes = supplied_classes.map { |k, v| [Name.new(k.to_s), v] }.to_h
|
11
|
+
required_classes.each do |class_name|
|
12
|
+
unless supplied_classes[class_name]
|
13
|
+
raise ArgumentError, "missing duck type: #{class_name.camel_case(:upper)}"
|
14
|
+
end
|
15
|
+
resultant_classes[class_name.snake_case.to_sym] = supplied_classes[class_name]
|
16
|
+
end
|
17
|
+
else
|
18
|
+
required_classes.each do |class_name|
|
19
|
+
resultant_classes[class_name.snake_case.to_sym] = Object.const_get(class_name.camel_case(:upper))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
resultant_classes
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -1,14 +1,10 @@
|
|
1
1
|
module QuackConcurrency
|
2
|
-
class Future
|
3
|
-
|
4
|
-
class Canceled < StandardError
|
5
|
-
end
|
2
|
+
class Future < ConcurrencyTool
|
6
3
|
|
7
|
-
def initialize(duck_types:
|
8
|
-
|
9
|
-
|
10
|
-
@
|
11
|
-
@mutex = mutex_class.new
|
4
|
+
def initialize(duck_types: nil)
|
5
|
+
classes = setup_duck_types(duck_types)
|
6
|
+
@condition_variable = classes[:condition_variable].new
|
7
|
+
@mutex = classes[:mutex].new
|
12
8
|
@value = nil
|
13
9
|
@value_set = false
|
14
10
|
@complete = false
|
@@ -16,18 +12,19 @@ module QuackConcurrency
|
|
16
12
|
|
17
13
|
def set(new_value = nil)
|
18
14
|
@mutex.synchronize do
|
19
|
-
raise if @complete
|
15
|
+
raise Complete if @complete
|
20
16
|
@value_set = true
|
21
17
|
@complete = true
|
22
18
|
@value = new_value
|
23
19
|
@condition_variable.broadcast
|
24
20
|
end
|
21
|
+
nil
|
25
22
|
end
|
26
23
|
|
27
24
|
def get
|
28
25
|
@mutex.synchronize do
|
29
26
|
@condition_variable.wait(@mutex) unless complete?
|
30
|
-
raise '
|
27
|
+
raise 'internal error, invalid state' unless complete?
|
31
28
|
raise Canceled unless @value_set
|
32
29
|
@value
|
33
30
|
end
|
@@ -35,9 +32,11 @@ module QuackConcurrency
|
|
35
32
|
|
36
33
|
def cancel
|
37
34
|
@mutex.synchronize do
|
38
|
-
raise if @complete
|
35
|
+
raise Complete if @complete
|
39
36
|
@complete = true
|
37
|
+
@condition_variable.broadcast
|
40
38
|
end
|
39
|
+
nil
|
41
40
|
end
|
42
41
|
|
43
42
|
def complete?
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module QuackConcurrency
|
2
|
+
class Name < String
|
3
|
+
|
4
|
+
def initialize(*args)
|
5
|
+
super
|
6
|
+
# set all names to a default case
|
7
|
+
# if we create two of the same name with different cases they will now be equal
|
8
|
+
replace(snake_case)
|
9
|
+
end
|
10
|
+
|
11
|
+
def camel_case(first_letter = :upper)
|
12
|
+
case first_letter
|
13
|
+
when :upper
|
14
|
+
self.split('_').collect(&:capitalize).join
|
15
|
+
when :lower
|
16
|
+
self.camelcase(:upper)[0].downcase + self.camelcase(:upper)[1..-1]
|
17
|
+
else
|
18
|
+
raise ArgumentError, 'invalid option, use either :upper or :lower'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def snake_case
|
23
|
+
self.gsub(/::/, '/').
|
24
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
25
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
26
|
+
tr("- ", "_").
|
27
|
+
downcase
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -1,11 +1,10 @@
|
|
1
1
|
module QuackConcurrency
|
2
|
-
class Queue
|
2
|
+
class Queue < ConcurrencyTool
|
3
3
|
|
4
|
-
def initialize(duck_types:
|
5
|
-
|
6
|
-
|
7
|
-
@
|
8
|
-
@mutex = mutex_class.new
|
4
|
+
def initialize(duck_types: nil)
|
5
|
+
classes = setup_duck_types(duck_types)
|
6
|
+
@condition_variable = classes[:condition_variable].new
|
7
|
+
@mutex = classes[:mutex].new
|
9
8
|
@queue = []
|
10
9
|
@waiting_count = 0
|
11
10
|
@closed = false
|
@@ -1,14 +1,12 @@
|
|
1
1
|
# based off https://en.wikipedia.org/wiki/Reentrant_mutex
|
2
2
|
|
3
3
|
module QuackConcurrency
|
4
|
-
class ReentrantMutex
|
4
|
+
class ReentrantMutex < ConcurrencyTool
|
5
5
|
|
6
|
-
def initialize(duck_types:
|
7
|
-
|
8
|
-
@
|
9
|
-
|
10
|
-
@condition_variable = condition_variable_class.new
|
11
|
-
@mutex = mutex_class.new
|
6
|
+
def initialize(duck_types: nil)
|
7
|
+
classes = setup_duck_types(duck_types)
|
8
|
+
@condition_variable = classes[:condition_variable].new
|
9
|
+
@mutex = classes[:mutex].new
|
12
10
|
@owner = nil
|
13
11
|
@lock_depth = 0
|
14
12
|
end
|
@@ -16,6 +14,7 @@ module QuackConcurrency
|
|
16
14
|
def lock
|
17
15
|
@mutex.synchronize do
|
18
16
|
@condition_variable.wait(@mutex) if @owner && @owner != caller
|
17
|
+
raise 'internal error, invalid state' if @owner && @owner != caller
|
19
18
|
@owner = caller
|
20
19
|
@lock_depth += 1
|
21
20
|
end
|
@@ -34,14 +33,16 @@ module QuackConcurrency
|
|
34
33
|
@owner == caller
|
35
34
|
end
|
36
35
|
|
37
|
-
def sleep(
|
36
|
+
def sleep(*args)
|
38
37
|
unlock
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
@
|
38
|
+
# i would rather not need to get a ducktype for sleep so we will just take
|
39
|
+
# advantage of Mutex's sleep method that must take it into account already
|
40
|
+
@mutex.synchronize do
|
41
|
+
@mutex.sleep(*args)
|
43
42
|
end
|
44
|
-
|
43
|
+
nil
|
44
|
+
ensure
|
45
|
+
lock unless owned?
|
45
46
|
end
|
46
47
|
|
47
48
|
def synchronize
|
@@ -52,27 +53,24 @@ module QuackConcurrency
|
|
52
53
|
result
|
53
54
|
ensure
|
54
55
|
unless @lock_depth == start_depth && @owner == start_owner
|
55
|
-
raise 'could not unlock mutex as its state has been modified'
|
56
|
+
raise Error, 'could not unlock reentrant mutex as its state has been modified'
|
56
57
|
end
|
57
58
|
unlock
|
58
59
|
end
|
59
60
|
|
60
61
|
def try_lock
|
61
62
|
@mutex.synchronize do
|
62
|
-
if @owner && @owner != caller
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
@lock_depth += 1
|
67
|
-
end
|
63
|
+
return false if @owner && @owner != caller
|
64
|
+
@owner = caller
|
65
|
+
@lock_depth += 1
|
66
|
+
true
|
68
67
|
end
|
69
|
-
true
|
70
68
|
end
|
71
69
|
|
72
70
|
def unlock
|
73
71
|
@mutex.synchronize do
|
74
|
-
raise
|
75
|
-
raise
|
72
|
+
raise Error, 'can not unlock reentrant mutex, it is not locked' if @lock_depth == 0
|
73
|
+
raise Error, 'can not unlock reentrant mutex, caller is not the owner' unless @owner == caller
|
76
74
|
@lock_depth -= 1
|
77
75
|
if @lock_depth == 0
|
78
76
|
@owner = nil
|
@@ -1,30 +1,52 @@
|
|
1
1
|
module QuackConcurrency
|
2
|
-
class Semaphore
|
2
|
+
class Semaphore < ConcurrencyTool
|
3
3
|
|
4
4
|
attr_reader :permit_count
|
5
5
|
|
6
|
-
def initialize(permit_count = 1, duck_types:
|
7
|
-
|
8
|
-
|
6
|
+
def initialize(permit_count = 1, duck_types: nil)
|
7
|
+
classes = setup_duck_types(duck_types)
|
8
|
+
@condition_variable = classes[:condition_variable].new
|
9
|
+
verify_permit_count(permit_count)
|
9
10
|
@permit_count = permit_count
|
10
11
|
@permits_used = 0
|
11
|
-
@condition_variable = condition_variable_class.new
|
12
12
|
@mutex = ReentrantMutex.new(duck_types: duck_types)
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
15
|
+
def permit_available?
|
16
|
+
permits_available >= 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def permits_available
|
20
|
+
@mutex.synchronize do
|
21
|
+
raw_permits_available = @permit_count - @permits_used
|
22
|
+
raw_permits_available.positive? ? raw_permits_available : 0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def reacquire
|
27
|
+
@mutex.synchronize do
|
28
|
+
raise Error, 'can not reacquire a permit, no permits released right now' if @permits_used == 0
|
29
|
+
@permits_used -= 1
|
30
|
+
@condition_variable.signal if permit_available?
|
31
|
+
end
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def release
|
16
36
|
@mutex.synchronize do
|
17
37
|
@condition_variable.wait(@mutex) unless permit_available?
|
38
|
+
raise 'internal error, invalid state' unless permit_available?
|
18
39
|
@permits_used += 1
|
19
40
|
end
|
20
41
|
nil
|
21
42
|
end
|
22
43
|
|
23
44
|
def set_permit_count(new_permit_count)
|
45
|
+
verify_permit_count(new_permit_count)
|
24
46
|
@mutex.synchronize do
|
25
47
|
remove_permits = @permit_count - new_permit_count
|
26
48
|
if remove_permits.positive? && remove_permits > permits_available
|
27
|
-
raise 'can not
|
49
|
+
raise Error, 'can not set new permit count, not enough permits available to remove right now'
|
28
50
|
end
|
29
51
|
set_permit_count!(new_permit_count)
|
30
52
|
end
|
@@ -32,7 +54,7 @@ module QuackConcurrency
|
|
32
54
|
end
|
33
55
|
|
34
56
|
def set_permit_count!(new_permit_count)
|
35
|
-
|
57
|
+
verify_permit_count(new_permit_count)
|
36
58
|
@mutex.synchronize do
|
37
59
|
new_permits = new_permit_count - @permit_count
|
38
60
|
if new_permits.positive?
|
@@ -45,42 +67,44 @@ module QuackConcurrency
|
|
45
67
|
nil
|
46
68
|
end
|
47
69
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
70
|
+
def synchronize
|
71
|
+
release
|
72
|
+
begin
|
73
|
+
yield
|
74
|
+
ensure
|
75
|
+
reacquire
|
53
76
|
end
|
54
|
-
nil
|
55
77
|
end
|
56
78
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
def permits_available
|
68
|
-
@mutex.synchronize { @permit_count - @permits_used }
|
69
|
-
end
|
70
|
-
|
71
|
-
def permit_available?
|
72
|
-
@mutex.synchronize { permits_available >= 1 }
|
79
|
+
def try_release
|
80
|
+
@mutex.synchronize do
|
81
|
+
if permit_available?
|
82
|
+
release
|
83
|
+
true
|
84
|
+
else
|
85
|
+
false
|
86
|
+
end
|
87
|
+
end
|
73
88
|
end
|
74
89
|
|
75
90
|
private
|
76
91
|
|
77
92
|
def add_permit
|
78
93
|
@permit_count += 1
|
79
|
-
@condition_variable.signal
|
94
|
+
@condition_variable.signal if permit_available?
|
95
|
+
nil
|
80
96
|
end
|
81
97
|
|
82
98
|
def remove_permit!
|
83
99
|
@permit_count -= 1
|
100
|
+
raise 'internal error, invalid state' if @permit_count < 0
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def verify_permit_count(permit_count)
|
105
|
+
unless permit_count.is_a?(Integer) && permit_count >= 0
|
106
|
+
raise ArgumentError, "'permit_count' must be a non negative Integer"
|
107
|
+
end
|
84
108
|
end
|
85
109
|
|
86
110
|
end
|
data/lib/quack_concurrency.rb
CHANGED
@@ -1,11 +1,27 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
|
+
require 'quack_concurrency/concurrency_tool'
|
4
|
+
require 'quack_concurrency/error'
|
3
5
|
require 'quack_concurrency/future'
|
6
|
+
require 'quack_concurrency/name'
|
4
7
|
require 'quack_concurrency/queue'
|
5
8
|
require 'quack_concurrency/reentrant_mutex'
|
6
9
|
require 'quack_concurrency/semaphore'
|
7
10
|
require 'quack_concurrency/waiter'
|
11
|
+
require 'quack_concurrency/future/canceled'
|
12
|
+
require 'quack_concurrency/future/complete'
|
13
|
+
require 'quack_concurrency/reentrant_mutex/error'
|
14
|
+
require 'quack_concurrency/semaphore/error'
|
15
|
+
|
16
|
+
|
17
|
+
# if you pass a duck type Hash to any of the concurrency tools it will force you to
|
18
|
+
# supply all the required ducktypes, all or nothing, as it were
|
19
|
+
# this is to protect against forgetting to pass one of the duck types as this
|
20
|
+
# would be a hard bug to solve otherwise
|
8
21
|
|
9
22
|
|
10
23
|
module QuackConcurrency
|
24
|
+
|
25
|
+
ClosedQueueError = ::ClosedQueueError
|
26
|
+
|
11
27
|
end
|
data/spec/future_spec.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'quack_concurrency'
|
2
|
+
|
3
|
+
RSpec.describe QuackConcurrency::Future do
|
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
|
+
describe "#set" do
|
39
|
+
|
40
|
+
context "when called" do
|
41
|
+
it "should not raise error" do
|
42
|
+
future = QuackConcurrency::Future.new
|
43
|
+
expect{ future.set(1) }.not_to raise_error
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when called a second time" do
|
48
|
+
it "should raise QuackConcurrency::Future::Complete" do
|
49
|
+
future = QuackConcurrency::Future.new
|
50
|
+
future.set(1)
|
51
|
+
expect{ future.set(2) }.to raise_error(QuackConcurrency::Future::Complete)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#cancel" do
|
58
|
+
|
59
|
+
context "when called" do
|
60
|
+
it "should not raise error" do
|
61
|
+
future = QuackConcurrency::Future.new
|
62
|
+
expect{ future.cancel }.not_to raise_error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "when called a second time" do
|
67
|
+
it "should raise QuackConcurrency::Future::Complete" do
|
68
|
+
future = QuackConcurrency::Future.new
|
69
|
+
future.cancel
|
70
|
+
expect{ future.cancel }.to raise_error(QuackConcurrency::Future::Complete)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#set, #get" do
|
77
|
+
|
78
|
+
context "when #get called after #set" do
|
79
|
+
it "should return value from #set argument" do
|
80
|
+
future = QuackConcurrency::Future.new
|
81
|
+
future.set(1)
|
82
|
+
expect(future.get).to eql 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when #get called a second time" do
|
87
|
+
it "should return value from #set argument" do
|
88
|
+
future = QuackConcurrency::Future.new
|
89
|
+
future.set(1)
|
90
|
+
future.get
|
91
|
+
expect(future.get).to eql 1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "when #get called before #set" do
|
96
|
+
it "should wait and return value from #set argument after #set is called" do
|
97
|
+
future = QuackConcurrency::Future.new
|
98
|
+
thread = Thread.new do
|
99
|
+
sleep 1
|
100
|
+
future.set 1
|
101
|
+
end
|
102
|
+
start_time = Time.now
|
103
|
+
expect(future.get).to eql 1
|
104
|
+
end_time = Time.now
|
105
|
+
duration = end_time - start_time
|
106
|
+
thread.join
|
107
|
+
expect(duration).to be > 0.5
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "#set, #cancel" do
|
114
|
+
|
115
|
+
context "when #set called after #cancel" do
|
116
|
+
it "should raise QuackConcurrency::Future::Complete" do
|
117
|
+
future = QuackConcurrency::Future.new
|
118
|
+
future.cancel
|
119
|
+
expect{ future.set(1) }.to raise_error(QuackConcurrency::Future::Complete)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context "when #cancel called after #set" do
|
124
|
+
it "should raise QuackConcurrency::Future::Complete" do
|
125
|
+
future = QuackConcurrency::Future.new
|
126
|
+
future.set(1)
|
127
|
+
expect{ future.cancel }.to raise_error(QuackConcurrency::Future::Complete)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "#get, #cancel" do
|
134
|
+
|
135
|
+
context "when #get called after #cancel" do
|
136
|
+
it "should raise QuackConcurrency::Future::Canceled" do
|
137
|
+
future = QuackConcurrency::Future.new
|
138
|
+
future.cancel
|
139
|
+
expect{ future.get }.to raise_error(QuackConcurrency::Future::Canceled)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
data/spec/queue_spec.rb
CHANGED
@@ -2,50 +2,151 @@ require 'quack_concurrency'
|
|
2
2
|
|
3
3
|
RSpec.describe QuackConcurrency::Queue do
|
4
4
|
|
5
|
-
describe "
|
5
|
+
describe "::new" do
|
6
6
|
|
7
|
-
context "when called
|
8
|
-
it "should
|
9
|
-
|
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
|
+
describe "#push" do
|
39
|
+
|
40
|
+
context "when called many times when queue is not closed" do
|
41
|
+
it "should not raise error" do
|
42
|
+
queue = QuackConcurrency::Queue.new
|
43
|
+
expect{ queue.push(1) }.not_to raise_error
|
44
|
+
expect{ queue.push(2) }.not_to raise_error
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#close, #push" do
|
51
|
+
|
52
|
+
context "when called when queue is closed" do
|
53
|
+
it "should raise ClosedQueueError" do
|
54
|
+
queue = QuackConcurrency::Queue.new
|
55
|
+
queue.close
|
56
|
+
expect{ queue.push(1) }.to raise_error(ClosedQueueError)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#pop, #close" do
|
63
|
+
|
64
|
+
context "when #pop is called after #close on empty queue" do
|
65
|
+
it "should reutrn nil" do
|
66
|
+
queue = QuackConcurrency::Queue.new
|
67
|
+
queue.close
|
68
|
+
expect(queue.pop).to eql nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "#pop, #close" do
|
75
|
+
|
76
|
+
context "when #pop is called before #close on empty queue" do
|
77
|
+
it "should wait for #close then reutrn nil" do
|
10
78
|
queue = QuackConcurrency::Queue.new
|
11
79
|
thread = Thread.new do
|
12
|
-
queue.pop
|
13
|
-
$test << 1
|
14
80
|
sleep 1
|
15
|
-
queue.
|
81
|
+
queue.close
|
16
82
|
end
|
17
|
-
|
18
|
-
queue.
|
19
|
-
|
20
|
-
|
83
|
+
start_time = Time.now
|
84
|
+
expect(queue.pop).to eql nil
|
85
|
+
end_time = Time.now
|
86
|
+
duration = end_time - start_time
|
21
87
|
thread.join
|
22
|
-
expect(
|
88
|
+
expect(duration).to be > 0.5
|
23
89
|
end
|
24
90
|
end
|
25
91
|
|
26
|
-
|
27
|
-
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#pop, #push, #close" do
|
95
|
+
|
96
|
+
context "when #pop is called after #close on queue with one item" do
|
97
|
+
it "should reutrn item" do
|
98
|
+
queue = QuackConcurrency::Queue.new
|
99
|
+
queue.push(1)
|
100
|
+
queue.close
|
101
|
+
expect(queue.pop).to eql 1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#pop, #push" do
|
108
|
+
|
109
|
+
context "when #pop is called before #push" do
|
110
|
+
it "should wait until #push is called" do
|
28
111
|
$test = []
|
29
112
|
queue = QuackConcurrency::Queue.new
|
30
|
-
queue.push
|
31
113
|
thread = Thread.new do
|
32
114
|
queue.pop
|
33
115
|
$test << 1
|
116
|
+
sleep 1
|
117
|
+
queue.push
|
34
118
|
end
|
35
119
|
sleep 1
|
120
|
+
queue.push
|
121
|
+
queue.pop
|
36
122
|
$test << 2
|
37
123
|
thread.join
|
38
124
|
expect($test).to eql [1, 2]
|
39
125
|
end
|
40
126
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "#pop, #push, #clear" do
|
131
|
+
|
132
|
+
context "when #pop is called after #push but before #clear" do
|
133
|
+
it "should wait until #push is called again" do
|
44
134
|
queue = QuackConcurrency::Queue.new
|
45
|
-
queue.push
|
46
|
-
|
135
|
+
queue.push(1)
|
136
|
+
queue.clear
|
137
|
+
thread = Thread.new do
|
138
|
+
sleep 1
|
139
|
+
queue.push(2)
|
140
|
+
end
|
141
|
+
start_time = Time.now
|
142
|
+
expect(queue.pop).to eql 2
|
143
|
+
end_time = Time.now
|
144
|
+
duration = end_time - start_time
|
145
|
+
thread.join
|
146
|
+
expect(duration).to be > 0.5
|
47
147
|
end
|
48
148
|
end
|
49
|
-
|
149
|
+
|
50
150
|
end
|
151
|
+
|
51
152
|
end
|
@@ -2,7 +2,40 @@ require 'quack_concurrency'
|
|
2
2
|
|
3
3
|
RSpec.describe QuackConcurrency::ReentrantMutex do
|
4
4
|
|
5
|
-
describe "
|
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
|
+
describe "#lock" do
|
6
39
|
|
7
40
|
context "when called for first time" do
|
8
41
|
it "should not raise error" do
|
@@ -11,7 +44,7 @@ RSpec.describe QuackConcurrency::ReentrantMutex do
|
|
11
44
|
end
|
12
45
|
end
|
13
46
|
|
14
|
-
context "when called
|
47
|
+
context "when called a second time" do
|
15
48
|
it "should not raise error" do
|
16
49
|
mutex = QuackConcurrency::ReentrantMutex.new
|
17
50
|
mutex.lock
|
@@ -19,29 +52,29 @@ RSpec.describe QuackConcurrency::ReentrantMutex do
|
|
19
52
|
end
|
20
53
|
end
|
21
54
|
|
22
|
-
|
23
|
-
|
24
|
-
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#lock, #unlock" do
|
58
|
+
|
59
|
+
context "when #lock called on non owning thread" do
|
60
|
+
it "should wait for #unlock" do
|
25
61
|
mutex = QuackConcurrency::ReentrantMutex.new
|
26
62
|
thread = Thread.new do
|
27
|
-
sleep 1
|
28
63
|
mutex.lock
|
29
|
-
|
64
|
+
sleep 2
|
65
|
+
mutex.unlock
|
30
66
|
end
|
67
|
+
sleep 1
|
68
|
+
start_time = Time.now
|
31
69
|
mutex.lock
|
32
|
-
|
33
|
-
|
34
|
-
mutex.unlock
|
70
|
+
end_time = Time.now
|
71
|
+
duration = end_time - start_time
|
35
72
|
thread.join
|
36
|
-
expect(
|
73
|
+
expect(duration).to be > 0.5
|
37
74
|
end
|
38
75
|
end
|
39
76
|
|
40
|
-
|
41
|
-
|
42
|
-
describe "unlock" do
|
43
|
-
|
44
|
-
context "when called after one lock" do
|
77
|
+
context "when #unlock called after one #lock" do
|
45
78
|
it "should not raise error" do
|
46
79
|
mutex = QuackConcurrency::ReentrantMutex.new
|
47
80
|
mutex.lock
|
@@ -49,7 +82,7 @@ RSpec.describe QuackConcurrency::ReentrantMutex do
|
|
49
82
|
end
|
50
83
|
end
|
51
84
|
|
52
|
-
context "when called after two locks" do
|
85
|
+
context "when #unlock called after two #locks" do
|
53
86
|
it "should not raise error" do
|
54
87
|
mutex = QuackConcurrency::ReentrantMutex.new
|
55
88
|
mutex.lock
|
@@ -58,14 +91,108 @@ RSpec.describe QuackConcurrency::ReentrantMutex do
|
|
58
91
|
end
|
59
92
|
end
|
60
93
|
|
61
|
-
context "when called twice after only one lock" do
|
94
|
+
context "when #unlock called twice after only one #lock" do
|
62
95
|
it "should raise error" do
|
63
96
|
mutex = QuackConcurrency::ReentrantMutex.new
|
64
97
|
mutex.lock
|
65
98
|
mutex.unlock
|
66
|
-
expect { mutex.unlock }.to raise_error(
|
99
|
+
expect { mutex.unlock }.to raise_error(QuackConcurrency::ReentrantMutex::Error)
|
67
100
|
end
|
68
101
|
end
|
69
102
|
|
70
103
|
end
|
104
|
+
|
105
|
+
describe "#lock, #try_lock" do
|
106
|
+
|
107
|
+
context "when #try_lock called" do
|
108
|
+
it "should reutrn true" do
|
109
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
110
|
+
expect(mutex.try_lock).to eql true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "when #try_lock called after #lock called from other Thread" do
|
115
|
+
it "should reutrn false" do
|
116
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
117
|
+
thread = Thread.new { mutex.lock }
|
118
|
+
sleep 1
|
119
|
+
expect(mutex.try_lock).to eql false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "#lock, #try_lock, #unlock" do
|
126
|
+
|
127
|
+
context "when #lock called after #try_lock called from other Thread" do
|
128
|
+
it "should wait for #unlock" do
|
129
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
130
|
+
thread = Thread.new do
|
131
|
+
mutex.try_lock
|
132
|
+
sleep 2
|
133
|
+
mutex.unlock
|
134
|
+
end
|
135
|
+
sleep 1
|
136
|
+
start_time = Time.now
|
137
|
+
mutex.lock
|
138
|
+
end_time = Time.now
|
139
|
+
duration = end_time - start_time
|
140
|
+
thread.join
|
141
|
+
expect(duration).to be > 0.5
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "#synchronize" do
|
148
|
+
|
149
|
+
context "when #synchronize called" do
|
150
|
+
it "should return last value from block" do
|
151
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
152
|
+
value = mutex.synchronize do
|
153
|
+
1
|
154
|
+
end
|
155
|
+
expect(value).to eql 1
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
describe "#sleep" do
|
162
|
+
|
163
|
+
context "when #sleep called with time argument" do
|
164
|
+
it "should wait for that time" do
|
165
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
166
|
+
start_time = Time.now
|
167
|
+
mutex.synchronize do
|
168
|
+
mutex.sleep(1)
|
169
|
+
end
|
170
|
+
end_time = Time.now
|
171
|
+
duration = end_time - start_time
|
172
|
+
expect(duration).to be > 0.5
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context "when #sleep called with no time argument" do
|
177
|
+
it "should wait until Thread is resumed" do
|
178
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
179
|
+
start_time = nil
|
180
|
+
end_time = nil
|
181
|
+
thread = Thread.new do
|
182
|
+
start_time = Time.now
|
183
|
+
mutex.synchronize do
|
184
|
+
mutex.sleep
|
185
|
+
end
|
186
|
+
end_time = Time.now
|
187
|
+
end
|
188
|
+
sleep 1
|
189
|
+
thread.run
|
190
|
+
thread.join
|
191
|
+
duration = end_time - start_time
|
192
|
+
expect(duration).to be > 0.5
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
71
198
|
end
|
data/spec/semaphore_spec.rb
CHANGED
@@ -2,26 +2,276 @@ require 'quack_concurrency'
|
|
2
2
|
|
3
3
|
RSpec.describe QuackConcurrency::Semaphore do
|
4
4
|
|
5
|
-
describe "
|
5
|
+
describe "::new" do
|
6
6
|
|
7
|
-
context "when
|
8
|
-
it "should
|
9
|
-
|
7
|
+
context "when called without a 'duck_types' argument" do
|
8
|
+
it "should create a new QuackConcurrency::Semaphore" do
|
9
|
+
semaphore = QuackConcurrency::Semaphore.new
|
10
|
+
expect(semaphore).to be_a(QuackConcurrency::Semaphore)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "when called with 'condition_variable' and 'mutex' duck types" do
|
15
|
+
it "should create a new QuackConcurrency::Semaphore" do
|
16
|
+
duck_types = {condition_variable: Class.new, mutex: Class.new}
|
17
|
+
semaphore = QuackConcurrency::Semaphore.new(duck_types: duck_types)
|
18
|
+
expect(semaphore).to be_a(QuackConcurrency::Semaphore)
|
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::Semaphore.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::Semaphore.new(duck_types: duck_types) }.to raise_error(ArgumentError)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#release" do
|
39
|
+
|
40
|
+
context "when called for the first time with many permits available" do
|
41
|
+
it "should not raise error" do
|
42
|
+
semaphore = QuackConcurrency::Semaphore.new(2)
|
43
|
+
|
44
|
+
expect{ semaphore.release }.not_to raise_error
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when called a second time with one permit available" do
|
49
|
+
it "should not raise error" do
|
10
50
|
semaphore = QuackConcurrency::Semaphore.new(2)
|
51
|
+
semaphore.release
|
52
|
+
expect{ semaphore.release }.not_to raise_error
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#release, #reacquire" do
|
59
|
+
|
60
|
+
context "when #release called with no permits available" do
|
61
|
+
it "should wait until #reacquire is called" do
|
62
|
+
semaphore = QuackConcurrency::Semaphore.new(2)
|
63
|
+
semaphore.release
|
64
|
+
semaphore.release
|
11
65
|
thread = Thread.new do
|
12
66
|
sleep 1
|
13
|
-
semaphore.
|
14
|
-
$test << 2
|
67
|
+
semaphore.reacquire
|
15
68
|
end
|
16
|
-
|
17
|
-
semaphore.acquire
|
18
|
-
sleep 2
|
19
|
-
$test << 1
|
69
|
+
start_time = Time.now
|
20
70
|
semaphore.release
|
71
|
+
end_time = Time.now
|
72
|
+
duration = end_time - start_time
|
21
73
|
thread.join
|
22
|
-
expect(
|
74
|
+
expect(duration).to be > 0.5
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "when #reacquire called when all permits are available" do
|
79
|
+
it "should raise QuackConcurrency::Semaphore::Error" do
|
80
|
+
semaphore = QuackConcurrency::Semaphore.new(2)
|
81
|
+
expect{ semaphore.reacquire }.to raise_error(QuackConcurrency::Semaphore::Error)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "#release, #reacquire, #permit_available?, #permits_available" do
|
88
|
+
|
89
|
+
context "#permit_available? and #permits_available" do
|
90
|
+
it "should work as expected" do
|
91
|
+
semaphore = QuackConcurrency::Semaphore.new(2)
|
92
|
+
expect(semaphore.permit_available?).to eql true
|
93
|
+
expect(semaphore.permits_available).to eql 2
|
94
|
+
semaphore.release
|
95
|
+
expect(semaphore.permit_available?).to eql true
|
96
|
+
expect(semaphore.permits_available).to eql 1
|
97
|
+
semaphore.release
|
98
|
+
expect(semaphore.permit_available?).to eql false
|
99
|
+
expect(semaphore.permits_available).to eql 0
|
100
|
+
semaphore.reacquire
|
101
|
+
expect(semaphore.permit_available?).to eql true
|
102
|
+
expect(semaphore.permits_available).to eql 1
|
103
|
+
semaphore.reacquire
|
104
|
+
expect(semaphore.permit_available?).to eql true
|
105
|
+
expect(semaphore.permits_available).to eql 2
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "#set_permit_count, #permits_available" do
|
112
|
+
|
113
|
+
context "when #set_permit_count is called with more permits then currently exist" do
|
114
|
+
it "should add permits" do
|
115
|
+
semaphore = QuackConcurrency::Semaphore.new(2)
|
116
|
+
expect(semaphore.permits_available).to eql 2
|
117
|
+
semaphore.set_permit_count(3)
|
118
|
+
expect(semaphore.permits_available).to eql 3
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "when #set_permit_count is called with less permits then currently exist" do
|
123
|
+
it "should remove permits" do
|
124
|
+
semaphore = QuackConcurrency::Semaphore.new(2)
|
125
|
+
expect(semaphore.permits_available).to eql 2
|
126
|
+
semaphore.set_permit_count(1)
|
127
|
+
expect(semaphore.permits_available).to eql 1
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "#set_permit_count, #permits_available, #release" do
|
134
|
+
|
135
|
+
context "when #set_permit_count is called with less permits then currently available" do
|
136
|
+
it "should raise QuackConcurrency::Semaphore::Error" do
|
137
|
+
semaphore = QuackConcurrency::Semaphore.new(2)
|
138
|
+
expect(semaphore.permits_available).to eql 2
|
139
|
+
semaphore.release
|
140
|
+
semaphore.release
|
141
|
+
expect{ semaphore.set_permit_count(1) }.to raise_error(QuackConcurrency::Semaphore::Error)
|
23
142
|
end
|
24
143
|
end
|
25
144
|
|
26
145
|
end
|
146
|
+
|
147
|
+
describe "#set_permit_count, #release" do
|
148
|
+
|
149
|
+
context "when #set_permit_count is called with more permits when one thread is waiting on #release" do
|
150
|
+
it "should resume the thread" do
|
151
|
+
semaphore = QuackConcurrency::Semaphore.new(2)
|
152
|
+
semaphore.release
|
153
|
+
semaphore.release
|
154
|
+
thread = Thread.new do
|
155
|
+
sleep 1
|
156
|
+
semaphore.set_permit_count(3)
|
157
|
+
end
|
158
|
+
start_time = Time.now
|
159
|
+
semaphore.release
|
160
|
+
end_time = Time.now
|
161
|
+
duration = end_time - start_time
|
162
|
+
thread.join
|
163
|
+
expect(duration).to be > 0.5
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "#set_permit_count!, #permits_available" do
|
170
|
+
|
171
|
+
context "when #set_permit_count! is called with more permits then currently exist" do
|
172
|
+
it "should add permits" do
|
173
|
+
semaphore = QuackConcurrency::Semaphore.new(2)
|
174
|
+
expect(semaphore.permits_available).to eql 2
|
175
|
+
semaphore.set_permit_count!(3)
|
176
|
+
expect(semaphore.permits_available).to eql 3
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context "when #set_permit_count! is called with less permits then currently exist" do
|
181
|
+
it "should remove permits" do
|
182
|
+
semaphore = QuackConcurrency::Semaphore.new(2)
|
183
|
+
expect(semaphore.permits_available).to eql 2
|
184
|
+
semaphore.set_permit_count!(1)
|
185
|
+
expect(semaphore.permits_available).to eql 1
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
describe "#set_permit_count!, #permits_available, #release, #reacquire" do
|
192
|
+
|
193
|
+
context "when #set_permit_count! is called with less permits then currently available" do
|
194
|
+
it "should force new permit count" do
|
195
|
+
semaphore = QuackConcurrency::Semaphore.new(2)
|
196
|
+
expect(semaphore.permits_available).to eql 2
|
197
|
+
semaphore.release
|
198
|
+
semaphore.release
|
199
|
+
semaphore.set_permit_count!(1)
|
200
|
+
expect(semaphore.permit_available?).to eql false
|
201
|
+
semaphore.reacquire
|
202
|
+
expect(semaphore.permit_available?).to eql false
|
203
|
+
semaphore.reacquire
|
204
|
+
expect(semaphore.permit_available?).to eql true
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
describe "#set_permit_count!, #release" do
|
211
|
+
|
212
|
+
context "when #set_permit_count! is called with more permits when one thread is waiting on #release" do
|
213
|
+
it "should resume the thread" do
|
214
|
+
semaphore = QuackConcurrency::Semaphore.new(2)
|
215
|
+
semaphore.release
|
216
|
+
semaphore.release
|
217
|
+
thread = Thread.new do
|
218
|
+
sleep 1
|
219
|
+
semaphore.set_permit_count(3)
|
220
|
+
end
|
221
|
+
start_time = Time.now
|
222
|
+
semaphore.release
|
223
|
+
end_time = Time.now
|
224
|
+
duration = end_time - start_time
|
225
|
+
thread.join
|
226
|
+
expect(duration).to be > 0.5
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
describe "#set_permit_count!, #release, #permit_available?" do
|
233
|
+
|
234
|
+
context "when semaphore has no permits available, them #set_permit_count! is called to remove 2 permits, then called again to add 1 permit" do
|
235
|
+
it "should not have any permits available" do
|
236
|
+
semaphore = QuackConcurrency::Semaphore.new(3)
|
237
|
+
semaphore.release
|
238
|
+
semaphore.release
|
239
|
+
expect(semaphore.permit_available?).to eql true
|
240
|
+
semaphore.set_permit_count!(1)
|
241
|
+
expect(semaphore.permit_available?).to eql false
|
242
|
+
semaphore.set_permit_count!(2)
|
243
|
+
expect(semaphore.permit_available?).to eql false
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
describe "#set_permit_count!, #release, #reacquire" do
|
250
|
+
|
251
|
+
context "when semaphore has no permits available, them #set_permit_count! is called to remove 2 permits, then a thread starts waiting for #release, then #set_permit_count! is called again to add 1 permit" do
|
252
|
+
it "thread should wait for #reacquire to be called" do
|
253
|
+
semaphore = QuackConcurrency::Semaphore.new(3)
|
254
|
+
semaphore.release
|
255
|
+
semaphore.release
|
256
|
+
semaphore.release
|
257
|
+
semaphore.set_permit_count!(1)
|
258
|
+
thread = Thread.new do
|
259
|
+
sleep 1
|
260
|
+
semaphore.set_permit_count!(2)
|
261
|
+
sleep 1
|
262
|
+
semaphore.reacquire
|
263
|
+
sleep 1
|
264
|
+
semaphore.reacquire
|
265
|
+
end
|
266
|
+
start_time = Time.now
|
267
|
+
semaphore.release
|
268
|
+
end_time = Time.now
|
269
|
+
duration = end_time - start_time
|
270
|
+
thread.join
|
271
|
+
expect(duration).to be_between(2.5, 3.5)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|
276
|
+
|
27
277
|
end
|
metadata
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quack_concurrency
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Fors
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-18 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description: Offers concurrency tools that could also be found in the Concurrent
|
14
|
-
|
15
|
-
|
13
|
+
description: Offers concurrency tools that could also be found in the 'Concurrent
|
14
|
+
Ruby'. However, all these tools will also accept core class ducktypes to build off
|
15
|
+
of.
|
16
16
|
email: mail@robfors.com
|
17
17
|
executables: []
|
18
18
|
extensions: []
|
@@ -21,11 +21,19 @@ files:
|
|
21
21
|
- LICENSE
|
22
22
|
- README.md
|
23
23
|
- lib/quack_concurrency.rb
|
24
|
+
- lib/quack_concurrency/concurrency_tool.rb
|
25
|
+
- lib/quack_concurrency/error.rb
|
24
26
|
- lib/quack_concurrency/future.rb
|
27
|
+
- lib/quack_concurrency/future/canceled.rb
|
28
|
+
- lib/quack_concurrency/future/complete.rb
|
29
|
+
- lib/quack_concurrency/name.rb
|
25
30
|
- lib/quack_concurrency/queue.rb
|
26
31
|
- lib/quack_concurrency/reentrant_mutex.rb
|
32
|
+
- lib/quack_concurrency/reentrant_mutex/error.rb
|
27
33
|
- lib/quack_concurrency/semaphore.rb
|
34
|
+
- lib/quack_concurrency/semaphore/error.rb
|
28
35
|
- lib/quack_concurrency/waiter.rb
|
36
|
+
- spec/future_spec.rb
|
29
37
|
- spec/queue_spec.rb
|
30
38
|
- spec/reentrant_mutex_spec.rb
|
31
39
|
- spec/semaphore_spec.rb
|
@@ -50,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
58
|
version: '0'
|
51
59
|
requirements: []
|
52
60
|
rubyforge_project:
|
53
|
-
rubygems_version: 2.
|
61
|
+
rubygems_version: 2.7.6
|
54
62
|
signing_key:
|
55
63
|
specification_version: 4
|
56
64
|
summary: Concurrency tools that accept duck types of core classes.
|