quack_concurrency 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/quack_concurrency/condition_variable.rb +34 -0
- data/lib/quack_concurrency/future.rb +51 -0
- data/lib/quack_concurrency/reentrant_mutex.rb +85 -0
- data/lib/quack_concurrency/semaphore.rb +90 -0
- data/lib/quack_concurrency/waiter.rb +21 -0
- data/lib/quack_concurrency.rb +10 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dc94fc7f4e6591c7475601d36f579e27f92d3489
|
4
|
+
data.tar.gz: c799eea3980d05c7615bb188f20691fde758839f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5acd18b4ddcce70586e1029deab493435c73d077934f3ce0df32de5616ba5ea466526bb9f3c8c58a7c538a024ed75d2c33aa776e1624a0f6453547f2c2133d25
|
7
|
+
data.tar.gz: b4586e26fdf8fc425ebb70dca328bc90e9eb88a229fd44982433710554b36cf61c6773ef7f43745d1e23a0d40a64680f6e351921db6a5457711b723eff8f5fe5
|
@@ -0,0 +1,34 @@
|
|
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
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Author: Rob Fors
|
2
|
+
# Revision Date: 20180102
|
3
|
+
|
4
|
+
module QuackConcurrency
|
5
|
+
class Future
|
6
|
+
|
7
|
+
class Canceled < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(duck_types: {})
|
11
|
+
mutex_class = duck_types[:mutex] || Mutex
|
12
|
+
condition_variable_class = duck_types[:condition_variable] || ::ConditionVariable
|
13
|
+
@mutex = mutex_class.new
|
14
|
+
@condition_variable = condition_variable_class.new
|
15
|
+
@value = nil
|
16
|
+
@value_set = false
|
17
|
+
@complete = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def set(new_value = nil)
|
21
|
+
@mutex.synchronize do
|
22
|
+
raise if @complete
|
23
|
+
@value_set = true
|
24
|
+
@complete = true
|
25
|
+
@value = new_value
|
26
|
+
@condition_variable.broadcast
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def get
|
31
|
+
@mutex.synchronize do
|
32
|
+
@condition_variable.wait(@mutex) unless complete?
|
33
|
+
raise 'should not get here' unless complete?
|
34
|
+
raise Canceled unless @value_set
|
35
|
+
@value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def cancel
|
40
|
+
@mutex.synchronize do
|
41
|
+
raise if @complete
|
42
|
+
@complete = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def complete?
|
47
|
+
@complete
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# Author: Rob Fors
|
2
|
+
# Revision Date: 20180102
|
3
|
+
|
4
|
+
# based off https://en.wikipedia.org/wiki/Reentrant_mutex
|
5
|
+
|
6
|
+
module QuackConcurrency
|
7
|
+
class ReentrantMutex
|
8
|
+
|
9
|
+
def initialize(duck_types: {})
|
10
|
+
mutex_class = duck_types[:mutex] || Mutex
|
11
|
+
condition_variable_class = duck_types[:condition_variable] || ::ConditionVariable
|
12
|
+
@mutex = mutex_class.new
|
13
|
+
@condition_variable = condition_variable_class.new
|
14
|
+
@owner = nil
|
15
|
+
@lock_depth = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def lock
|
19
|
+
@mutex.synchronize do
|
20
|
+
@condition_variable.wait(@mutex) if @owner && @owner != caller
|
21
|
+
@owner = caller
|
22
|
+
@lock_depth += 1
|
23
|
+
end
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def locked?
|
28
|
+
!!@owner
|
29
|
+
end
|
30
|
+
|
31
|
+
def locked_out?
|
32
|
+
@mutex.synchronize { locked? && @owner != caller }
|
33
|
+
end
|
34
|
+
|
35
|
+
def owned?
|
36
|
+
@owner == caller
|
37
|
+
end
|
38
|
+
|
39
|
+
def synchronize
|
40
|
+
lock
|
41
|
+
start_depth = @lock_depth
|
42
|
+
start_owner = @owner
|
43
|
+
result = yield
|
44
|
+
result
|
45
|
+
ensure
|
46
|
+
unless @lock_depth == start_depth && @owner == start_owner
|
47
|
+
raise 'Could not unlock mutex as its state has been modified.'
|
48
|
+
end
|
49
|
+
unlock
|
50
|
+
end
|
51
|
+
|
52
|
+
def try_lock
|
53
|
+
@mutex.synchronize do
|
54
|
+
if @owner && @owner != caller
|
55
|
+
return false
|
56
|
+
else
|
57
|
+
@owner = caller
|
58
|
+
@lock_depth += 1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def unlock
|
65
|
+
@mutex.synchronize do
|
66
|
+
raise "Not locked." if @lock_depth == 0
|
67
|
+
raise "Not the owner." unless @owner == Thread.current
|
68
|
+
@lock_depth -= 1
|
69
|
+
if @lock_depth == 0
|
70
|
+
@owner = nil
|
71
|
+
@condition_variable.signal
|
72
|
+
end
|
73
|
+
end
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def caller
|
80
|
+
Thread.current
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Author: Rob Fors
|
2
|
+
# Revision Date: 20180102
|
3
|
+
|
4
|
+
module QuackConcurrency
|
5
|
+
class Semaphore
|
6
|
+
|
7
|
+
attr_reader :permit_count
|
8
|
+
|
9
|
+
def initialize(permit_count = 1, duck_types: {})
|
10
|
+
raise 'Error: permit_count invalid' if permit_count < 1
|
11
|
+
@permit_count = permit_count
|
12
|
+
@permits_used = 0
|
13
|
+
@mutex = ReentrantMutex.new(duck_types: duck_types)
|
14
|
+
@condition_variable = ConditionVariable.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def acquire
|
18
|
+
@mutex.synchronize do
|
19
|
+
@condition_variable.wait(@mutex) unless permit_available?
|
20
|
+
@permits_used += 1
|
21
|
+
end
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_permit_count(new_permit_count)
|
26
|
+
@mutex.synchronize do
|
27
|
+
remove_permits = @permit_count - new_permit_count
|
28
|
+
if remove_permits.positive? && remove_permits > permits_available
|
29
|
+
raise 'Error: can not remove enough permits right not'
|
30
|
+
end
|
31
|
+
set_permit_count!(new_permit_count)
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_permit_count!(new_permit_count)
|
37
|
+
raise 'Error: permit_count invalid' if new_permit_count < 1
|
38
|
+
@mutex.synchronize do
|
39
|
+
new_permits = new_permit_count - @permit_count
|
40
|
+
if new_permits.positive?
|
41
|
+
new_permits.times { add_permit }
|
42
|
+
else
|
43
|
+
remove_permits = -new_permits
|
44
|
+
remove_permits.times { remove_permit! }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def release
|
51
|
+
@mutex.synchronize do
|
52
|
+
raise 'No pemit to release.' if @permits_used == 0
|
53
|
+
@permits_used -= 1
|
54
|
+
@condition_variable.signal if permit_available?
|
55
|
+
end
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
# how to handle if yield raises an error but has temporarily released its permit?
|
60
|
+
#def synchronize
|
61
|
+
# acquire
|
62
|
+
# begin
|
63
|
+
# yield
|
64
|
+
# ensure
|
65
|
+
# release
|
66
|
+
# end
|
67
|
+
#end
|
68
|
+
|
69
|
+
def permits_available
|
70
|
+
@mutex.synchronize { @permit_count - @permits_used }
|
71
|
+
end
|
72
|
+
|
73
|
+
def permit_available?
|
74
|
+
@mutex.synchronize { permits_available >= 1 }
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def add_permit
|
80
|
+
@permit_count += 1
|
81
|
+
@condition_variable.signal
|
82
|
+
end
|
83
|
+
|
84
|
+
def remove_permit!
|
85
|
+
@permit_count -= 1
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Author: Rob Fors
|
2
|
+
# Revision Date: 20180102
|
3
|
+
|
4
|
+
module QuackConcurrency
|
5
|
+
class Waiter
|
6
|
+
|
7
|
+
def initialize(duck_types: {})
|
8
|
+
queue_class = duck_types[:queue] || Queue
|
9
|
+
@queue = queue_class.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def resume(value = nil)
|
13
|
+
@queue << value
|
14
|
+
end
|
15
|
+
|
16
|
+
def wait
|
17
|
+
@queue.pop
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'thread'
|
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"
|
8
|
+
|
9
|
+
module QuackConcurrency
|
10
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: quack_concurrency
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rob Fors
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-02-25 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Offers concurrency tools that accept duck types to allow core classes
|
14
|
+
to behave as desired.
|
15
|
+
email: mail@robfors.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/quack_concurrency.rb
|
21
|
+
- lib/quack_concurrency/condition_variable.rb
|
22
|
+
- lib/quack_concurrency/future.rb
|
23
|
+
- lib/quack_concurrency/reentrant_mutex.rb
|
24
|
+
- lib/quack_concurrency/semaphore.rb
|
25
|
+
- lib/quack_concurrency/waiter.rb
|
26
|
+
homepage: https://github.com/robfors/quack_concurrency
|
27
|
+
licenses:
|
28
|
+
- MIT
|
29
|
+
metadata: {}
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 2.4.8
|
47
|
+
signing_key:
|
48
|
+
specification_version: 4
|
49
|
+
summary: Concurrency tools that accept duck types.
|
50
|
+
test_files: []
|