quack_concurrency 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/quack_concurrency/concurrency_tool.rb +2 -0
- data/lib/quack_concurrency/future.rb +25 -8
- data/lib/quack_concurrency/name.rb +2 -0
- data/lib/quack_concurrency/queue/error.rb +6 -0
- data/lib/quack_concurrency/queue.rb +32 -1
- data/lib/quack_concurrency/reentrant_mutex.rb +27 -2
- data/lib/quack_concurrency/semaphore.rb +27 -0
- data/lib/quack_concurrency/waiter.rb +11 -0
- data/lib/quack_concurrency.rb +1 -0
- data/spec/queue_spec.rb +11 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10e029270e81499f14736ffdbd03926f83696ded0fd26b8115fe476d9c7724a9
|
4
|
+
data.tar.gz: 057fddbd478fb2fc8a7ff785f3f0315b360c01491fd4db927f220cadfe7fad64
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9859b662740ad3c4a0d5808b0af646be37d6bad5d06be25885746cd33aaadda174d914c6aabf901a88d7b9ca2a0dffef7424c51e4b7533a94c0031640cacf53a
|
7
|
+
data.tar.gz: cf1689c54c26edb32c9ea46fccb9872cd2c239407401818cbf6cd1c3392ffe52907322a0b2a9a08e53637d42e5707e8415a11c88f666dd1d92006a5198330f3f
|
@@ -1,6 +1,10 @@
|
|
1
1
|
module QuackConcurrency
|
2
2
|
class Future < ConcurrencyTool
|
3
3
|
|
4
|
+
# Creates a new +Future+ concurrency tool.
|
5
|
+
# @param duck_types [Hash] hash of core Ruby classes to overload.
|
6
|
+
# If a +Hash+ is given, the keys +:condition_variable+ and +:mutex+ must be present.
|
7
|
+
# @return [Future]
|
4
8
|
def initialize(duck_types: nil)
|
5
9
|
classes = setup_duck_types(duck_types)
|
6
10
|
@condition_variable = classes[:condition_variable].new
|
@@ -10,17 +14,28 @@ module QuackConcurrency
|
|
10
14
|
@complete = false
|
11
15
|
end
|
12
16
|
|
13
|
-
|
17
|
+
# Cancels the future.
|
18
|
+
# @raise [Complete] if the future is already completed
|
19
|
+
# @return [void] value of the future
|
20
|
+
def cancel
|
14
21
|
@mutex.synchronize do
|
15
22
|
raise Complete if @complete
|
16
|
-
@value_set = true
|
17
23
|
@complete = true
|
18
|
-
@value = new_value
|
19
24
|
@condition_variable.broadcast
|
20
25
|
end
|
21
26
|
nil
|
22
27
|
end
|
23
28
|
|
29
|
+
# Checks if future has a value or is canceled.
|
30
|
+
# @return [Boolean]
|
31
|
+
def complete?
|
32
|
+
@complete
|
33
|
+
end
|
34
|
+
|
35
|
+
# Gets the value of the future.
|
36
|
+
# @note This method will block until the future has completed.
|
37
|
+
# @raise [Canceled] if the future is canceled
|
38
|
+
# @return value of the future
|
24
39
|
def get
|
25
40
|
@mutex.synchronize do
|
26
41
|
@condition_variable.wait(@mutex) unless complete?
|
@@ -30,18 +45,20 @@ module QuackConcurrency
|
|
30
45
|
end
|
31
46
|
end
|
32
47
|
|
33
|
-
|
48
|
+
# Sets the value of the future.
|
49
|
+
# @raise [Complete] if the future has already completed
|
50
|
+
# @param new_value value to assign to future
|
51
|
+
# @return [void]
|
52
|
+
def set(new_value = nil)
|
34
53
|
@mutex.synchronize do
|
35
54
|
raise Complete if @complete
|
55
|
+
@value_set = true
|
36
56
|
@complete = true
|
57
|
+
@value = new_value
|
37
58
|
@condition_variable.broadcast
|
38
59
|
end
|
39
60
|
nil
|
40
61
|
end
|
41
62
|
|
42
|
-
def complete?
|
43
|
-
@complete
|
44
|
-
end
|
45
|
-
|
46
63
|
end
|
47
64
|
end
|
@@ -1,6 +1,12 @@
|
|
1
1
|
module QuackConcurrency
|
2
|
+
|
3
|
+
# @note duck type for +::Thread::Queue+
|
2
4
|
class Queue < ConcurrencyTool
|
3
5
|
|
6
|
+
# Creates a new {Queue} concurrency tool.
|
7
|
+
# @param duck_types [Hash] hash of core Ruby classes to overload.
|
8
|
+
# If a +Hash+ is given, the keys +:condition_variable+ and +:mutex+ must be present.
|
9
|
+
# @return [Queue]
|
4
10
|
def initialize(duck_types: nil)
|
5
11
|
classes = setup_duck_types(duck_types)
|
6
12
|
@condition_variable = classes[:condition_variable].new
|
@@ -10,11 +16,20 @@ module QuackConcurrency
|
|
10
16
|
@closed = false
|
11
17
|
end
|
12
18
|
|
19
|
+
# Removes all objects from the queue.
|
20
|
+
# @return [self]
|
13
21
|
def clear
|
14
22
|
@mutex.synchronize { @queue = [] }
|
15
23
|
self
|
16
24
|
end
|
17
25
|
|
26
|
+
# Closes the queue. A closed queue cannot be re-opened.
|
27
|
+
# After the call to close completes, the following are true:
|
28
|
+
# * {#closed?} will return +true+.
|
29
|
+
# * {#close} will be ignored.
|
30
|
+
# * {#push} will raise an exception.
|
31
|
+
# * until empty, calling {#pop} will return an object from the queue as usual.
|
32
|
+
# @return [self]
|
18
33
|
def close
|
19
34
|
@mutex.synchronize do
|
20
35
|
return if closed?
|
@@ -24,27 +39,41 @@ module QuackConcurrency
|
|
24
39
|
self
|
25
40
|
end
|
26
41
|
|
42
|
+
# Checks if queue is closed.
|
43
|
+
# @return [Boolean]
|
27
44
|
def closed?
|
28
45
|
@closed
|
29
46
|
end
|
30
47
|
|
48
|
+
# Checks if queue is empty.
|
49
|
+
# @return [Boolean]
|
31
50
|
def empty?
|
32
51
|
@queue.empty?
|
33
52
|
end
|
34
53
|
|
54
|
+
# Returns the length of the queue.
|
55
|
+
# @return [Integer]
|
35
56
|
def length
|
36
57
|
@queue.length
|
37
58
|
end
|
38
59
|
alias_method :size, :length
|
39
60
|
|
61
|
+
# Returns the number of threads waiting on the queue.
|
62
|
+
# @return [Integer]
|
40
63
|
def num_waiting
|
41
64
|
@waiting_count
|
42
65
|
end
|
43
66
|
|
44
|
-
|
67
|
+
# Retrieves item from the queue.
|
68
|
+
# @note If the queue is empty, it will block until an item is available.
|
69
|
+
# If +non_block+ is true, it will raise {Error} instead.
|
70
|
+
# @raise {Error} if queue is empty and +non_block+ is true
|
71
|
+
# @param non_block [Boolean]
|
72
|
+
def pop(non_block = false)
|
45
73
|
@mutex.synchronize do
|
46
74
|
if @waiting_count >= length
|
47
75
|
return if closed?
|
76
|
+
raise Error if non_block
|
48
77
|
@waiting_count += 1
|
49
78
|
@condition_variable.wait(@mutex)
|
50
79
|
@waiting_count -= 1
|
@@ -56,6 +85,8 @@ module QuackConcurrency
|
|
56
85
|
alias_method :deq, :pop
|
57
86
|
alias_method :shift, :pop
|
58
87
|
|
88
|
+
# Pushes the given object to the queue.
|
89
|
+
# @return [self]
|
59
90
|
def push(item = nil)
|
60
91
|
@mutex.synchronize do
|
61
92
|
raise ClosedQueueError if closed?
|
@@ -1,8 +1,13 @@
|
|
1
1
|
# based off https://en.wikipedia.org/wiki/Reentrant_mutex
|
2
2
|
|
3
|
+
|
3
4
|
module QuackConcurrency
|
4
5
|
class ReentrantMutex < ConcurrencyTool
|
5
6
|
|
7
|
+
# Creates a new {ReentrantMutex} concurrency tool.
|
8
|
+
# @param duck_types [Hash] hash of core Ruby classes to overload.
|
9
|
+
# If a +Hash+ is given, the keys +:condition_variable+ and +:mutex+ must be present.
|
10
|
+
# @return [ReentrantMutex]
|
6
11
|
def initialize(duck_types: nil)
|
7
12
|
classes = setup_duck_types(duck_types)
|
8
13
|
@condition_variable = classes[:condition_variable].new
|
@@ -11,6 +16,8 @@ module QuackConcurrency
|
|
11
16
|
@lock_depth = 0
|
12
17
|
end
|
13
18
|
|
19
|
+
# Locks this {ReentrantMutex}. Will block until available.
|
20
|
+
# @return [void]
|
14
21
|
def lock
|
15
22
|
@mutex.synchronize do
|
16
23
|
@condition_variable.wait(@mutex) if @owner && @owner != caller
|
@@ -21,30 +28,43 @@ module QuackConcurrency
|
|
21
28
|
nil
|
22
29
|
end
|
23
30
|
|
31
|
+
# Checks if this {ReentrantMutex} is locked by some thread.
|
32
|
+
# @return [Boolean]
|
24
33
|
def locked?
|
25
34
|
!!@owner
|
26
35
|
end
|
27
36
|
|
37
|
+
# Checks if this {ReentrantMutex} is locked by a thread other than the caller.
|
38
|
+
# @return [Boolean]
|
28
39
|
def locked_out?
|
29
40
|
@mutex.synchronize { locked? && @owner != caller }
|
30
41
|
end
|
31
42
|
|
43
|
+
# Checks if this {ReentrantMutex} is locked by the calling thread.
|
44
|
+
# @return [Boolean]
|
32
45
|
def owned?
|
33
46
|
@owner == caller
|
34
47
|
end
|
35
48
|
|
36
|
-
|
49
|
+
# Releases the lock and sleeps.
|
50
|
+
# When the calling thread is next woken up, it will attempt to reacquire the lock.
|
51
|
+
# @param timeout [Integer] seconds to sleep, +nil+ will sleep forever
|
52
|
+
# @raise [Error] if this {ReentrantMutex} wasn't locked by the calling thread.
|
53
|
+
# @return [void]
|
54
|
+
def sleep(timeout = nil)
|
37
55
|
unlock
|
38
56
|
# i would rather not need to get a ducktype for sleep so we will just take
|
39
57
|
# advantage of Mutex's sleep method that must take it into account already
|
40
58
|
@mutex.synchronize do
|
41
|
-
@mutex.sleep(
|
59
|
+
@mutex.sleep(timeout)
|
42
60
|
end
|
43
61
|
nil
|
44
62
|
ensure
|
45
63
|
lock unless owned?
|
46
64
|
end
|
47
65
|
|
66
|
+
# Obtains a lock, runs the block, and releases the lock when the block completes.
|
67
|
+
# @return return value from yielded block
|
48
68
|
def synchronize
|
49
69
|
lock
|
50
70
|
start_depth = @lock_depth
|
@@ -58,6 +78,8 @@ module QuackConcurrency
|
|
58
78
|
unlock
|
59
79
|
end
|
60
80
|
|
81
|
+
# Attempts to obtain the lock and returns immediately.
|
82
|
+
# @return [Boolean] returns if the lock was granted
|
61
83
|
def try_lock
|
62
84
|
@mutex.synchronize do
|
63
85
|
return false if @owner && @owner != caller
|
@@ -67,6 +89,9 @@ module QuackConcurrency
|
|
67
89
|
end
|
68
90
|
end
|
69
91
|
|
92
|
+
# Releases the lock.
|
93
|
+
# @raise [Error] if {ReentrantMutex} wasn't locked by the calling thread
|
94
|
+
# @return [void]
|
70
95
|
def unlock
|
71
96
|
@mutex.synchronize do
|
72
97
|
raise Error, 'can not unlock reentrant mutex, it is not locked' if @lock_depth == 0
|
@@ -1,8 +1,14 @@
|
|
1
1
|
module QuackConcurrency
|
2
2
|
class Semaphore < ConcurrencyTool
|
3
3
|
|
4
|
+
# Gets total permit count
|
5
|
+
# @return [Integer]
|
4
6
|
attr_reader :permit_count
|
5
7
|
|
8
|
+
# Creates a new {Semaphore} concurrency tool.
|
9
|
+
# @param duck_types [Hash] hash of core Ruby classes to overload.
|
10
|
+
# If a +Hash+ is given, the keys +:condition_variable+ and +:mutex+ must be present.
|
11
|
+
# @return [Semaphore]
|
6
12
|
def initialize(permit_count = 1, duck_types: nil)
|
7
13
|
classes = setup_duck_types(duck_types)
|
8
14
|
@condition_variable = classes[:condition_variable].new
|
@@ -12,10 +18,14 @@ module QuackConcurrency
|
|
12
18
|
@mutex = ReentrantMutex.new(duck_types: duck_types)
|
13
19
|
end
|
14
20
|
|
21
|
+
# Check if a permit is available to be released.
|
22
|
+
# @return [Boolean]
|
15
23
|
def permit_available?
|
16
24
|
permits_available >= 1
|
17
25
|
end
|
18
26
|
|
27
|
+
# Counts number of permits available to be released.
|
28
|
+
# @return [Integer]
|
19
29
|
def permits_available
|
20
30
|
@mutex.synchronize do
|
21
31
|
raw_permits_available = @permit_count - @permits_used
|
@@ -23,6 +33,8 @@ module QuackConcurrency
|
|
23
33
|
end
|
24
34
|
end
|
25
35
|
|
36
|
+
# Returns a permit so it can be released again in the future.
|
37
|
+
# @return [void]
|
26
38
|
def reacquire
|
27
39
|
@mutex.synchronize do
|
28
40
|
raise Error, 'can not reacquire a permit, no permits released right now' if @permits_used == 0
|
@@ -32,6 +44,9 @@ module QuackConcurrency
|
|
32
44
|
nil
|
33
45
|
end
|
34
46
|
|
47
|
+
# Releases a permit.
|
48
|
+
# @note Will block until a permit is available.
|
49
|
+
# @return [void]
|
35
50
|
def release
|
36
51
|
@mutex.synchronize do
|
37
52
|
@condition_variable.wait(@mutex) unless permit_available?
|
@@ -41,6 +56,9 @@ module QuackConcurrency
|
|
41
56
|
nil
|
42
57
|
end
|
43
58
|
|
59
|
+
# Changes the permit count after {Semaphore} has been created.
|
60
|
+
# @raise [Error] if total permit count is reduced and not enough permits are available to remove
|
61
|
+
# @return [void]
|
44
62
|
def set_permit_count(new_permit_count)
|
45
63
|
verify_permit_count(new_permit_count)
|
46
64
|
@mutex.synchronize do
|
@@ -53,6 +71,11 @@ module QuackConcurrency
|
|
53
71
|
nil
|
54
72
|
end
|
55
73
|
|
74
|
+
# Changes the permit count after {Semaphore} has been created.
|
75
|
+
# If total permit count is reduced and not enough permits are available to remove,
|
76
|
+
# it will change the count anyway but some permits will need to be reacquired
|
77
|
+
# before any can be released.
|
78
|
+
# @return [void]
|
56
79
|
def set_permit_count!(new_permit_count)
|
57
80
|
verify_permit_count(new_permit_count)
|
58
81
|
@mutex.synchronize do
|
@@ -67,6 +90,8 @@ module QuackConcurrency
|
|
67
90
|
nil
|
68
91
|
end
|
69
92
|
|
93
|
+
# Releases a permit, runs the block, and reacquires the permit when the block completes.
|
94
|
+
# @return return value from yielded block
|
70
95
|
def synchronize
|
71
96
|
release
|
72
97
|
begin
|
@@ -76,6 +101,8 @@ module QuackConcurrency
|
|
76
101
|
end
|
77
102
|
end
|
78
103
|
|
104
|
+
# Attempts to release a permit and returns immediately.
|
105
|
+
# @return [Boolean] returns if the permit was released
|
79
106
|
def try_release
|
80
107
|
@mutex.synchronize do
|
81
108
|
if permit_available?
|
@@ -1,14 +1,25 @@
|
|
1
1
|
module QuackConcurrency
|
2
2
|
class Waiter < ConcurrencyTool
|
3
3
|
|
4
|
+
# Creates a new {Waiter} concurrency tool.
|
5
|
+
# @param duck_types [Hash] hash of core Ruby classes to overload.
|
6
|
+
# If a +Hash+ is given, the keys +:condition_variable+ and +:mutex+ must be present.
|
7
|
+
# @return [Waiter]
|
4
8
|
def initialize(duck_types: nil)
|
5
9
|
@queue = Queue.new(duck_types: duck_types)
|
6
10
|
end
|
7
11
|
|
12
|
+
# Resumes next waiting thread.
|
13
|
+
# @param value value to pass to waiting thread
|
14
|
+
# @return [void]
|
8
15
|
def resume(value = nil)
|
9
16
|
@queue << value
|
17
|
+
nil
|
10
18
|
end
|
11
19
|
|
20
|
+
# Waits for another thread to resume the calling thread.
|
21
|
+
# @note Will block until resumed.
|
22
|
+
# @return value passed from resuming thread
|
12
23
|
def wait
|
13
24
|
@queue.pop
|
14
25
|
end
|
data/lib/quack_concurrency.rb
CHANGED
@@ -10,6 +10,7 @@ require 'quack_concurrency/semaphore'
|
|
10
10
|
require 'quack_concurrency/waiter'
|
11
11
|
require 'quack_concurrency/future/canceled'
|
12
12
|
require 'quack_concurrency/future/complete'
|
13
|
+
require 'quack_concurrency/queue/error'
|
13
14
|
require 'quack_concurrency/reentrant_mutex/error'
|
14
15
|
require 'quack_concurrency/semaphore/error'
|
15
16
|
|
data/spec/queue_spec.rb
CHANGED
@@ -47,6 +47,17 @@ RSpec.describe QuackConcurrency::Queue do
|
|
47
47
|
|
48
48
|
end
|
49
49
|
|
50
|
+
describe "#pop" do
|
51
|
+
|
52
|
+
context "when #pop is called with non_block set to true" do
|
53
|
+
it "should raise Error" do
|
54
|
+
queue = QuackConcurrency::Queue.new
|
55
|
+
expect{ queue.pop(true) }.to raise_error(QuackConcurrency::Queue::Error)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
50
61
|
describe "#close, #push" do
|
51
62
|
|
52
63
|
context "when called when queue is closed" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quack_concurrency
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Fors
|
@@ -29,6 +29,7 @@ files:
|
|
29
29
|
- lib/quack_concurrency/future/complete.rb
|
30
30
|
- lib/quack_concurrency/name.rb
|
31
31
|
- lib/quack_concurrency/queue.rb
|
32
|
+
- lib/quack_concurrency/queue/error.rb
|
32
33
|
- lib/quack_concurrency/reentrant_mutex.rb
|
33
34
|
- lib/quack_concurrency/reentrant_mutex/error.rb
|
34
35
|
- lib/quack_concurrency/semaphore.rb
|