Skip to content

Commit 239ef50

Browse files
committedJan 22, 2018
Avoid race when deduplicating frozen strings. Fixes #4970.
The logic here goes like this: * Attempt to lookup the dedup'ed string. * If found, return it. * Try to insert new dedup'ed string. * If nobody beat us to it, return it. * If someone beat us to it and the reference is still good, return it. * If someone beat us to it and the reference is vacated, atomically replace it or retrieve any updates that beat us. * Keep trying until we succeed in updating or get back a populated reference. Previously, there could be a race if two threads try to dedup the same string at the same time. Only one would get in the cache, but the n+1 threads might not return the already-cached value.
1 parent cce752c commit 239ef50

File tree

1 file changed

+21
-1
lines changed

1 file changed

+21
-1
lines changed
 

‎core/src/main/java/org/jruby/Ruby.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -4679,11 +4679,31 @@ public RubyString freezeAndDedupString(RubyString string) {
46794679
RubyString deduped;
46804680

46814681
if (dedupedRef == null || (deduped = dedupedRef.get()) == null) {
4682+
// Never use incoming value as key
46824683
deduped = string.strDup(this);
46834684
deduped.setFrozen(true);
4684-
dedupMap.putIfAbsent(string, new WeakReference<RubyString>(deduped));
4685+
4686+
final WeakReference<RubyString> weakref = new WeakReference<>(deduped);
4687+
4688+
// try to insert new
4689+
dedupedRef = dedupMap.computeIfAbsent(deduped, key -> weakref);
4690+
if (dedupedRef == null) return deduped;
4691+
4692+
// entry exists, return result if not vacated
4693+
RubyString unduped = dedupedRef.get();
4694+
if (unduped != null) return unduped;
4695+
4696+
// ref is there but vacated, try to replace it until we have a result
4697+
while (true) {
4698+
dedupedRef = dedupMap.computeIfPresent(string, (key, old) -> old.get() == null ? weakref : old);
4699+
4700+
// return result if not vacated
4701+
unduped = dedupedRef.get();
4702+
if (unduped != null) return unduped;
4703+
}
46854704
} else if (deduped.getEncoding() != string.getEncoding()) {
46864705
// if encodings don't match, new string loses; can't dedup
4706+
// FIXME: This may never happen, if we are properly considering encoding in RubyString.hashCode
46874707
deduped = string.strDup(this);
46884708
deduped.setFrozen(true);
46894709
}

0 commit comments

Comments
 (0)
Please sign in to comment.