quack_concurrency 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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