Skip to content

Commit 6c6982c

Browse files
committed
Reimplement subclasses with a weak linked list
This is similar to the CRuby subclasses list except for two key features: * We use weak references instead of pointer values; the GC vacates references for us. * The linked list structure is immutable and concurrency-safe. This impl is thread-safe and lock-free due to the immutable linked list structure. Adding a new class creates a new head node and atomically reassigns it into the class. Removing a class finds that element and vacates its reference. Replacing a class first removes the old and then adds the new. Traversing is a matter of walking the chain and omitting vacated references. Periodically, the list must be rebuilt without dead references. This is hardcoded currently to be when the list contains more than 25% vacated references. Adding a class is an O(1) operation. Removal, replacement, and traversal are amortized O(n). This structure is also lighter-weight than either the original ConcurrentWeakHashMap or any implementation of WeakHashMap provided by the JDK, plus it has no lock overhead and very little synchronization overhead.
1 parent 09bcd0d commit 6c6982c

File tree

1 file changed

+127
-48
lines changed

1 file changed

+127
-48
lines changed

core/src/main/java/org/jruby/RubyClass.java

Lines changed: 127 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@
4848
import static org.objectweb.asm.Opcodes.ACC_VARARGS;
4949

5050
import java.io.IOException;
51+
import java.lang.ref.WeakReference;
5152
import java.lang.reflect.Constructor;
5253
import java.lang.reflect.InvocationTargetException;
5354
import java.lang.reflect.Method;
5455
import java.lang.reflect.Modifier;
5556
import java.util.*;
57+
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
5658
import java.util.stream.Collectors;
5759

5860
import org.jruby.anno.JRubyClass;
@@ -93,7 +95,6 @@
9395
import org.jruby.util.Loader;
9496
import org.jruby.util.OneShotClassLoader;
9597
import org.jruby.util.StringSupport;
96-
import org.jruby.util.WeakIdentityHashMap;
9798
import org.jruby.util.log.Logger;
9899
import org.jruby.util.log.LoggerFactory;
99100
import org.objectweb.asm.AnnotationVisitor;
@@ -1060,8 +1061,8 @@ public final Collection<RubyClass> subclasses() {
10601061
}
10611062

10621063
public Collection<RubyClass> subclasses(boolean includeDescendants) {
1063-
Map<RubyClass, Object> subclasses = this.subclasses;
1064-
if (subclasses != null) {
1064+
SubclassNode subclassNode = this.subclassNode;
1065+
if (subclassNode != null) {
10651066
Collection<RubyClass> mine = new ArrayList<>();
10661067
subclassesInner(mine, includeDescendants);
10671068

@@ -1071,30 +1072,94 @@ public Collection<RubyClass> subclasses(boolean includeDescendants) {
10711072
}
10721073

10731074
private void subclassesInner(Collection<RubyClass> mine, boolean includeDescendants) {
1074-
Map<RubyClass, Object> subclasses = this.subclasses;
1075-
if (subclasses != null) {
1076-
Set<RubyClass> keys = subclasses.keySet();
1077-
mine.addAll(keys);
1078-
if (includeDescendants) {
1079-
for (RubyClass klass: keys) {
1080-
klass.subclassesInner(mine, includeDescendants);
1075+
SubclassNode subclassNode = this.subclassNode;
1076+
if (subclassNode != null) {
1077+
int clearedCount = 0;
1078+
while (subclassNode != null) {
1079+
RubyClass klass = subclassNode.ref.get();
1080+
subclassNode = subclassNode.next;
1081+
1082+
if (klass == null) {
1083+
clearedCount++;
1084+
continue;
10811085
}
1086+
1087+
mine.add(klass);
1088+
1089+
if (includeDescendants) klass.subclassesInner(mine, includeDescendants);
1090+
}
1091+
int newSize = mine.size();
1092+
1093+
// tidy up if more than 25% cleared references
1094+
if ((double) clearedCount / newSize > 0.25) {
1095+
cleanSubclasses();
10821096
}
10831097
}
10841098
}
10851099

10861100
private void concreteSubclasses(RubyArray<RubyClass> subs) {
1087-
Map<RubyClass, Object> subclasses = this.subclasses;
1088-
if (subclasses != null) {
1089-
subclasses.forEach((klass, $) -> {
1101+
SubclassNode subclassNode = this.subclassNode;
1102+
if (subclassNode != null) {
1103+
int clearedCount = 0;
1104+
while (subclassNode != null) {
1105+
RubyClass klass = subclassNode.ref.get();
1106+
subclassNode = subclassNode.next;
1107+
1108+
if (klass == null) {
1109+
clearedCount++;
1110+
continue;
1111+
}
1112+
10901113
if (!klass.isSingleton()) {
10911114
if (klass.isIncluded() || klass.isPrepended()) {
10921115
klass.concreteSubclasses(subs);
10931116
} else {
10941117
subs.append(klass);
10951118
}
10961119
}
1097-
});
1120+
}
1121+
int newSize = subs.size();
1122+
subclassEstimate = newSize + 4;
1123+
1124+
// tidy up if more than 25% cleared references
1125+
if ((double) clearedCount / newSize > 0.25) {
1126+
cleanSubclasses();
1127+
}
1128+
}
1129+
}
1130+
1131+
private void cleanSubclasses() {
1132+
SubclassNode subclassNode = this.subclassNode;
1133+
SubclassNode newTop = rebuildSubclasses(subclassNode);
1134+
while (!SUBCLASS_UPDATER.compareAndSet(this, subclassNode, newTop)) {
1135+
subclassNode = this.subclassNode;
1136+
newTop = rebuildSubclasses(subclassNode);
1137+
}
1138+
}
1139+
1140+
private static SubclassNode rebuildSubclasses(SubclassNode subclassNode) {
1141+
SubclassNode newTop = null;
1142+
while (subclassNode != null) {
1143+
WeakReference<RubyClass> ref = subclassNode.ref;
1144+
RubyClass klass = ref.get();
1145+
subclassNode = subclassNode.next;
1146+
if (klass == null) continue;
1147+
newTop = new SubclassNode(ref, newTop);
1148+
}
1149+
return newTop;
1150+
}
1151+
1152+
// TODO: make into a Record
1153+
static class SubclassNode {
1154+
final SubclassNode next;
1155+
final WeakReference<RubyClass> ref;
1156+
SubclassNode(RubyClass klass, SubclassNode next) {
1157+
ref = new WeakReference<>(klass);
1158+
this.next = next;
1159+
}
1160+
SubclassNode(WeakReference<RubyClass> ref, SubclassNode next) {
1161+
this.ref = ref;
1162+
this.next = next;
10981163
}
10991164
}
11001165

@@ -1108,18 +1173,12 @@ private void concreteSubclasses(RubyArray<RubyClass> subs) {
11081173
* @param subclass The subclass to add
11091174
*/
11101175
public void addSubclass(RubyClass subclass) {
1111-
Map<RubyClass, Object> subclasses = this.subclasses;
1112-
if (subclasses == null) {
1113-
// check again
1114-
synchronized (this) {
1115-
subclasses = this.subclasses;
1116-
if (subclasses == null) {
1117-
this.subclasses = subclasses = Collections.synchronizedMap(new WeakHashMap<>(4));
1118-
}
1119-
}
1176+
SubclassNode subclassNode = this.subclassNode;
1177+
SubclassNode newNode = new SubclassNode(subclass, subclassNode);
1178+
while (!SUBCLASS_UPDATER.compareAndSet(this, subclassNode, newNode)) {
1179+
subclassNode = this.subclassNode;
1180+
newNode = new SubclassNode(subclass, subclassNode);
11201181
}
1121-
1122-
subclasses.put(subclass, NEVER);
11231182
}
11241183

11251184
/**
@@ -1128,10 +1187,16 @@ public void addSubclass(RubyClass subclass) {
11281187
* @param subclass The subclass to remove
11291188
*/
11301189
public void removeSubclass(RubyClass subclass) {
1131-
Map<RubyClass, Object> subclasses = this.subclasses;
1132-
if (subclasses == null) return;
1133-
1134-
subclasses.remove(subclass);
1190+
SubclassNode subclassNode = this.subclassNode;
1191+
while (subclassNode != null) {
1192+
WeakReference<RubyClass> ref = subclassNode.ref;
1193+
RubyClass klass = ref.get();
1194+
if (klass == subclass) {
1195+
ref.clear();
1196+
return;
1197+
}
1198+
subclassNode = subclassNode.next;
1199+
}
11351200
}
11361201

11371202
/**
@@ -1141,20 +1206,25 @@ public void removeSubclass(RubyClass subclass) {
11411206
* @param newSubclass The subclass to replace it with
11421207
*/
11431208
public void replaceSubclass(RubyClass subclass, RubyClass newSubclass) {
1144-
Map<RubyClass, Object> subclasses = this.subclasses;
1145-
if (subclasses == null) return;
1146-
1147-
subclasses.remove(subclass);
1148-
subclasses.put(newSubclass, NEVER);
1209+
removeSubclass(subclass);
1210+
addSubclass(newSubclass);
11491211
}
11501212

1213+
/**
1214+
* make this class and all subclasses sync
1215+
*/
11511216
@Override
11521217
public void becomeSynchronized() {
1153-
// make this class and all subclasses sync
11541218
super.becomeSynchronized();
1155-
Map<RubyClass, Object> subclasses = this.subclasses;
1156-
if (subclasses != null) {
1157-
for (RubyClass subclass : subclasses.keySet()) subclass.becomeSynchronized();
1219+
1220+
SubclassNode subclassNode = this.subclassNode;
1221+
while (subclassNode != null) {
1222+
WeakReference<RubyClass> ref = subclassNode.ref;
1223+
RubyClass klass = ref.get();
1224+
if (klass != null) {
1225+
klass.becomeSynchronized();
1226+
}
1227+
subclassNode = subclassNode.next;
11581228
}
11591229
}
11601230

@@ -1174,9 +1244,14 @@ public void becomeSynchronized() {
11741244
public void invalidateCacheDescendants() {
11751245
super.invalidateCacheDescendants();
11761246

1177-
Map<RubyClass, Object> subclasses = this.subclasses;
1178-
if (subclasses != null) {
1179-
for (RubyClass subclass : subclasses.keySet()) subclass.invalidateCacheDescendants();
1247+
SubclassNode subclassNode = this.subclassNode;
1248+
while (subclassNode != null) {
1249+
WeakReference<RubyClass> ref = subclassNode.ref;
1250+
RubyClass klass = ref.get();
1251+
if (klass != null) {
1252+
klass.invalidateCacheDescendants();
1253+
}
1254+
subclassNode = subclassNode.next;
11801255
}
11811256
}
11821257

@@ -1187,12 +1262,15 @@ public void addInvalidatorsAndFlush(List<Invalidator> invalidators) {
11871262
// if we're not at boot time, don't bother fully clearing caches
11881263
if (!runtime.isBootingCore()) cachedMethods.clear();
11891264

1190-
Map<RubyClass, Object> subclasses = this.subclasses;
1191-
// no subclasses, don't bother with lock and iteration
1192-
if (subclasses == null || subclasses.isEmpty()) return;
1193-
1194-
// cascade into subclasses
1195-
for (RubyClass subclass : subclasses.keySet()) subclass.addInvalidatorsAndFlush(invalidators);
1265+
SubclassNode subclassNode = this.subclassNode;
1266+
while (subclassNode != null) {
1267+
WeakReference<RubyClass> ref = subclassNode.ref;
1268+
RubyClass klass = ref.get();
1269+
if (klass != null) {
1270+
klass.addInvalidatorsAndFlush(invalidators);
1271+
}
1272+
subclassNode = subclassNode.next;
1273+
}
11961274
}
11971275

11981276
public final Ruby getClassRuntime() {
@@ -3087,7 +3165,8 @@ public IRubyObject invokeFrom(ThreadContext context, CallType callType, IRubyObj
30873165
protected final Ruby runtime;
30883166
private ObjectAllocator allocator; // the default allocator
30893167
protected ObjectMarshal marshal;
3090-
private volatile Map<RubyClass, Object> subclasses;
3168+
private volatile SubclassNode subclassNode;
3169+
private static final AtomicReferenceFieldUpdater SUBCLASS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(RubyClass.class, SubclassNode.class, "subclassNode");
30913170
private int subclassEstimate = -1;
30923171
public static final int CS_IDX_INITIALIZE = 0;
30933172
public enum CS_NAMES {

0 commit comments

Comments
 (0)