Skip to content

Commit 2534dda

Browse files
committed
Clean up some locking around hierarchy modification
There are too many locks in this logic, leading to deadlock potential as reported in #8846. This commit tries to centralize most hierarchy-modifying or hierarchy-walking locks to using the global runtime.getHierarchyLock, and localizes parts of the same code that are only modifying the current class. In the future we should consider the thread-safety of the various structures that make up RubyModule/RubyClass, since currently most of those structures are themselves thread-safe or lock-guarded, so much of the extra locking here may be unnecessary. We also have opportunities to improve some "compute if absent" scenarios and refine the way we lock for hierarchy walking to be more localized. Fixes #8846
1 parent 88dcc38 commit 2534dda

2 files changed

Lines changed: 36 additions & 29 deletions

File tree

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1308,7 +1308,9 @@ public void becomeSynchronized() {
13081308
public void invalidateCacheDescendants() {
13091309
super.invalidateCacheDescendants();
13101310

1311-
getSubclassesForRead().forEachClass(RubyClass::invalidateCacheDescendants);
1311+
synchronized (getRuntime().getHierarchyLock()) {
1312+
getSubclassesForRead().forEachClass(RubyClass::invalidateCacheDescendants);
1313+
}
13121314
}
13131315

13141316
void addInvalidatorsAndFlush(InvalidatorList invalidators) {

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

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,7 +1115,7 @@ public void prependModule(RubyModule module) {
11151115
// Make sure the module we include does not already exist
11161116
checkForCyclicPrepend(module);
11171117

1118-
synchronized (this) {
1118+
synchronized (getRuntime().getHierarchyLock()) {
11191119
if (hasModuleInPrepends(module)) {
11201120
invalidateCacheDescendants();
11211121
return;
@@ -1124,25 +1124,23 @@ public void prependModule(RubyModule module) {
11241124
doPrependModule(module);
11251125

11261126
if (this.isModule()) {
1127-
synchronized (getRuntime().getHierarchyLock()) {
1128-
includingHierarchies.forEachClass(new RubyClass.BiConsumerIgnoresSecond<RubyClass>() {
1129-
boolean doPrepend = true;
1130-
1131-
public void accept(RubyClass includeClass) {
1132-
RubyClass checkClass = includeClass;
1133-
while (checkClass != null) {
1134-
if (checkClass instanceof IncludedModule && checkClass.getOrigin() == module) {
1135-
doPrepend = false;
1136-
}
1137-
checkClass = checkClass.superClass;
1138-
}
1127+
includingHierarchies.forEachClass(new RubyClass.BiConsumerIgnoresSecond<RubyClass>() {
1128+
boolean doPrepend = true;
11391129

1140-
if (doPrepend) {
1141-
includeClass.doPrependModule(module);
1130+
public void accept(RubyClass includeClass) {
1131+
RubyClass checkClass = includeClass;
1132+
while (checkClass != null) {
1133+
if (checkClass instanceof IncludedModule && checkClass.getOrigin() == module) {
1134+
doPrepend = false;
11421135
}
1136+
checkClass = checkClass.superClass;
11431137
}
1144-
});
1145-
}
1138+
1139+
if (doPrepend) {
1140+
includeClass.doPrependModule(module);
1141+
}
1142+
}
1143+
});
11461144
}
11471145

11481146
invalidateCoreClasses();
@@ -1166,7 +1164,7 @@ public void prependModule(IRubyObject arg) {
11661164
*
11671165
* @param arg The module to include
11681166
*/
1169-
public synchronized void includeModule(IRubyObject arg) {
1167+
public void includeModule(IRubyObject arg) {
11701168
assert arg != null;
11711169

11721170
testFrozen("module");
@@ -1185,10 +1183,10 @@ public synchronized void includeModule(IRubyObject arg) {
11851183
// Make sure the module we include does not already exist
11861184
checkForCyclicInclude(module);
11871185

1188-
doIncludeModule(module);
1186+
synchronized (getRuntime().getHierarchyLock()) {
1187+
doIncludeModule(module);
11891188

1190-
if (this.isModule()) {
1191-
synchronized (getRuntime().getHierarchyLock()) {
1189+
if (this.isModule()) {
11921190
includingHierarchies.forEachClass(new RubyClass.BiConsumerIgnoresSecond<RubyClass>() {
11931191
boolean doInclude = true;
11941192

@@ -1207,11 +1205,11 @@ public void accept(RubyClass includeClass) {
12071205
}
12081206
});
12091207
}
1210-
}
12111208

1212-
invalidateCoreClasses();
1213-
invalidateCacheDescendants();
1214-
invalidateConstantCacheForModuleInclusion(module);
1209+
invalidateCoreClasses();
1210+
invalidateCacheDescendants();
1211+
invalidateConstantCacheForModuleInclusion(module);
1212+
}
12151213
}
12161214

12171215
public void defineAnnotatedMethod(Class clazz, String name) {
@@ -1530,6 +1528,9 @@ private void addRefinedMethodEntry(String id, DynamicMethod method) {
15301528
public final void addMethodInternal(String name, DynamicMethod method) {
15311529
synchronized (methodLocation.getMethodsForWrite()) {
15321530
putMethod(getRuntime(), name, method);
1531+
}
1532+
1533+
synchronized (getRuntime().getHierarchyLock()) {
15331534
invalidateCoreClasses();
15341535
invalidateCacheDescendants();
15351536
}
@@ -1574,7 +1575,9 @@ public void removeMethod(ThreadContext context, String id) {
15741575
if (method.isRefined()) {
15751576
methodsForWrite.put(id, new RefinedMarker(method.getImplementationClass(), method.getVisibility(), id));
15761577
}
1578+
}
15771579

1580+
synchronized (getRuntime().getHierarchyLock()) {
15781581
invalidateCoreClasses();
15791582
invalidateCacheDescendants();
15801583
}
@@ -2084,7 +2087,7 @@ public void addModuleFunction(String name, DynamicMethod method) {
20842087
/** rb_alias
20852088
*
20862089
*/
2087-
public synchronized void defineAlias(String name, String oldName) {
2090+
public void defineAlias(String name, String oldName) {
20882091
testFrozen("module");
20892092

20902093
Ruby runtime = getRuntime();
@@ -2105,8 +2108,10 @@ public synchronized void defineAlias(String name, String oldName) {
21052108
checkAliasFrameAccesses(runtime, oldName, name, entry.method);
21062109
putAlias(name, entry, oldName);
21072110

2108-
methodLocation.invalidateCoreClasses();
2109-
methodLocation.invalidateCacheDescendants();
2111+
synchronized (getRuntime().getHierarchyLock()) {
2112+
methodLocation.invalidateCoreClasses();
2113+
methodLocation.invalidateCacheDescendants();
2114+
}
21102115
}
21112116

21122117
/**

0 commit comments

Comments
 (0)