Skip to content

Commit b80bfee

Browse files
committed
AtomicMarkableReference: compare object identities if not Numeric type
Objects should always be compared by reference so we can ensure that we are referring to the *same* object, not simply one that maybe be considered logically equivalent by overriding `==`. This is, of course, unless it is a Numeric. When comparing something primitive like a number, we expect that the equality operator makes mathematical sense.
1 parent ca83c2a commit b80bfee

File tree

2 files changed

+37
-8
lines changed

2 files changed

+37
-8
lines changed

lib/concurrent/atomic/atomic_markable_reference.rb

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,30 @@ def compare_and_set(expected_val, new_val, expected_mark, new_mark)
3030
current = @Reference.get
3131
curr_val, curr_mark = current
3232

33-
# Ensure that that the expected values match.
34-
return false unless (expected_val == curr_val) &&
35-
(expected_mark == curr_mark)
33+
# Ensure that that the expected marks match.
34+
return false unless expected_mark == curr_mark
3635

37-
# In this case, it would be redundant to set the fields. Just
38-
# shortcircuit without wasting CPU time on CAS.
39-
return true if (new_val == curr_val) &&
40-
(new_mark == curr_mark)
36+
if expected_val.is_a? Numeric
37+
# If the object is a numeric, we need to ensure we are comparing
38+
# the numerical values
39+
return false unless expected_val == curr_val
40+
else
41+
# Otherwise, we need to ensure we are comparing the object identity.
42+
# Theoretically, this could be incorrect if a user monkey-patched
43+
# `Object#equal?`, but they should know that they are playing with
44+
# fire at that point.
45+
return false unless expected_val.equal? curr_val
46+
end
4147

4248
prospect = ImmutableArray[new_val, new_mark]
4349

44-
@Reference.compare_and_set current, prospect
50+
# If we guarantee internally that `current` will never be a Numeric, we
51+
# can skip a type check in `compare_and_set` and directly call
52+
# `_compare_and_set`. This is possible since we always internally wrap
53+
# the users `value` and `mark` in an ImmutableArray.
54+
@Reference._compare_and_set current, prospect
4555
end
56+
alias_method :compare_and_swap, :compare_and_set
4657

4758
# @!macro [attach] atomic_markable_reference_method_get
4859
#

spec/concurrent/atomic/atomic_markable_reference_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,22 @@
8383
expect(subject.compare_and_set(Complex(1, 2), Complex(1, 3), false, true))
8484
.to be_truthy, "CAS failed for (#{Complex(1, 2)}, false) => (0, false)"
8585
end
86+
87+
describe '#compare_and_set' do
88+
context 'objects have the same identity' do
89+
it 'is successful' do
90+
arr = [1, 2, 3]
91+
subject.set(arr, true)
92+
expect(subject.compare_and_set(arr, 1.2, true, false)).to be_truthy
93+
end
94+
end
95+
96+
context 'objects have the different identity' do
97+
it 'is not successful' do
98+
subject.set([1, 2, 3], true)
99+
expect(subject.compare_and_set([1, 2, 3], 1.2, true, false))
100+
.to be_falsey
101+
end
102+
end
103+
end
86104
end

0 commit comments

Comments
 (0)