Skip to content

Instantly share code, notes, and snippets.

@headius
Last active November 27, 2024 00:03
Show Gist options
  • Save headius/30578ea0e26c9566462e70d963b2cd29 to your computer and use it in GitHub Desktop.
Save headius/30578ea0e26c9566462e70d963b2cd29 to your computer and use it in GitHub Desktop.
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");
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
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