Skip to content

Commit ba71526

Browse files
committed
Reduce locking in HostClassLoader
Using a neat trick described in #13219 (comment) to avoid large synchronization blocks.
1 parent ce34785 commit ba71526

File tree

1 file changed

+48
-35
lines changed

1 file changed

+48
-35
lines changed

engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/HostClassLoader.java

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
import java.net.MalformedURLException;
1818
import java.net.URL;
1919
import java.net.URLClassLoader;
20-
import java.util.Map;
20+
import java.util.concurrent.CompletableFuture;
2121
import java.util.concurrent.ConcurrentHashMap;
22+
import java.util.concurrent.ExecutionException;
23+
import java.util.concurrent.Future;
2224
import org.graalvm.polyglot.Context;
2325

2426
/**
@@ -30,7 +32,8 @@
3032
@ExportLibrary(InteropLibrary.class)
3133
final class HostClassLoader extends URLClassLoader implements AutoCloseable, TruffleObject {
3234

33-
private final Map<String, Class<?>> loadedClasses = new ConcurrentHashMap<>();
35+
private final ConcurrentHashMap<String, Future<Class<?>>> loadedClasses =
36+
new ConcurrentHashMap<>();
3437
private static final Logger logger = System.getLogger(HostClassLoader.class.getName());
3538
// Classes from "org.graalvm" packages are loaded either by a class loader for the boot
3639
// module layer, or by a specific class loader, depending on how enso is run. For example,
@@ -68,43 +71,53 @@ public Class<?> loadClass(String name) throws ClassNotFoundException {
6871

6972
@Override
7073
@CompilerDirectives.TruffleBoundary
74+
@SuppressWarnings("unchecked")
7175
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
7276
logger.log(Logger.Level.TRACE, "Loading class {0}", name);
73-
var l = loadedClasses.get(name);
74-
if (l != null) {
75-
logger.log(Logger.Level.TRACE, "Class {0} found in cache", name);
76-
return l;
77+
var placeholder = new CompletableFuture[1];
78+
var pendingClass =
79+
loadedClasses.computeIfAbsent(
80+
name,
81+
t -> {
82+
logger.log(Logger.Level.TRACE, "Class {0} found in cache", name);
83+
var f = new CompletableFuture<Class<?>>();
84+
placeholder[0] = f;
85+
return f;
86+
});
87+
if (placeholder[0] != null) {
88+
placeholder[0].complete(loadClassUnsafe(name, resolve));
7789
}
78-
synchronized (this) {
79-
l = loadedClasses.get(name);
80-
if (l != null) {
81-
logger.log(Logger.Level.TRACE, "Class {0} found in cache", name);
82-
return l;
83-
}
84-
if (!isRuntimeModInBootLayer && name.startsWith("org.graalvm")) {
85-
return polyglotClassLoader.loadClass(name);
86-
}
87-
if (name.startsWith("org.slf4j")) {
88-
// Delegating to system class loader ensures that log classes are not loaded again
89-
// and do not require special setup. In other words, it is using log configuration that
90-
// has been setup by the runner that started the process. See #11641.
91-
return polyglotClassLoader.loadClass(name);
92-
}
93-
try {
94-
l = findClass(name);
95-
if (resolve) {
96-
l.getMethods();
97-
}
98-
logger.log(Logger.Level.TRACE, "Class {0} found, putting in cache", name);
99-
loadedClasses.put(name, l);
100-
return l;
101-
} catch (ClassNotFoundException ex) {
102-
logger.log(Logger.Level.TRACE, "Class {0} not found, delegating to super", name);
103-
return super.loadClass(name, resolve);
104-
} catch (Throwable e) {
105-
logger.log(Logger.Level.TRACE, "Failure while loading a class: " + e.getMessage(), e);
106-
throw e;
90+
try {
91+
return pendingClass.get();
92+
} catch (InterruptedException | ExecutionException e) {
93+
throw new ClassNotFoundException("Unable to find class " + name, e);
94+
}
95+
}
96+
97+
/** Find a class with a given name without giving any Thread-safety guarantees. */
98+
private Class<?> loadClassUnsafe(String name, boolean resolve) throws ClassNotFoundException {
99+
if (!isRuntimeModInBootLayer && name.startsWith("org.graalvm")) {
100+
return polyglotClassLoader.loadClass(name);
101+
}
102+
if (name.startsWith("org.slf4j")) {
103+
// Delegating to system class loader ensures that log classes are not loaded again
104+
// and do not require special setup. In other words, it is using log configuration that
105+
// has been setup by the runner that started the process. See #11641.
106+
return polyglotClassLoader.loadClass(name);
107+
}
108+
try {
109+
var l = findClass(name);
110+
if (resolve) {
111+
l.getMethods();
107112
}
113+
logger.log(Logger.Level.TRACE, "Class {0} found, putting in cache", name);
114+
return l;
115+
} catch (ClassNotFoundException ex) {
116+
logger.log(Logger.Level.TRACE, "Class {0} not found, delegating to super", name);
117+
return super.loadClass(name, resolve);
118+
} catch (Throwable e) {
119+
logger.log(Logger.Level.TRACE, "Failure while loading a class: " + e.getMessage(), e);
120+
throw e;
108121
}
109122
}
110123

0 commit comments

Comments
 (0)