quack_concurrency 0.2.0 → 0.3.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 +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.
|