concurrent-ruby-edge 0.1.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
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