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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1e1b4dcf29faab53b77b05eb8017b8b73c339829
4
- data.tar.gz: 85e7852afdcad9dba9fab2b264104844ebd54ad9
2
+ SHA256:
3
+ metadata.gz: 164e219c34134cf836d5a4169272a1f6185d832790ae69c29ad4dd4496a2af0f
4
+ data.tar.gz: e4f01597b27e39da0e1d916228abe30764b5b8b30a3d68d90fabea53635a5d10
5
5
  SHA512:
6
- metadata.gz: b934eb948f2cb2a9a7565db507d83130e14363b3ab2e816f590f06ef8d9ccdeff8c3f6e655e4dcf9878f77f1311fcb0ff95ee092cdd881c26ea6e66840cb7c1c
7
- data.tar.gz: 27891264874dd9a5083dcb7e9ac10a18665eeb7d538b10caa6471e69bda84c3c93996b3f9d49e4b602f70fe94593a41edb001c77e46b38deb6dad77ec75dee84
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
@@ -0,0 +1,5 @@
1
+ module QuackConcurrency
2
+ class Error < StandardError
3
+ end
4
+ end
5
+
@@ -0,0 +1,6 @@
1
+ module QuackConcurrency
2
+ class Future
3
+ class Canceled < Error
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module QuackConcurrency
2
+ class Future
3
+ class Complete < Error
4
+ end
5
+ end
6
+ 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
- condition_variable_class = duck_types[:condition_variable] || ConditionVariable
9
- mutex_class = duck_types[:mutex] || Mutex
10
- @condition_variable = condition_variable_class.new
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 'should not get here' unless complete?
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
- condition_variable_class = duck_types[:condition_variable] || ConditionVariable
6
- mutex_class = duck_types[:mutex] || Mutex
7
- @condition_variable = condition_variable_class.new
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
@@ -0,0 +1,6 @@
1
+ module QuackConcurrency
2
+ class ReentrantMutex
3
+ class Error < Error
4
+ end
5
+ end
6
+ end
@@ -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
- condition_variable_class = duck_types[:condition_variable] || ConditionVariable
8
- @kernel_module = duck_types[:kernel] || Kernel
9
- mutex_class = duck_types[:mutex] || Mutex
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(timeout = nil)
36
+ def sleep(*args)
38
37
  unlock
39
- if timeout
40
- @kernel_module.sleep(timeout)
41
- else
42
- @kernel_module.sleep
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
- lock
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
- return false
64
- else
65
- @owner = caller
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 "not locked" if @lock_depth == 0
75
- raise "not the owner" unless @owner == caller
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
@@ -0,0 +1,6 @@
1
+ module QuackConcurrency
2
+ class Semaphore
3
+ class Error < Error
4
+ end
5
+ end
6
+ end
@@ -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
- condition_variable_class = duck_types[:condition_variable] || ConditionVariable
8
- raise 'permit_count invalid' if permit_count < 1
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 acquire
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 remove enough permits right 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
- raise "'permit_count' invalid" if new_permit_count < 1
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 release
49
- @mutex.synchronize do
50
- raise 'no pemit to release' if @permits_used == 0
51
- @permits_used -= 1
52
- @condition_variable.signal if permit_available?
70
+ def synchronize
71
+ release
72
+ begin
73
+ yield
74
+ ensure
75
+ reacquire
53
76
  end
54
- nil
55
77
  end
56
78
 
57
- # how to handle if yield raises an error but has temporarily released its permit?
58
- #def synchronize
59
- # acquire
60
- # begin
61
- # yield
62
- # ensure
63
- # release
64
- # end
65
- #end
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
@@ -1,7 +1,7 @@
1
1
  module QuackConcurrency
2
- class Waiter
2
+ class Waiter < ConcurrencyTool
3
3
 
4
- def initialize(duck_types: {})
4
+ def initialize(duck_types: nil)
5
5
  @queue = Queue.new(duck_types: duck_types)
6
6
  end
7
7
 
@@ -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
@@ -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 "pop" do
5
+ describe "::new" do
6
6
 
7
- context "when called when queue empty" do
8
- it "should wait" do
9
- $test = []
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.push
81
+ queue.close
16
82
  end
17
- sleep 1
18
- queue.push
19
- queue.pop
20
- $test << 2
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($test).to eql [1, 2]
88
+ expect(duration).to be > 0.5
23
89
  end
24
90
  end
25
91
 
26
- context "when called when queue not empty" do
27
- it "should immediately return" do
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
- context "when called" do
43
- it "should return value of the push" do
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 1
46
- expect(queue.pop).to eql 1
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 "lock" do
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 the second time" do
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
- context "when called on non owning thread" do
23
- it "should wait" do
24
- $test = []
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
- $test << 2
64
+ sleep 2
65
+ mutex.unlock
30
66
  end
67
+ sleep 1
68
+ start_time = Time.now
31
69
  mutex.lock
32
- sleep 2
33
- $test << 1
34
- mutex.unlock
70
+ end_time = Time.now
71
+ duration = end_time - start_time
35
72
  thread.join
36
- expect($test).to eql [1, 2]
73
+ expect(duration).to be > 0.5
37
74
  end
38
75
  end
39
76
 
40
- end
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(RuntimeError)
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
@@ -2,26 +2,276 @@ require 'quack_concurrency'
2
2
 
3
3
  RSpec.describe QuackConcurrency::Semaphore do
4
4
 
5
- describe "acquire" do
5
+ describe "::new" do
6
6
 
7
- context "when no permits are available" do
8
- it "should wait" do
9
- $test = []
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.acquire
14
- $test << 2
67
+ semaphore.reacquire
15
68
  end
16
- semaphore.acquire
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($test).to eql [1, 2]
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.2.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-14 00:00:00.000000000 Z
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 Ruby
14
- project. However, all these tools will also accept duck types to allow core classes
15
- to behave as desired.
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.4.8
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.