quack_concurrency 0.0.1 → 0.2.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
  SHA1:
3
- metadata.gz: 9ced3c77423c57792ac57dc44069cb0351e7748c
4
- data.tar.gz: ad10b488d8f181de7eb70a8360fba3469053edc4
3
+ metadata.gz: 1e1b4dcf29faab53b77b05eb8017b8b73c339829
4
+ data.tar.gz: 85e7852afdcad9dba9fab2b264104844ebd54ad9
5
5
  SHA512:
6
- metadata.gz: 8c8a971447fec19075e50479653285b5c7a65348a01f9107fa27b3d2b530ee3210b1334ef4c5f26190739532a83442ba2f38fc6945b2d509b5c80cce7a9e5b87
7
- data.tar.gz: 23c46a5b6c6d6f3ecfe71e8c7edf28e8068eec4883a1bb5817e85f60be878fbe75dcefc1e6f6019182b2d1244e78f8b7db3113df5162f6381f320902c662ddd0
6
+ metadata.gz: b934eb948f2cb2a9a7565db507d83130e14363b3ab2e816f590f06ef8d9ccdeff8c3f6e655e4dcf9878f77f1311fcb0ff95ee092cdd881c26ea6e66840cb7c1c
7
+ data.tar.gz: 27891264874dd9a5083dcb7e9ac10a18665eeb7d538b10caa6471e69bda84c3c93996b3f9d49e4b602f70fe94593a41edb001c77e46b38deb6dad77ec75dee84
@@ -1,10 +1,11 @@
1
1
  require 'thread'
2
2
 
3
- require_relative "quack_concurrency/condition_variable.rb"
4
- require_relative "quack_concurrency/future.rb"
5
- require_relative "quack_concurrency/reentrant_mutex.rb"
6
- require_relative "quack_concurrency/semaphore.rb"
7
- require_relative "quack_concurrency/waiter.rb"
3
+ require 'quack_concurrency/future'
4
+ require 'quack_concurrency/queue'
5
+ require 'quack_concurrency/reentrant_mutex'
6
+ require 'quack_concurrency/semaphore'
7
+ require 'quack_concurrency/waiter'
8
+
8
9
 
9
10
  module QuackConcurrency
10
11
  end
@@ -1,17 +1,14 @@
1
- # Author: Rob Fors
2
- # Revision Date: 20180102
3
-
4
1
  module QuackConcurrency
5
2
  class Future
6
-
3
+
7
4
  class Canceled < StandardError
8
5
  end
9
6
 
10
7
  def initialize(duck_types: {})
8
+ condition_variable_class = duck_types[:condition_variable] || ConditionVariable
11
9
  mutex_class = duck_types[:mutex] || Mutex
12
- condition_variable_class = duck_types[:condition_variable] || ::ConditionVariable
13
- @mutex = mutex_class.new
14
10
  @condition_variable = condition_variable_class.new
11
+ @mutex = mutex_class.new
15
12
  @value = nil
16
13
  @value_set = false
17
14
  @complete = false
@@ -0,0 +1,72 @@
1
+ module QuackConcurrency
2
+ class Queue
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
9
+ @queue = []
10
+ @waiting_count = 0
11
+ @closed = false
12
+ end
13
+
14
+ def clear
15
+ @mutex.synchronize { @queue = [] }
16
+ self
17
+ end
18
+
19
+ def close
20
+ @mutex.synchronize do
21
+ return if closed?
22
+ @closed = true
23
+ @condition_variable.broadcast
24
+ end
25
+ self
26
+ end
27
+
28
+ def closed?
29
+ @closed
30
+ end
31
+
32
+ def empty?
33
+ @queue.empty?
34
+ end
35
+
36
+ def length
37
+ @queue.length
38
+ end
39
+ alias_method :size, :length
40
+
41
+ def num_waiting
42
+ @waiting_count
43
+ end
44
+
45
+ def pop
46
+ @mutex.synchronize do
47
+ if @waiting_count >= length
48
+ return if closed?
49
+ @waiting_count += 1
50
+ @condition_variable.wait(@mutex)
51
+ @waiting_count -= 1
52
+ return if closed?
53
+ end
54
+ @queue.shift
55
+ end
56
+ end
57
+ alias_method :deq, :pop
58
+ alias_method :shift, :pop
59
+
60
+ def push(item = nil)
61
+ @mutex.synchronize do
62
+ raise ClosedQueueError if closed?
63
+ @queue.push(item)
64
+ @condition_variable.signal
65
+ end
66
+ self
67
+ end
68
+ alias_method :<<, :push
69
+ alias_method :enq, :push
70
+
71
+ end
72
+ end
@@ -1,16 +1,14 @@
1
- # Author: Rob Fors
2
- # Revision Date: 20180102
3
-
4
1
  # based off https://en.wikipedia.org/wiki/Reentrant_mutex
5
2
 
6
3
  module QuackConcurrency
7
4
  class ReentrantMutex
8
5
 
9
6
  def initialize(duck_types: {})
7
+ condition_variable_class = duck_types[:condition_variable] || ConditionVariable
8
+ @kernel_module = duck_types[:kernel] || Kernel
10
9
  mutex_class = duck_types[:mutex] || Mutex
11
- condition_variable_class = duck_types[:condition_variable] || ::ConditionVariable
12
- @mutex = mutex_class.new
13
10
  @condition_variable = condition_variable_class.new
11
+ @mutex = mutex_class.new
14
12
  @owner = nil
15
13
  @lock_depth = 0
16
14
  end
@@ -36,6 +34,16 @@ module QuackConcurrency
36
34
  @owner == caller
37
35
  end
38
36
 
37
+ def sleep(timeout = nil)
38
+ unlock
39
+ if timeout
40
+ @kernel_module.sleep(timeout)
41
+ else
42
+ @kernel_module.sleep
43
+ end
44
+ lock
45
+ end
46
+
39
47
  def synchronize
40
48
  lock
41
49
  start_depth = @lock_depth
@@ -44,7 +52,7 @@ module QuackConcurrency
44
52
  result
45
53
  ensure
46
54
  unless @lock_depth == start_depth && @owner == start_owner
47
- raise 'Could not unlock mutex as its state has been modified.'
55
+ raise 'could not unlock mutex as its state has been modified'
48
56
  end
49
57
  unlock
50
58
  end
@@ -63,8 +71,8 @@ module QuackConcurrency
63
71
 
64
72
  def unlock
65
73
  @mutex.synchronize do
66
- raise "Not locked." if @lock_depth == 0
67
- raise "Not the owner." unless @owner == Thread.current
74
+ raise "not locked" if @lock_depth == 0
75
+ raise "not the owner" unless @owner == caller
68
76
  @lock_depth -= 1
69
77
  if @lock_depth == 0
70
78
  @owner = nil
@@ -1,17 +1,15 @@
1
- # Author: Rob Fors
2
- # Revision Date: 20180102
3
-
4
1
  module QuackConcurrency
5
2
  class Semaphore
6
3
 
7
4
  attr_reader :permit_count
8
5
 
9
6
  def initialize(permit_count = 1, duck_types: {})
10
- raise 'Error: permit_count invalid' if permit_count < 1
7
+ condition_variable_class = duck_types[:condition_variable] || ConditionVariable
8
+ raise 'permit_count invalid' if permit_count < 1
11
9
  @permit_count = permit_count
12
10
  @permits_used = 0
11
+ @condition_variable = condition_variable_class.new
13
12
  @mutex = ReentrantMutex.new(duck_types: duck_types)
14
- @condition_variable = ConditionVariable.new
15
13
  end
16
14
 
17
15
  def acquire
@@ -26,7 +24,7 @@ module QuackConcurrency
26
24
  @mutex.synchronize do
27
25
  remove_permits = @permit_count - new_permit_count
28
26
  if remove_permits.positive? && remove_permits > permits_available
29
- raise 'Error: can not remove enough permits right not'
27
+ raise 'can not remove enough permits right not'
30
28
  end
31
29
  set_permit_count!(new_permit_count)
32
30
  end
@@ -34,7 +32,7 @@ module QuackConcurrency
34
32
  end
35
33
 
36
34
  def set_permit_count!(new_permit_count)
37
- raise 'Error: permit_count invalid' if new_permit_count < 1
35
+ raise "'permit_count' invalid" if new_permit_count < 1
38
36
  @mutex.synchronize do
39
37
  new_permits = new_permit_count - @permit_count
40
38
  if new_permits.positive?
@@ -49,7 +47,7 @@ module QuackConcurrency
49
47
 
50
48
  def release
51
49
  @mutex.synchronize do
52
- raise 'No pemit to release.' if @permits_used == 0
50
+ raise 'no pemit to release' if @permits_used == 0
53
51
  @permits_used -= 1
54
52
  @condition_variable.signal if permit_available?
55
53
  end
@@ -1,12 +1,8 @@
1
- # Author: Rob Fors
2
- # Revision Date: 20180102
3
-
4
1
  module QuackConcurrency
5
2
  class Waiter
6
3
 
7
4
  def initialize(duck_types: {})
8
- queue_class = duck_types[:queue] || Queue
9
- @queue = queue_class.new
5
+ @queue = Queue.new(duck_types: duck_types)
10
6
  end
11
7
 
12
8
  def resume(value = nil)
@@ -0,0 +1,51 @@
1
+ require 'quack_concurrency'
2
+
3
+ RSpec.describe QuackConcurrency::Queue do
4
+
5
+ describe "pop" do
6
+
7
+ context "when called when queue empty" do
8
+ it "should wait" do
9
+ $test = []
10
+ queue = QuackConcurrency::Queue.new
11
+ thread = Thread.new do
12
+ queue.pop
13
+ $test << 1
14
+ sleep 1
15
+ queue.push
16
+ end
17
+ sleep 1
18
+ queue.push
19
+ queue.pop
20
+ $test << 2
21
+ thread.join
22
+ expect($test).to eql [1, 2]
23
+ end
24
+ end
25
+
26
+ context "when called when queue not empty" do
27
+ it "should immediately return" do
28
+ $test = []
29
+ queue = QuackConcurrency::Queue.new
30
+ queue.push
31
+ thread = Thread.new do
32
+ queue.pop
33
+ $test << 1
34
+ end
35
+ sleep 1
36
+ $test << 2
37
+ thread.join
38
+ expect($test).to eql [1, 2]
39
+ end
40
+ end
41
+
42
+ context "when called" do
43
+ it "should return value of the push" do
44
+ queue = QuackConcurrency::Queue.new
45
+ queue.push 1
46
+ expect(queue.pop).to eql 1
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,71 @@
1
+ require 'quack_concurrency'
2
+
3
+ RSpec.describe QuackConcurrency::ReentrantMutex do
4
+
5
+ describe "lock" do
6
+
7
+ context "when called for first time" do
8
+ it "should not raise error" do
9
+ mutex = QuackConcurrency::ReentrantMutex.new
10
+ expect { mutex.lock }.not_to raise_error
11
+ end
12
+ end
13
+
14
+ context "when called the second time" do
15
+ it "should not raise error" do
16
+ mutex = QuackConcurrency::ReentrantMutex.new
17
+ mutex.lock
18
+ expect { mutex.lock }.not_to raise_error
19
+ end
20
+ end
21
+
22
+ context "when called on non owning thread" do
23
+ it "should wait" do
24
+ $test = []
25
+ mutex = QuackConcurrency::ReentrantMutex.new
26
+ thread = Thread.new do
27
+ sleep 1
28
+ mutex.lock
29
+ $test << 2
30
+ end
31
+ mutex.lock
32
+ sleep 2
33
+ $test << 1
34
+ mutex.unlock
35
+ thread.join
36
+ expect($test).to eql [1, 2]
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ describe "unlock" do
43
+
44
+ context "when called after one lock" do
45
+ it "should not raise error" do
46
+ mutex = QuackConcurrency::ReentrantMutex.new
47
+ mutex.lock
48
+ expect { mutex.unlock }.not_to raise_error
49
+ end
50
+ end
51
+
52
+ context "when called after two locks" do
53
+ it "should not raise error" do
54
+ mutex = QuackConcurrency::ReentrantMutex.new
55
+ mutex.lock
56
+ mutex.lock
57
+ expect { mutex.unlock }.not_to raise_error
58
+ end
59
+ end
60
+
61
+ context "when called twice after only one lock" do
62
+ it "should raise error" do
63
+ mutex = QuackConcurrency::ReentrantMutex.new
64
+ mutex.lock
65
+ mutex.unlock
66
+ expect { mutex.unlock }.to raise_error(RuntimeError)
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,27 @@
1
+ require 'quack_concurrency'
2
+
3
+ RSpec.describe QuackConcurrency::Semaphore do
4
+
5
+ describe "acquire" do
6
+
7
+ context "when no permits are available" do
8
+ it "should wait" do
9
+ $test = []
10
+ semaphore = QuackConcurrency::Semaphore.new(2)
11
+ thread = Thread.new do
12
+ sleep 1
13
+ semaphore.acquire
14
+ $test << 2
15
+ end
16
+ semaphore.acquire
17
+ semaphore.acquire
18
+ sleep 2
19
+ $test << 1
20
+ semaphore.release
21
+ thread.join
22
+ expect($test).to eql [1, 2]
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,100 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
16
+ RSpec.configure do |config|
17
+ # rspec-expectations config goes here. You can use an alternate
18
+ # assertion/expectation library such as wrong or the stdlib/minitest
19
+ # assertions if you prefer.
20
+ config.expect_with :rspec do |expectations|
21
+ # This option will default to `true` in RSpec 4. It makes the `description`
22
+ # and `failure_message` of custom matchers include text for helper methods
23
+ # defined using `chain`, e.g.:
24
+ # be_bigger_than(2).and_smaller_than(4).description
25
+ # # => "be bigger than 2 and smaller than 4"
26
+ # ...rather than:
27
+ # # => "be bigger than 2"
28
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
29
+ end
30
+
31
+ # rspec-mocks config goes here. You can use an alternate test double
32
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
33
+ config.mock_with :rspec do |mocks|
34
+ # Prevents you from mocking or stubbing a method that does not exist on
35
+ # a real object. This is generally recommended, and will default to
36
+ # `true` in RSpec 4.
37
+ mocks.verify_partial_doubles = true
38
+ end
39
+
40
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
41
+ # have no way to turn it off -- the option exists only for backwards
42
+ # compatibility in RSpec 3). It causes shared context metadata to be
43
+ # inherited by the metadata hash of host groups and examples, rather than
44
+ # triggering implicit auto-inclusion in groups with matching metadata.
45
+ config.shared_context_metadata_behavior = :apply_to_host_groups
46
+
47
+ # The settings below are suggested to provide a good initial experience
48
+ # with RSpec, but feel free to customize to your heart's content.
49
+ =begin
50
+ # This allows you to limit a spec run to individual examples or groups
51
+ # you care about by tagging them with `:focus` metadata. When nothing
52
+ # is tagged with `:focus`, all examples get run. RSpec also provides
53
+ # aliases for `it`, `describe`, and `context` that include `:focus`
54
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
55
+ config.filter_run_when_matching :focus
56
+
57
+ # Allows RSpec to persist some state between runs in order to support
58
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
59
+ # you configure your source control system to ignore this file.
60
+ config.example_status_persistence_file_path = "spec/examples.txt"
61
+
62
+ # Limits the available syntax to the non-monkey patched syntax that is
63
+ # recommended. For more details, see:
64
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
65
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
66
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
67
+ config.disable_monkey_patching!
68
+
69
+ # This setting enables warnings. It's recommended, but in some cases may
70
+ # be too noisy due to issues in dependencies.
71
+ config.warnings = true
72
+
73
+ # Many RSpec users commonly either run the entire suite or an individual
74
+ # file, and it's useful to allow more verbose output when running an
75
+ # individual spec file.
76
+ if config.files_to_run.one?
77
+ # Use the documentation formatter for detailed output,
78
+ # unless a formatter has already been configured
79
+ # (e.g. via a command-line flag).
80
+ config.default_formatter = "doc"
81
+ end
82
+
83
+ # Print the 10 slowest examples and example groups at the
84
+ # end of the spec run, to help surface which specs are running
85
+ # particularly slow.
86
+ config.profile_examples = 10
87
+
88
+ # Run specs in random order to surface order dependencies. If you find an
89
+ # order dependency and want to debug it, you can fix the order by providing
90
+ # the seed, which is printed after each run.
91
+ # --seed 1234
92
+ config.order = :random
93
+
94
+ # Seed global randomization in this process using the `--seed` CLI option.
95
+ # Setting this allows you to use `--seed` to deterministically reproduce
96
+ # test failures related to randomization by passing the same `--seed` value
97
+ # as the one that triggered the failure.
98
+ Kernel.srand config.seed
99
+ =end
100
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quack_concurrency
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.2.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-02-25 00:00:00.000000000 Z
11
+ date: 2018-04-14 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Offers concurrency tools that could also be found in the Concurrent Ruby
14
14
  project. However, all these tools will also accept duck types to allow core classes
@@ -21,12 +21,15 @@ files:
21
21
  - LICENSE
22
22
  - README.md
23
23
  - lib/quack_concurrency.rb
24
- - lib/quack_concurrency/condition_variable.rb
25
24
  - lib/quack_concurrency/future.rb
25
+ - lib/quack_concurrency/queue.rb
26
26
  - lib/quack_concurrency/reentrant_mutex.rb
27
27
  - lib/quack_concurrency/semaphore.rb
28
28
  - lib/quack_concurrency/waiter.rb
29
- - test/test.rb
29
+ - spec/queue_spec.rb
30
+ - spec/reentrant_mutex_spec.rb
31
+ - spec/semaphore_spec.rb
32
+ - spec/spec_helper.rb
30
33
  homepage: https://github.com/robfors/quack_concurrency
31
34
  licenses:
32
35
  - MIT
@@ -1,34 +0,0 @@
1
- # Author: Rob Fors
2
- # Revision Date: 20180102
3
-
4
- module QuackConcurrency
5
- class ConditionVariable
6
-
7
- def initialize(duck_types: {})
8
- mutex_class = duck_types[:mutex] || Mutex
9
- queue_class = duck_types[:queue] || Queue
10
- @mutex = mutex_class.new
11
- @queue = queue_class.new
12
- end
13
-
14
- def signal
15
- @mutex.synchronize do
16
- @queue.push(nil) unless @queue.num_waiting == 0
17
- end
18
- end
19
-
20
- def broadcast
21
- @mutex.synchronize do
22
- @queue.push(nil) until @queue.num_waiting == 0
23
- end
24
- end
25
-
26
- def wait(mutex)
27
- mutex.unlock
28
- @queue.pop
29
- mutex.lock
30
- nil
31
- end
32
-
33
- end
34
- end
data/test/test.rb DELETED
@@ -1,97 +0,0 @@
1
- require 'pry'
2
-
3
- require_relative "../lib/quack_concurrency.rb"
4
-
5
- Thread.abort_on_exception = true
6
-
7
-
8
-
9
- puts 'test ConditionVariable'
10
- m = Mutex.new
11
- c = QuackConcurrency::ConditionVariable.new
12
-
13
- t = []
14
- 3.times do
15
- t << Thread.new do
16
- m.synchronize do
17
- c.wait(m)
18
- print '.'
19
- end
20
- end
21
- end
22
-
23
- 4.times do
24
- sleep 1
25
- c.signal
26
- end
27
-
28
- t.each(&:join)
29
- puts
30
-
31
-
32
- puts 'test ConditionVariable'
33
- w = QuackConcurrency::Waiter.new
34
-
35
- t = []
36
- 3.times do
37
- t << Thread.new do
38
- w.wait
39
- print '.'
40
- end
41
- end
42
-
43
- 4.times do
44
- sleep 1
45
- w.resume
46
- end
47
-
48
- t.each(&:join)
49
- puts
50
-
51
-
52
- puts 'test ReentrantMutex'
53
- r = QuackConcurrency::ReentrantMutex.new
54
-
55
- t = []
56
- 3.times do
57
- t << Thread.new do
58
- r.lock
59
- r.lock
60
- r.lock
61
- sleep 1
62
- r.unlock
63
- r.unlock
64
- r.unlock
65
- begin
66
- r.unlock
67
- rescue
68
- else
69
- raise
70
- end
71
- print '.'
72
- end
73
- end
74
-
75
- t.each(&:join)
76
- puts
77
-
78
-
79
- puts 'test Semaphore'
80
- s = QuackConcurrency::Semaphore.new(2)
81
-
82
- t = []
83
- 4.times do
84
- t << Thread.new do
85
- s.acquire
86
- print '.'
87
- sleep 1
88
- s.release
89
- end
90
- end
91
-
92
- t.each(&:join)
93
- puts
94
-
95
- exit
96
- binding.pry
97
- binding.pry