Last active
November 27, 2024 00:03
-
-
Save headius/30578ea0e26c9566462e70d963b2cd29 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/core/src/main/java/org/jruby/RubyClass.java b/core/src/main/java/org/jruby/RubyClass.java | |
index 1d6c3c631f..bd7314e387 100644 | |
--- a/core/src/main/java/org/jruby/RubyClass.java | |
+++ b/core/src/main/java/org/jruby/RubyClass.java | |
@@ -53,6 +53,8 @@ import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Modifier; | |
import java.util.*; | |
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; | |
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; | |
import java.util.stream.Collectors; | |
import org.jruby.anno.JRubyClass; | |
@@ -1019,14 +1021,9 @@ public class RubyClass extends RubyModule { | |
@JRubyMethod | |
public IRubyObject subclasses(ThreadContext context) { | |
- Map<RubyClass, Object> subclasses = this.subclasses; | |
- int sizeEstimate = subclasses == null ? 0 : subclasses.size(); | |
- | |
- if (sizeEstimate == 0) { | |
- return RubyArray.newEmptyArray(context.runtime); | |
- } | |
+ int subclassEstimate = this.subclassEstimate; | |
- RubyArray<RubyClass> subs = RubyArray.newArray(context.runtime, sizeEstimate); | |
+ RubyArray<RubyClass> subs = RubyArray.newArray(context.runtime, subclassEstimate == -1 ? 4 : subclassEstimate); | |
concreteSubclasses(subs); | |
@@ -1078,6 +1075,7 @@ public class RubyClass extends RubyModule { | |
private void subclassesInner(Collection<RubyClass> mine, boolean includeDescendants) { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses != null) { | |
+ subclassWalkEntry(); | |
Set<RubyClass> keys = subclasses.keySet(); | |
mine.addAll(keys); | |
if (includeDescendants) { | |
@@ -1085,12 +1083,14 @@ public class RubyClass extends RubyModule { | |
klass.subclassesInner(mine, includeDescendants); | |
} | |
} | |
+ subclassWalkExit(); | |
} | |
} | |
private void concreteSubclasses(RubyArray<RubyClass> subs) { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses != null) { | |
+ subclassWalkEntry(); | |
subclasses.forEach((klass, $) -> { | |
if (!klass.isSingleton()) { | |
if (klass.isIncluded() || klass.isPrepended()) { | |
@@ -1100,9 +1100,20 @@ public class RubyClass extends RubyModule { | |
} | |
} | |
}); | |
+ subclassWalkExit(); | |
+ subclassEstimate = subs.size(); | |
} | |
} | |
+ private void subclassWalkEntry() { | |
+ Thread currentThread = Thread.currentThread(); | |
+ while (!SUBCLASS_WALKING.compareAndSet(this, null, currentThread)) Thread.yield(); | |
+ } | |
+ | |
+ private void subclassWalkExit() { | |
+ SUBCLASS_WALKING.set(this, null); | |
+ } | |
+ | |
/** | |
* Add a new subclass to the weak set of subclasses. | |
* | |
@@ -1119,12 +1130,14 @@ public class RubyClass extends RubyModule { | |
synchronized (this) { | |
subclasses = this.subclasses; | |
if (subclasses == null) { | |
- this.subclasses = subclasses = Collections.synchronizedMap(new WeakHashMap<>(4)); | |
+ this.subclasses = subclasses = new WeakHashMap<>(4); | |
} | |
} | |
} | |
+ subclassWalkEntry(); | |
subclasses.put(subclass, NEVER); | |
+ subclassWalkExit(); | |
} | |
/** | |
@@ -1136,7 +1149,9 @@ public class RubyClass extends RubyModule { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses == null) return; | |
+ subclassWalkEntry(); | |
subclasses.remove(subclass); | |
+ subclassWalkExit(); | |
} | |
/** | |
@@ -1149,8 +1164,10 @@ public class RubyClass extends RubyModule { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses == null) return; | |
+ subclassWalkEntry(); | |
subclasses.remove(subclass); | |
subclasses.put(newSubclass, NEVER); | |
+ subclassWalkExit(); | |
} | |
@Override | |
@@ -1159,7 +1176,9 @@ public class RubyClass extends RubyModule { | |
super.becomeSynchronized(); | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses != null) { | |
+ subclassWalkEntry(); | |
for (RubyClass subclass : subclasses.keySet()) subclass.becomeSynchronized(); | |
+ subclassWalkExit(); | |
} | |
} | |
@@ -1181,7 +1200,9 @@ public class RubyClass extends RubyModule { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses != null) { | |
+ subclassWalkEntry(); | |
for (RubyClass subclass : subclasses.keySet()) subclass.invalidateCacheDescendants(); | |
+ subclassWalkExit(); | |
} | |
} | |
@@ -3093,6 +3114,9 @@ public class RubyClass extends RubyModule { | |
private ObjectAllocator allocator; // the default allocator | |
protected ObjectMarshal marshal; | |
private volatile Map<RubyClass, Object> subclasses; | |
+ private int subclassEstimate = -1; | |
+ private volatile Thread subclassWalking = null; | |
+ private static final AtomicReferenceFieldUpdater SUBCLASS_WALKING = AtomicReferenceFieldUpdater.newUpdater(RubyClass.class, Thread.class, "subclassWalking"); | |
public static final int CS_IDX_INITIALIZE = 0; | |
public enum CS_NAMES { | |
INITIALIZE("initialize"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/core/src/main/java/org/jruby/RubyClass.java b/core/src/main/java/org/jruby/RubyClass.java | |
index 8c8c93e8bf..f9e05d4df8 100644 | |
--- a/core/src/main/java/org/jruby/RubyClass.java | |
+++ b/core/src/main/java/org/jruby/RubyClass.java | |
@@ -1108,18 +1108,17 @@ public class RubyClass extends RubyModule { | |
* @param subclass The subclass to add | |
*/ | |
public void addSubclass(RubyClass subclass) { | |
- Map<RubyClass, Object> subclasses = this.subclasses; | |
- if (subclasses == null) { | |
- // check again | |
- synchronized (this) { | |
- subclasses = this.subclasses; | |
- if (subclasses == null) { | |
- this.subclasses = subclasses = Collections.synchronizedMap(new WeakHashMap<>(4)); | |
- } | |
+ synchronized (this) { | |
+ Map<RubyClass, Object> subclasses = this.subclasses; | |
+ if (subclasses == null) { | |
+ subclasses = new WeakHashMap<>(4); | |
+ } else { | |
+ subclasses = new WeakHashMap<>(subclasses); | |
} | |
- } | |
+ subclasses.put(subclass, NEVER); | |
- subclasses.put(subclass, NEVER); | |
+ this.subclasses = subclasses; | |
+ } | |
} | |
/** | |
@@ -1131,7 +1130,11 @@ public class RubyClass extends RubyModule { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses == null) return; | |
- subclasses.remove(subclass); | |
+ synchronized (this) { | |
+ subclasses = new WeakHashMap<>(subclasses); | |
+ subclasses.remove(subclass); | |
+ this.subclasses = subclasses; | |
+ } | |
} | |
/** | |
@@ -1144,8 +1147,12 @@ public class RubyClass extends RubyModule { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses == null) return; | |
- subclasses.remove(subclass); | |
- subclasses.put(newSubclass, NEVER); | |
+ synchronized (this) { | |
+ subclasses = new WeakHashMap<>(subclasses); | |
+ subclasses.remove(subclass); | |
+ subclasses.put(newSubclass, NEVER); | |
+ this.subclasses = subclasses; | |
+ } | |
} | |
@Override |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/core/src/main/java/org/jruby/RubyClass.java b/core/src/main/java/org/jruby/RubyClass.java | |
index 1d6c3c631f..04ad363edc 100644 | |
--- a/core/src/main/java/org/jruby/RubyClass.java | |
+++ b/core/src/main/java/org/jruby/RubyClass.java | |
@@ -53,6 +53,7 @@ import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Modifier; | |
import java.util.*; | |
+import java.util.concurrent.locks.ReentrantReadWriteLock; | |
import java.util.stream.Collectors; | |
import org.jruby.anno.JRubyClass; | |
@@ -1019,12 +1020,7 @@ public class RubyClass extends RubyModule { | |
@JRubyMethod | |
public IRubyObject subclasses(ThreadContext context) { | |
- Map<RubyClass, Object> subclasses = this.subclasses; | |
- int sizeEstimate = subclasses == null ? 0 : subclasses.size(); | |
- | |
- if (sizeEstimate == 0) { | |
- return RubyArray.newEmptyArray(context.runtime); | |
- } | |
+ int sizeEstimate = subclassesEstimate == -1 ? 4 : subclassesEstimate; | |
RubyArray<RubyClass> subs = RubyArray.newArray(context.runtime, sizeEstimate); | |
@@ -1078,12 +1074,17 @@ public class RubyClass extends RubyModule { | |
private void subclassesInner(Collection<RubyClass> mine, boolean includeDescendants) { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses != null) { | |
- Set<RubyClass> keys = subclasses.keySet(); | |
- mine.addAll(keys); | |
- if (includeDescendants) { | |
- for (RubyClass klass: keys) { | |
- klass.subclassesInner(mine, includeDescendants); | |
+ subclassesLock.readLock().lock(); | |
+ try { | |
+ Set<RubyClass> keys = subclasses.keySet(); | |
+ mine.addAll(keys); | |
+ if (includeDescendants) { | |
+ for (RubyClass klass : keys) { | |
+ klass.subclassesInner(mine, includeDescendants); | |
+ } | |
} | |
+ } finally { | |
+ subclassesLock.readLock().unlock(); | |
} | |
} | |
} | |
@@ -1091,15 +1092,21 @@ public class RubyClass extends RubyModule { | |
private void concreteSubclasses(RubyArray<RubyClass> subs) { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses != null) { | |
- subclasses.forEach((klass, $) -> { | |
- if (!klass.isSingleton()) { | |
- if (klass.isIncluded() || klass.isPrepended()) { | |
- klass.concreteSubclasses(subs); | |
- } else { | |
- subs.append(klass); | |
+ subclassesLock.readLock().lock(); | |
+ try { | |
+ subclasses.forEach((klass, $) -> { | |
+ if (!klass.isSingleton()) { | |
+ if (klass.isIncluded() || klass.isPrepended()) { | |
+ klass.concreteSubclasses(subs); | |
+ } else { | |
+ subs.append(klass); | |
+ } | |
} | |
- } | |
- }); | |
+ }); | |
+ subclassesEstimate = subs.size(); | |
+ } finally { | |
+ subclassesLock.readLock().unlock(); | |
+ } | |
} | |
} | |
@@ -1119,12 +1126,17 @@ public class RubyClass extends RubyModule { | |
synchronized (this) { | |
subclasses = this.subclasses; | |
if (subclasses == null) { | |
- this.subclasses = subclasses = Collections.synchronizedMap(new WeakHashMap<>(4)); | |
+ this.subclasses = subclasses = new WeakHashMap<>(4); | |
} | |
} | |
} | |
- subclasses.put(subclass, NEVER); | |
+ subclassesLock.writeLock().lock(); | |
+ try { | |
+ subclasses.put(subclass, NEVER); | |
+ } finally { | |
+ subclassesLock.writeLock().unlock(); | |
+ } | |
} | |
/** | |
@@ -1136,7 +1148,12 @@ public class RubyClass extends RubyModule { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses == null) return; | |
- subclasses.remove(subclass); | |
+ subclassesLock.writeLock().lock(); | |
+ try { | |
+ subclasses.remove(subclass); | |
+ } finally { | |
+ subclassesLock.writeLock().unlock(); | |
+ } | |
} | |
/** | |
@@ -1149,8 +1166,13 @@ public class RubyClass extends RubyModule { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses == null) return; | |
- subclasses.remove(subclass); | |
- subclasses.put(newSubclass, NEVER); | |
+ subclassesLock.writeLock().lock(); | |
+ try { | |
+ subclasses.remove(subclass); | |
+ subclasses.put(newSubclass, NEVER); | |
+ } finally { | |
+ subclassesLock.writeLock().unlock(); | |
+ } | |
} | |
@Override | |
@@ -1159,7 +1181,12 @@ public class RubyClass extends RubyModule { | |
super.becomeSynchronized(); | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses != null) { | |
- for (RubyClass subclass : subclasses.keySet()) subclass.becomeSynchronized(); | |
+ subclassesLock.readLock().lock(); | |
+ try { | |
+ subclasses.forEach((klass, $) -> klass.becomeSynchronized()); | |
+ } finally { | |
+ subclassesLock.readLock().unlock(); | |
+ } | |
} | |
} | |
@@ -1181,7 +1208,12 @@ public class RubyClass extends RubyModule { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
if (subclasses != null) { | |
- for (RubyClass subclass : subclasses.keySet()) subclass.invalidateCacheDescendants(); | |
+ subclassesLock.readLock().lock(); | |
+ try { | |
+ subclasses.forEach((klass, $) -> klass.invalidateCacheDescendants()); | |
+ } finally { | |
+ subclassesLock.readLock().unlock(); | |
+ } | |
} | |
} | |
@@ -1194,10 +1226,15 @@ public class RubyClass extends RubyModule { | |
Map<RubyClass, Object> subclasses = this.subclasses; | |
// no subclasses, don't bother with lock and iteration | |
- if (subclasses == null || subclasses.isEmpty()) return; | |
+ if (subclasses == null) return; | |
// cascade into subclasses | |
- for (RubyClass subclass : subclasses.keySet()) subclass.addInvalidatorsAndFlush(invalidators); | |
+ subclassesLock.readLock().lock(); | |
+ try { | |
+ subclasses.forEach((klass, $) -> klass.addInvalidatorsAndFlush(invalidators)); | |
+ } finally { | |
+ subclassesLock.readLock().unlock(); | |
+ } | |
} | |
public final Ruby getClassRuntime() { | |
@@ -3093,6 +3130,8 @@ public class RubyClass extends RubyModule { | |
private ObjectAllocator allocator; // the default allocator | |
protected ObjectMarshal marshal; | |
private volatile Map<RubyClass, Object> subclasses; | |
+ private int subclassesEstimate = -1; | |
+ private final ReentrantReadWriteLock subclassesLock = new ReentrantReadWriteLock(); | |
public static final int CS_IDX_INITIALIZE = 0; | |
public enum CS_NAMES { | |
INITIALIZE("initialize"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment