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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c54a140614cc505337efaaab611c8bb6d8e19bc794b0204fbd6630dcabe742a9
4
- data.tar.gz: '0568def469ea6c5be8645b53f8ccc9aa8a07b18ee072d6b72a88c0717519629b'
3
+ metadata.gz: 10e029270e81499f14736ffdbd03926f83696ded0fd26b8115fe476d9c7724a9
4
+ data.tar.gz: 057fddbd478fb2fc8a7ff785f3f0315b360c01491fd4db927f220cadfe7fad64
5
5
  SHA512:
6
- metadata.gz: 9b52245506a8d0102c74b0160536c575e9e6784a4201776e0a9f4ac3b3975f6c93f0c376a7879529e2f1d6e19272e7b417bea57e77db3114da69daef62e6dcf8
7
- data.tar.gz: 6dc0136c93ad3918c68e7c80deb142db0f81760a995a984dfff77537d7f4e3699f95d7b31c9bd1980c8c8bce3981d1840ea63b086b0c095bfea0885a3dbddba4
6
+ metadata.gz: 9859b662740ad3c4a0d5808b0af646be37d6bad5d06be25885746cd33aaadda174d914c6aabf901a88d7b9ca2a0dffef7424c51e4b7533a94c0031640cacf53a
7
+ data.tar.gz: cf1689c54c26edb32c9ea46fccb9872cd2c239407401818cbf6cd1c3392ffe52907322a0b2a9a08e53637d42e5707e8415a11c88f666dd1d92006a5198330f3f
@@ -1,4 +1,6 @@
1
1
  module QuackConcurrency
2
+
3
+ # @api private
2
4
  class ConcurrencyTool
3
5
 
4
6
  def setup_duck_types(supplied_classes)
@@ -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
- def set(new_value = nil)
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
- def cancel
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,4 +1,6 @@
1
1
  module QuackConcurrency
2
+
3
+ # @api private
2
4
  class Name < String
3
5
 
4
6
  def initialize(*args)
@@ -0,0 +1,6 @@
1
+ module QuackConcurrency
2
+ class Queue
3
+ class Error < Error
4
+ end
5
+ end
6
+ 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
- def pop
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
- def sleep(*args)
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(*args)
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
@@ -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.3.1
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