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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +284 -0
  4. data/lib/concurrent-edge.rb +11 -0
  5. data/lib/concurrent/actor.rb +98 -0
  6. data/lib/concurrent/actor/behaviour.rb +143 -0
  7. data/lib/concurrent/actor/behaviour/abstract.rb +51 -0
  8. data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
  9. data/lib/concurrent/actor/behaviour/buffer.rb +56 -0
  10. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
  11. data/lib/concurrent/actor/behaviour/executes_context.rb +17 -0
  12. data/lib/concurrent/actor/behaviour/linking.rb +83 -0
  13. data/lib/concurrent/actor/behaviour/pausing.rb +123 -0
  14. data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
  15. data/lib/concurrent/actor/behaviour/sets_results.rb +37 -0
  16. data/lib/concurrent/actor/behaviour/supervising.rb +39 -0
  17. data/lib/concurrent/actor/behaviour/terminates_children.rb +14 -0
  18. data/lib/concurrent/actor/behaviour/termination.rb +74 -0
  19. data/lib/concurrent/actor/context.rb +167 -0
  20. data/lib/concurrent/actor/core.rb +220 -0
  21. data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
  22. data/lib/concurrent/actor/envelope.rb +41 -0
  23. data/lib/concurrent/actor/errors.rb +27 -0
  24. data/lib/concurrent/actor/internal_delegations.rb +59 -0
  25. data/lib/concurrent/actor/public_delegations.rb +40 -0
  26. data/lib/concurrent/actor/reference.rb +106 -0
  27. data/lib/concurrent/actor/root.rb +37 -0
  28. data/lib/concurrent/actor/type_check.rb +48 -0
  29. data/lib/concurrent/actor/utils.rb +10 -0
  30. data/lib/concurrent/actor/utils/ad_hoc.rb +27 -0
  31. data/lib/concurrent/actor/utils/balancer.rb +43 -0
  32. data/lib/concurrent/actor/utils/broadcast.rb +52 -0
  33. data/lib/concurrent/actor/utils/pool.rb +54 -0
  34. data/lib/concurrent/agent.rb +289 -0
  35. data/lib/concurrent/channel.rb +6 -0
  36. data/lib/concurrent/channel/blocking_ring_buffer.rb +82 -0
  37. data/lib/concurrent/channel/buffered_channel.rb +87 -0
  38. data/lib/concurrent/channel/channel.rb +19 -0
  39. data/lib/concurrent/channel/ring_buffer.rb +65 -0
  40. data/lib/concurrent/channel/unbuffered_channel.rb +39 -0
  41. data/lib/concurrent/channel/waitable_list.rb +48 -0
  42. data/lib/concurrent/edge/atomic_markable_reference.rb +184 -0
  43. data/lib/concurrent/edge/future.rb +1226 -0
  44. data/lib/concurrent/edge/lock_free_stack.rb +85 -0
  45. 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