quack_concurrency 0.0.1 → 0.2.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
  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