Skip to content

Commit 28f90ab

Browse files
committed
AtomicMarkableReference: Add initial Ruby version
This commit adds a pure Ruby implementation of an AtomicMarkableReference. An AtomicMarkableReference is much like an AtomicReference with that added property that it can be, you guessed it, marked either true or false. They are useful for implementing more advanced data sctructures such as recursive split-ordered hash tables, etc. Most of this code is shamelessly stolen from AtomicReference, and could likely be condensed. Seeing as these are my first commits, I do not feel comfortable embarking on such refactoring. So please feel free to rip this code in half and have your will with it.
1 parent 1e2828c commit 28f90ab

File tree

5 files changed

+156
-7
lines changed

5 files changed

+156
-7
lines changed

lib/concurrent.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
require 'concurrent/executors'
1212
require 'concurrent/utilities'
1313

14+
require 'concurrent/atomic/atomic_markable_reference'
1415
require 'concurrent/atomic/atomic_reference'
1516
require 'concurrent/agent'
1617
require 'concurrent/async'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
require 'concurrent/atomic_reference/mutex_markable_atomic'
2+
3+
# @!macro atomic_markable_reference
4+
module Concurrent
5+
class AtomicMarkableReference < MutexAtomicMarkableReference; end
6+
end
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
require 'thread'
2+
require 'concurrent/atomic_reference/numeric_cas_wrapper'
3+
4+
module Concurrent
5+
# @!macro atomic_markable_reference
6+
class MutexAtomicMarkableReference
7+
include AtomicNumericCompareAndSetWrapper
8+
9+
# @!macro [attach] atomic_markable_reference_method_initialize
10+
def initialize(value = nil, marked = false)
11+
@mutex = Mutex.new
12+
@value, @marked = value, marked
13+
end
14+
15+
# @!macro [attach] atomic_markable_reference_method_get
16+
#
17+
# Gets the current reference and marked values.
18+
#
19+
# @return [Array] the current reference and marked values
20+
def get
21+
@mutex.synchronize { [@value, @marked] }
22+
end
23+
24+
# @!macro [attach] atomic_markable_reference_method_value
25+
#
26+
# Gets the current value of the reference
27+
#
28+
# @return [Object] the current value of the reference
29+
def value
30+
@mutex.synchronize { @value }
31+
end
32+
33+
# @!macro [attach] atomic_markable_reference_method_marked?
34+
#
35+
# Gets the current marked value
36+
#
37+
# @return [Boolean] the current marked value
38+
def marked?
39+
@mutex.synchronize { @marked }
40+
end
41+
alias_method :marked, :marked?
42+
43+
# @!macro [attach] atomic_markable_reference_method_set
44+
#
45+
# Sets to the given value of both the reference and the mark.
46+
#
47+
# @param [Object] new_val the new value
48+
# @param [Boolean] new_mark the new mark
49+
#
50+
# @return [Array] both the new value and the new mark
51+
def set(new_val, new_mark)
52+
@mutex.synchronize do
53+
@value, @marked = new_val, new_mark
54+
55+
[@value, @marked]
56+
end
57+
end
58+
59+
# @!macro [attach] atomic_markable_reference_method_update
60+
#
61+
# Pass the current value and marked state to the given block, replacing it
62+
# with the block's results. May retry if the value changes during the
63+
# block's execution.
64+
#
65+
# @yield [Object] Calculate a new value and marked state for the atomic
66+
# reference using given (old) value and (old) marked
67+
# @yieldparam [Object] old_val the starting value of the atomic reference
68+
# @yieldparam [Boolean] old_mark the starting state of marked
69+
#
70+
# @return [Array] the new value and new mark
71+
def update
72+
loop do
73+
old_val, old_mark = value, marked?
74+
new_val, new_mark = yield old_val, old_mark
75+
76+
if compare_and_set old_val, new_val, old_mark, new_mark
77+
return [new_val, new_mark]
78+
end
79+
end
80+
end
81+
82+
# @!macro [attach] atomic_markable_reference_method_try_update
83+
#
84+
# Pass the current value to the given block, replacing it
85+
# with the block's result. Raise an exception if the update
86+
# fails.
87+
#
88+
# @yield [Object] Calculate a new value and marked state for the atomic
89+
# reference using given (old) value and (old) marked
90+
# @yieldparam [Object] old_val the starting value of the atomic reference
91+
# @yieldparam [Boolean] old_mark the starting state of marked
92+
#
93+
# @return [Array] the new value and marked state
94+
#
95+
# @raise [Concurrent::ConcurrentUpdateError] if the update fails
96+
def try_update
97+
old_val, old_mark = value, marked?
98+
new_val, new_mark = yield old_val, old_mark
99+
100+
unless compare_and_set old_val, new_val, old_mark, new_mark
101+
err = [ConcurrentUpdateError, 'Update failed']
102+
err << ConcurrentUpdateError::CONC_UP_ERR_BACKTRACE if $VERBOSE
103+
104+
fail(*err)
105+
end
106+
107+
[new_val, new_mark]
108+
end
109+
110+
# @!macro [attach] atomic_markable_reference_method_compare_and_set
111+
#
112+
# Atomically sets the value and mark to the given updated value and
113+
# mark given both:
114+
# - the current value == the expected value &&
115+
# - the current mark == the expected mark
116+
#
117+
# @param [Object] old_val the expected value
118+
# @param [Object] new_val the new value
119+
# @param [Boolean] old_mark the expected mark
120+
# @param [Boolean] new_mark the new mark
121+
#
122+
# @return [Boolean] `true` if successful. A `false` return indicates
123+
# that the actual value was not equal to the expected value or the
124+
# actual mark was not equal to the expected mark
125+
def _compare_and_set(old_val, new_val, old_mark, new_mark) #:nodoc:
126+
return false unless @mutex.try_lock
127+
128+
begin
129+
return false unless @value.equal?(old_val) && @marked == old_mark
130+
131+
@value, @marked = new_val, new_mark
132+
ensure
133+
@mutex.unlock
134+
end
135+
136+
true
137+
end
138+
end
139+
end

lib/concurrent/atomic_reference/numeric_cas_wrapper.rb

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@ module Concurrent
44
module AtomicNumericCompareAndSetWrapper
55

66
# @!macro atomic_reference_method_compare_and_set
7-
def compare_and_set(old_value, new_value)
7+
def compare_and_set(*args)
8+
old_value = args[0]
9+
810
if old_value.kind_of? Numeric
911
while true
10-
old = get
12+
old = value
1113

1214
return false unless old.kind_of? Numeric
13-
1415
return false unless old == old_value
1516

16-
result = _compare_and_set(old, new_value)
17+
args[0] = old
18+
result = _compare_and_set(*args)
19+
1720
return result if result
1821
end
1922
else
20-
_compare_and_set(old_value, new_value)
23+
_compare_and_set(*args)
2124
end
2225
end
2326
alias_method :compare_and_swap, :compare_and_set

spec/concurrent/atomic/atomic_markable_reference_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313
res = atomic.set(1001, true)
1414

1515
expect(atomic.value).to eq 1001
16-
expect(atomic.marked).to eq true
16+
expect(atomic.marked?).to eq true
1717
expect(res).to eq [1001, true]
1818
end
1919

2020
specify :test_update do
2121
res = atomic.update { |v, m| [v + 1, !m] }
2222

2323
expect(atomic.marked?).to eq false
24-
expect(atomic.get_reference).to eq 1001
24+
expect(atomic.value).to eq 1001
2525
expect(res).to eq [1001, false]
2626
end
2727

0 commit comments

Comments
 (0)