Skip to content

Commit 343923d

Browse files
authored
Merge pull request #737 from ruby-concurrency/pitr-ch/atomic-markable-reference
Move AtomicMarkableReference out of Edge
2 parents 476f593 + 5e340b4 commit 343923d

File tree

7 files changed

+168
-189
lines changed

7 files changed

+168
-189
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ Thread-safe variables:
108108
* [ReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReadWriteLock.html) A lock that supports multiple readers but only one writer.
109109
* [ReentrantReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReentrantReadWriteLock.html) A read/write lock with reentrant and upgrade features.
110110
* [Semaphore](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Semaphore.html) A counting-based locking mechanism that uses permits.
111+
* [AtomicMarkableReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Atomic/AtomicMarkableReference.html)
111112

112113
### Edge Features
113114

@@ -129,7 +130,6 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
129130
Functionally equivalent to Go [channels](https://tour.golang.org/concurrency/2) with additional
130131
inspiration from Clojure [core.async](https://clojure.github.io/core.async/).
131132
* [LazyRegister](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LazyRegister.html)
132-
* [AtomicMarkableReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/AtomicMarkableReference.html)
133133
* [LockFreeLinkedSet](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/LockFreeLinkedSet.html)
134134
* [LockFreeStack](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LockFreeStack.html)
135135

lib/concurrent-edge.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
require 'concurrent/exchanger'
77
require 'concurrent/lazy_register'
88

9-
require 'concurrent/edge/atomic_markable_reference'
109
require 'concurrent/edge/lock_free_linked_set'
1110
require 'concurrent/edge/lock_free_queue'
1211
require 'concurrent/edge/lock_free_stack'

lib/concurrent.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require 'concurrent/executors'
88
require 'concurrent/synchronization'
99

10+
require 'concurrent/atomic/atomic_markable_reference'
1011
require 'concurrent/atomic/atomic_reference'
1112
require 'concurrent/agent'
1213
require 'concurrent/atom'
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
module Concurrent
2+
# An atomic reference which maintains an object reference along with a mark bit
3+
# that can be updated atomically.
4+
#
5+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicMarkableReference.html
6+
# java.util.concurrent.atomic.AtomicMarkableReference
7+
class AtomicMarkableReference < ::Concurrent::Synchronization::Object
8+
9+
private(*attr_atomic(:reference))
10+
11+
def initialize(value = nil, mark = false)
12+
super()
13+
self.reference = immutable_array(value, mark)
14+
end
15+
16+
# Atomically sets the value and mark to the given updated value and
17+
# mark given both:
18+
# - the current value == the expected value &&
19+
# - the current mark == the expected mark
20+
#
21+
# @param [Object] expected_val the expected value
22+
# @param [Object] new_val the new value
23+
# @param [Boolean] expected_mark the expected mark
24+
# @param [Boolean] new_mark the new mark
25+
#
26+
# @return [Boolean] `true` if successful. A `false` return indicates
27+
# that the actual value was not equal to the expected value or the
28+
# actual mark was not equal to the expected mark
29+
def compare_and_set(expected_val, new_val, expected_mark, new_mark)
30+
# Memoize a valid reference to the current AtomicReference for
31+
# later comparison.
32+
current = reference
33+
curr_val, curr_mark = current
34+
35+
# Ensure that that the expected marks match.
36+
return false unless expected_mark == curr_mark
37+
38+
if expected_val.is_a? Numeric
39+
# If the object is a numeric, we need to ensure we are comparing
40+
# the numerical values
41+
return false unless expected_val == curr_val
42+
else
43+
# Otherwise, we need to ensure we are comparing the object identity.
44+
# Theoretically, this could be incorrect if a user monkey-patched
45+
# `Object#equal?`, but they should know that they are playing with
46+
# fire at that point.
47+
return false unless expected_val.equal? curr_val
48+
end
49+
50+
prospect = immutable_array(new_val, new_mark)
51+
52+
compare_and_set_reference current, prospect
53+
end
54+
55+
alias_method :compare_and_swap, :compare_and_set
56+
57+
# Gets the current reference and marked values.
58+
#
59+
# @return [Array] the current reference and marked values
60+
def get
61+
reference
62+
end
63+
64+
# Gets the current value of the reference
65+
#
66+
# @return [Object] the current value of the reference
67+
def value
68+
reference[0]
69+
end
70+
71+
# Gets the current marked value
72+
#
73+
# @return [Boolean] the current marked value
74+
def mark
75+
reference[1]
76+
end
77+
78+
alias_method :marked?, :mark
79+
80+
# _Unconditionally_ sets to the given value of both the reference and
81+
# the mark.
82+
#
83+
# @param [Object] new_val the new value
84+
# @param [Boolean] new_mark the new mark
85+
#
86+
# @return [Array] both the new value and the new mark
87+
def set(new_val, new_mark)
88+
self.reference = immutable_array(new_val, new_mark)
89+
end
90+
91+
# Pass the current value and marked state to the given block, replacing it
92+
# with the block's results. May retry if the value changes during the
93+
# block's execution.
94+
#
95+
# @yield [Object] Calculate a new value and marked state for the atomic
96+
# reference using given (old) value and (old) marked
97+
# @yieldparam [Object] old_val the starting value of the atomic reference
98+
# @yieldparam [Boolean] old_mark the starting state of marked
99+
#
100+
# @return [Array] the new value and new mark
101+
def update
102+
loop do
103+
old_val, old_mark = reference
104+
new_val, new_mark = yield old_val, old_mark
105+
106+
if compare_and_set old_val, new_val, old_mark, new_mark
107+
return immutable_array(new_val, new_mark)
108+
end
109+
end
110+
end
111+
112+
# Pass the current value to the given block, replacing it
113+
# with the block's result. Raise an exception if the update
114+
# fails.
115+
#
116+
# @yield [Object] Calculate a new value and marked state for the atomic
117+
# reference using given (old) value and (old) marked
118+
# @yieldparam [Object] old_val the starting value of the atomic reference
119+
# @yieldparam [Boolean] old_mark the starting state of marked
120+
#
121+
# @return [Array] the new value and marked state
122+
#
123+
# @raise [Concurrent::ConcurrentUpdateError] if the update fails
124+
def try_update!
125+
old_val, old_mark = reference
126+
new_val, new_mark = yield old_val, old_mark
127+
128+
unless compare_and_set old_val, new_val, old_mark, new_mark
129+
fail ::Concurrent::ConcurrentUpdateError,
130+
'AtomicMarkableReference: Update failed due to race condition.',
131+
'Note: If you would like to guarantee an update, please use ' +
132+
'the `AtomicMarkableReference#update` method.'
133+
end
134+
135+
immutable_array(new_val, new_mark)
136+
end
137+
138+
# Pass the current value to the given block, replacing it with the
139+
# block's result. Simply return nil if update fails.
140+
#
141+
# @yield [Object] Calculate a new value and marked state for the atomic
142+
# reference using given (old) value and (old) marked
143+
# @yieldparam [Object] old_val the starting value of the atomic reference
144+
# @yieldparam [Boolean] old_mark the starting state of marked
145+
#
146+
# @return [Array] the new value and marked state, or nil if
147+
# the update failed
148+
def try_update
149+
old_val, old_mark = reference
150+
new_val, new_mark = yield old_val, old_mark
151+
152+
return unless compare_and_set old_val, new_val, old_mark, new_mark
153+
154+
immutable_array(new_val, new_mark)
155+
end
156+
157+
private
158+
159+
def immutable_array(*args)
160+
args.freeze
161+
end
162+
end
163+
end

lib/concurrent/edge/atomic_markable_reference.rb

Lines changed: 0 additions & 184 deletions
This file was deleted.

lib/concurrent/edge/lock_free_linked_set/node.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require 'concurrent/edge/atomic_markable_reference'
1+
require 'concurrent/atomic/atomic_markable_reference'
22

33
module Concurrent
44
module Edge
@@ -10,7 +10,7 @@ class Node < Synchronization::Object
1010

1111
def initialize(data = nil, successor = nil)
1212
super()
13-
@SuccessorReference = AtomicMarkableReference.new(successor || Tail.new)
13+
@SuccessorReference = AtomicMarkableReference.new(successor || Tail.new)
1414
@Data = data
1515
@Key = key_for data
1616
end

0 commit comments

Comments
 (0)