concurrent-ruby-edge 0.1.0.pre2
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +284 -0
- data/lib/concurrent-edge.rb +11 -0
- data/lib/concurrent/actor.rb +98 -0
- data/lib/concurrent/actor/behaviour.rb +143 -0
- data/lib/concurrent/actor/behaviour/abstract.rb +51 -0
- data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
- data/lib/concurrent/actor/behaviour/buffer.rb +56 -0
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
- data/lib/concurrent/actor/behaviour/executes_context.rb +17 -0
- data/lib/concurrent/actor/behaviour/linking.rb +83 -0
- data/lib/concurrent/actor/behaviour/pausing.rb +123 -0
- data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
- data/lib/concurrent/actor/behaviour/sets_results.rb +37 -0
- data/lib/concurrent/actor/behaviour/supervising.rb +39 -0
- data/lib/concurrent/actor/behaviour/terminates_children.rb +14 -0
- data/lib/concurrent/actor/behaviour/termination.rb +74 -0
- data/lib/concurrent/actor/context.rb +167 -0
- data/lib/concurrent/actor/core.rb +220 -0
- data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
- data/lib/concurrent/actor/envelope.rb +41 -0
- data/lib/concurrent/actor/errors.rb +27 -0
- data/lib/concurrent/actor/internal_delegations.rb +59 -0
- data/lib/concurrent/actor/public_delegations.rb +40 -0
- data/lib/concurrent/actor/reference.rb +106 -0
- data/lib/concurrent/actor/root.rb +37 -0
- data/lib/concurrent/actor/type_check.rb +48 -0
- data/lib/concurrent/actor/utils.rb +10 -0
- data/lib/concurrent/actor/utils/ad_hoc.rb +27 -0
- data/lib/concurrent/actor/utils/balancer.rb +43 -0
- data/lib/concurrent/actor/utils/broadcast.rb +52 -0
- data/lib/concurrent/actor/utils/pool.rb +54 -0
- data/lib/concurrent/agent.rb +289 -0
- data/lib/concurrent/channel.rb +6 -0
- data/lib/concurrent/channel/blocking_ring_buffer.rb +82 -0
- data/lib/concurrent/channel/buffered_channel.rb +87 -0
- data/lib/concurrent/channel/channel.rb +19 -0
- data/lib/concurrent/channel/ring_buffer.rb +65 -0
- data/lib/concurrent/channel/unbuffered_channel.rb +39 -0
- data/lib/concurrent/channel/waitable_list.rb +48 -0
- data/lib/concurrent/edge/atomic_markable_reference.rb +184 -0
- data/lib/concurrent/edge/future.rb +1226 -0
- data/lib/concurrent/edge/lock_free_stack.rb +85 -0
- metadata +110 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'concurrent/channel/waitable_list'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
module Channel
|
5
|
+
|
6
|
+
# @api Channel
|
7
|
+
# @!macro edge_warning
|
8
|
+
class BufferedChannel
|
9
|
+
|
10
|
+
def initialize(size)
|
11
|
+
@mutex = Mutex.new
|
12
|
+
@buffer_condition = ConditionVariable.new
|
13
|
+
|
14
|
+
@probe_set = WaitableList.new
|
15
|
+
@buffer = RingBuffer.new(size)
|
16
|
+
end
|
17
|
+
|
18
|
+
def probe_set_size
|
19
|
+
@probe_set.size
|
20
|
+
end
|
21
|
+
|
22
|
+
def buffer_queue_size
|
23
|
+
@mutex.synchronize { @buffer.count }
|
24
|
+
end
|
25
|
+
|
26
|
+
def push(value)
|
27
|
+
until set_probe_or_push_into_buffer(value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def pop
|
32
|
+
probe = Channel::Probe.new
|
33
|
+
select(probe)
|
34
|
+
probe.value
|
35
|
+
end
|
36
|
+
|
37
|
+
def select(probe)
|
38
|
+
@mutex.synchronize do
|
39
|
+
|
40
|
+
if @buffer.empty?
|
41
|
+
@probe_set.put(probe)
|
42
|
+
true
|
43
|
+
else
|
44
|
+
shift_buffer if probe.try_set([peek_buffer, self])
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove_probe(probe)
|
51
|
+
@probe_set.delete(probe)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def push_into_buffer(value)
|
57
|
+
@buffer_condition.wait(@mutex) while @buffer.full?
|
58
|
+
@buffer.offer value
|
59
|
+
@buffer_condition.broadcast
|
60
|
+
end
|
61
|
+
|
62
|
+
def peek_buffer
|
63
|
+
@buffer_condition.wait(@mutex) while @buffer.empty?
|
64
|
+
@buffer.peek
|
65
|
+
end
|
66
|
+
|
67
|
+
def shift_buffer
|
68
|
+
@buffer_condition.wait(@mutex) while @buffer.empty?
|
69
|
+
result = @buffer.poll
|
70
|
+
@buffer_condition.broadcast
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_probe_or_push_into_buffer(value)
|
75
|
+
@mutex.synchronize do
|
76
|
+
if @probe_set.empty?
|
77
|
+
push_into_buffer(value)
|
78
|
+
true
|
79
|
+
else
|
80
|
+
@probe_set.take.try_set([value, self])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'concurrent/ivar'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
# @api Channel
|
6
|
+
# @!macro edge_warning
|
7
|
+
module Channel
|
8
|
+
|
9
|
+
Probe = IVar
|
10
|
+
|
11
|
+
def self.select(*channels)
|
12
|
+
probe = Probe.new
|
13
|
+
channels.each { |channel| channel.select(probe) }
|
14
|
+
result = probe.value
|
15
|
+
channels.each { |channel| channel.remove_probe(probe) }
|
16
|
+
result
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Channel
|
3
|
+
|
4
|
+
# non-thread safe buffer
|
5
|
+
#
|
6
|
+
# @api Channel
|
7
|
+
# @!macro edge_warning
|
8
|
+
class RingBuffer
|
9
|
+
|
10
|
+
def initialize(capacity)
|
11
|
+
@buffer = Array.new(capacity)
|
12
|
+
@first = @last = 0
|
13
|
+
@count = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
# @return [Integer] the capacity of the buffer
|
18
|
+
def capacity
|
19
|
+
@buffer.size
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Integer] the number of elements currently in the buffer
|
23
|
+
def count
|
24
|
+
@count
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Boolean] true if buffer is empty, false otherwise
|
28
|
+
def empty?
|
29
|
+
@count == 0
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Boolean] true if buffer is full, false otherwise
|
33
|
+
def full?
|
34
|
+
@count == capacity
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [Object] value
|
38
|
+
# @return [Boolean] true if value has been inserted, false otherwise
|
39
|
+
def offer(value)
|
40
|
+
return false if full?
|
41
|
+
|
42
|
+
@buffer[@last] = value
|
43
|
+
@last = (@last + 1) % @buffer.size
|
44
|
+
@count += 1
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Object] the first available value and removes it from the buffer. If buffer is empty returns nil
|
49
|
+
def poll
|
50
|
+
result = @buffer[@first]
|
51
|
+
@buffer[@first] = nil
|
52
|
+
@first = (@first + 1) % @buffer.size
|
53
|
+
@count -= 1
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Object] the first available value and without removing it from
|
58
|
+
# the buffer. If buffer is empty returns nil
|
59
|
+
def peek
|
60
|
+
@buffer[@first]
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'concurrent/channel/waitable_list'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
module Channel
|
5
|
+
|
6
|
+
# @api Channel
|
7
|
+
# @!macro edge_warning
|
8
|
+
class UnbufferedChannel
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@probe_set = WaitableList.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def probe_set_size
|
15
|
+
@probe_set.size
|
16
|
+
end
|
17
|
+
|
18
|
+
def push(value)
|
19
|
+
until @probe_set.take.try_set([value, self])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def pop
|
24
|
+
probe = Channel::Probe.new
|
25
|
+
select(probe)
|
26
|
+
probe.value
|
27
|
+
end
|
28
|
+
|
29
|
+
def select(probe)
|
30
|
+
@probe_set.put(probe)
|
31
|
+
end
|
32
|
+
|
33
|
+
def remove_probe(probe)
|
34
|
+
@probe_set.delete(probe)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'concurrent/synchronization'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
module Channel
|
5
|
+
|
6
|
+
# @api Channel
|
7
|
+
# @!macro edge_warning
|
8
|
+
class WaitableList < Synchronization::Object
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super()
|
12
|
+
synchronize { ns_initialize }
|
13
|
+
end
|
14
|
+
|
15
|
+
def size
|
16
|
+
synchronize { @list.size }
|
17
|
+
end
|
18
|
+
|
19
|
+
def empty?
|
20
|
+
synchronize { @list.empty? }
|
21
|
+
end
|
22
|
+
|
23
|
+
def put(value)
|
24
|
+
synchronize do
|
25
|
+
@list << value
|
26
|
+
ns_signal
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete(value)
|
31
|
+
synchronize { @list.delete(value) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def take
|
35
|
+
synchronize do
|
36
|
+
ns_wait_until { !@list.empty? }
|
37
|
+
@list.shift
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def ns_initialize
|
44
|
+
@list = []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Edge
|
3
|
+
|
4
|
+
# @!macro [attach] atomic_markable_reference
|
5
|
+
#
|
6
|
+
# An atomic reference which maintains an object reference along with a mark bit
|
7
|
+
# that can be updated atomically.
|
8
|
+
#
|
9
|
+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicMarkableReference.html java.util.concurrent.atomic.AtomicMarkableReference
|
10
|
+
#
|
11
|
+
# @api Edge
|
12
|
+
class AtomicMarkableReference < ::Concurrent::Synchronization::Object
|
13
|
+
|
14
|
+
# @!macro [attach] atomic_markable_reference_method_initialize
|
15
|
+
def initialize(value = nil, mark = false)
|
16
|
+
super()
|
17
|
+
@Reference = AtomicReference.new ImmutableArray[value, mark]
|
18
|
+
ensure_ivar_visibility!
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!macro [attach] atomic_markable_reference_method_compare_and_set
|
22
|
+
#
|
23
|
+
# Atomically sets the value and mark to the given updated value and
|
24
|
+
# mark given both:
|
25
|
+
# - the current value == the expected value &&
|
26
|
+
# - the current mark == the expected mark
|
27
|
+
#
|
28
|
+
# @param [Object] expected_val the expected value
|
29
|
+
# @param [Object] new_val the new value
|
30
|
+
# @param [Boolean] expected_mark the expected mark
|
31
|
+
# @param [Boolean] new_mark the new mark
|
32
|
+
#
|
33
|
+
# @return [Boolean] `true` if successful. A `false` return indicates
|
34
|
+
# that the actual value was not equal to the expected value or the
|
35
|
+
# actual mark was not equal to the expected mark
|
36
|
+
def compare_and_set(expected_val, new_val, expected_mark, new_mark)
|
37
|
+
# Memoize a valid reference to the current AtomicReference for
|
38
|
+
# later comparison.
|
39
|
+
current = @Reference.get
|
40
|
+
curr_val, curr_mark = current
|
41
|
+
|
42
|
+
# Ensure that that the expected marks match.
|
43
|
+
return false unless expected_mark == curr_mark
|
44
|
+
|
45
|
+
if expected_val.is_a? Numeric
|
46
|
+
# If the object is a numeric, we need to ensure we are comparing
|
47
|
+
# the numerical values
|
48
|
+
return false unless expected_val == curr_val
|
49
|
+
else
|
50
|
+
# Otherwise, we need to ensure we are comparing the object identity.
|
51
|
+
# Theoretically, this could be incorrect if a user monkey-patched
|
52
|
+
# `Object#equal?`, but they should know that they are playing with
|
53
|
+
# fire at that point.
|
54
|
+
return false unless expected_val.equal? curr_val
|
55
|
+
end
|
56
|
+
|
57
|
+
prospect = ImmutableArray[new_val, new_mark]
|
58
|
+
|
59
|
+
@Reference.compare_and_set current, prospect
|
60
|
+
end
|
61
|
+
alias_method :compare_and_swap, :compare_and_set
|
62
|
+
|
63
|
+
# @!macro [attach] atomic_markable_reference_method_get
|
64
|
+
#
|
65
|
+
# Gets the current reference and marked values.
|
66
|
+
#
|
67
|
+
# @return [ImmutableArray] the current reference and marked values
|
68
|
+
def get
|
69
|
+
@Reference.get
|
70
|
+
end
|
71
|
+
|
72
|
+
# @!macro [attach] atomic_markable_reference_method_value
|
73
|
+
#
|
74
|
+
# Gets the current value of the reference
|
75
|
+
#
|
76
|
+
# @return [Object] the current value of the reference
|
77
|
+
def value
|
78
|
+
@Reference.get[0]
|
79
|
+
end
|
80
|
+
|
81
|
+
# @!macro [attach] atomic_markable_reference_method_mark
|
82
|
+
#
|
83
|
+
# Gets the current marked value
|
84
|
+
#
|
85
|
+
# @return [Boolean] the current marked value
|
86
|
+
def mark
|
87
|
+
@Reference.get[1]
|
88
|
+
end
|
89
|
+
alias_method :marked?, :mark
|
90
|
+
|
91
|
+
# @!macro [attach] atomic_markable_reference_method_set
|
92
|
+
#
|
93
|
+
# _Unconditionally_ sets to the given value of both the reference and
|
94
|
+
# the mark.
|
95
|
+
#
|
96
|
+
# @param [Object] new_val the new value
|
97
|
+
# @param [Boolean] new_mark the new mark
|
98
|
+
#
|
99
|
+
# @return [ImmutableArray] both the new value and the new mark
|
100
|
+
def set(new_val, new_mark)
|
101
|
+
@Reference.set ImmutableArray[new_val, new_mark]
|
102
|
+
end
|
103
|
+
|
104
|
+
# @!macro [attach] atomic_markable_reference_method_update
|
105
|
+
#
|
106
|
+
# Pass the current value and marked state to the given block, replacing it
|
107
|
+
# with the block's results. May retry if the value changes during the
|
108
|
+
# block's execution.
|
109
|
+
#
|
110
|
+
# @yield [Object] Calculate a new value and marked state for the atomic
|
111
|
+
# reference using given (old) value and (old) marked
|
112
|
+
# @yieldparam [Object] old_val the starting value of the atomic reference
|
113
|
+
# @yieldparam [Boolean] old_mark the starting state of marked
|
114
|
+
#
|
115
|
+
# @return [ImmutableArray] the new value and new mark
|
116
|
+
def update
|
117
|
+
loop do
|
118
|
+
old_val, old_mark = @Reference.get
|
119
|
+
new_val, new_mark = yield old_val, old_mark
|
120
|
+
|
121
|
+
if compare_and_set old_val, new_val, old_mark, new_mark
|
122
|
+
return ImmutableArray[new_val, new_mark]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# @!macro [attach] atomic_markable_reference_method_try_update!
|
128
|
+
#
|
129
|
+
# Pass the current value to the given block, replacing it
|
130
|
+
# with the block's result. Raise an exception if the update
|
131
|
+
# fails.
|
132
|
+
#
|
133
|
+
# @yield [Object] Calculate a new value and marked state for the atomic
|
134
|
+
# reference using given (old) value and (old) marked
|
135
|
+
# @yieldparam [Object] old_val the starting value of the atomic reference
|
136
|
+
# @yieldparam [Boolean] old_mark the starting state of marked
|
137
|
+
#
|
138
|
+
# @return [ImmutableArray] the new value and marked state
|
139
|
+
#
|
140
|
+
# @raise [Concurrent::ConcurrentUpdateError] if the update fails
|
141
|
+
def try_update!
|
142
|
+
old_val, old_mark = @Reference.get
|
143
|
+
new_val, new_mark = yield old_val, old_mark
|
144
|
+
|
145
|
+
unless compare_and_set old_val, new_val, old_mark, new_mark
|
146
|
+
fail ::Concurrent::ConcurrentUpdateError,
|
147
|
+
'AtomicMarkableReference: Update failed due to race condition.',
|
148
|
+
'Note: If you would like to guarantee an update, please use ' \
|
149
|
+
'the `AtomicMarkableReference#update` method.'
|
150
|
+
end
|
151
|
+
|
152
|
+
ImmutableArray[new_val, new_mark]
|
153
|
+
end
|
154
|
+
|
155
|
+
# @!macro [attach] atomic_markable_reference_method_try_update
|
156
|
+
#
|
157
|
+
# Pass the current value to the given block, replacing it with the
|
158
|
+
# block's result. Simply return nil if update fails.
|
159
|
+
#
|
160
|
+
# @yield [Object] Calculate a new value and marked state for the atomic
|
161
|
+
# reference using given (old) value and (old) marked
|
162
|
+
# @yieldparam [Object] old_val the starting value of the atomic reference
|
163
|
+
# @yieldparam [Boolean] old_mark the starting state of marked
|
164
|
+
#
|
165
|
+
# @return [ImmutableArray] the new value and marked state, or nil if
|
166
|
+
# the update failed
|
167
|
+
def try_update
|
168
|
+
old_val, old_mark = @Reference.get
|
169
|
+
new_val, new_mark = yield old_val, old_mark
|
170
|
+
|
171
|
+
return unless compare_and_set old_val, new_val, old_mark, new_mark
|
172
|
+
|
173
|
+
ImmutableArray[new_val, new_mark]
|
174
|
+
end
|
175
|
+
|
176
|
+
# Internal/private ImmutableArray for representing pairs
|
177
|
+
class ImmutableArray < Array
|
178
|
+
def self.new(*args)
|
179
|
+
super(*args).freeze
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|