Skip to content

Commit

Permalink
Merge pull request #40392 from Sanne/ClassLoaderImprovements
Browse files Browse the repository at this point in the history
Classloader improvements: introducing quarkus-classloader-commons and benchmarking infrastructure
  • Loading branch information
Sanne authored May 3, 2024
2 parents 8832fa8 + ce4471f commit 096f6a2
Show file tree
Hide file tree
Showing 46 changed files with 369 additions and 42 deletions.
4 changes: 4 additions & 0 deletions core/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
<name>Quarkus - Core - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-classloader-commons</artifactId>
</dependency>
<dependency>
<groupId>org.aesh</groupId>
<artifactId>readline</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.deployment;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.UncheckedIOException;
Expand Down Expand Up @@ -381,7 +383,7 @@ private static Map<String, List<String>> getUnavailableConfigServices(ResolvedDe
.map(String::trim)
// skip comments and empty lines
.filter(line -> !line.startsWith("#") && !line.isEmpty())
.filter(className -> classLoader.getResource(className.replace('.', '/') + ".class") == null)
.filter(className -> classLoader.getResource(fromClassNameToResourceName(className)) == null)
.forEach(unavailableList::add);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read " + serviceFile, e);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.deployment;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;

import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
Expand Down Expand Up @@ -77,7 +79,7 @@ public static boolean isApplicationClass(String className) {
.getContextClassLoader();
//if the class file is present in this (and not the parent) CL then it is an application class
List<ClassPathElement> res = cl
.getElementsWithResource(className.replace('.', '/') + ".class", true);
.getElementsWithResource(fromClassNameToResourceName(className), true);
return !res.isEmpty();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import io.quarkus.builder.BuildChainBuilder;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.commons.classloading.ClassloadHelper;
import io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem;
import io.quarkus.deployment.console.ConsoleCommand;
import io.quarkus.deployment.console.ConsoleStateManager;
Expand Down Expand Up @@ -408,9 +409,10 @@ public void execute(BuildContext context) {
public boolean test(String s) {
QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread()
.getContextClassLoader();
String resourceName = ClassloadHelper.fromClassNameToResourceName(s);
//if the class file is present in this (and not the parent) CL then it is an application class
List<ClassPathElement> res = cl
.getElementsWithResource(s.replace('.', '/') + ".class", true);
.getElementsWithResource(resourceName, true);
return !res.isEmpty();
}
}));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.deployment.dev.filesystem;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -296,7 +298,7 @@ protected Class<?> findClass(String name) throws ClassNotFoundException {
// It would be easier to call the loadClass() methods of the delegateClassLoaders
// here, but we have to load the class from the bytecode ourselves, because we
// need it to be associated with our class loader.
String path = name.replace('.', '/') + ".class";
String path = fromClassNameToResourceName(name);
URL url = findResource(path);
if (url == null) {
throw new ClassNotFoundException(name);
Expand Down Expand Up @@ -376,4 +378,4 @@ private static byte[] read(InputStream source, int initialSize) throws IOExcepti
return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.deployment.dev.testing;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
Expand Down Expand Up @@ -654,13 +656,14 @@ public String apply(Class<?> aClass) {
Map<String, byte[]> transformedClasses = new HashMap<>();
for (String i : classesToTransform) {
try {
String resourceName = fromClassNameToResourceName(i);
byte[] classData = IoUtil
.readBytes(deploymentClassLoader.getResourceAsStream(i.replace('.', '/') + ".class"));
.readBytes(deploymentClassLoader.getResourceAsStream(resourceName));
ClassReader cr = new ClassReader(classData);
ClassWriter writer = new QuarkusClassWriter(cr,
ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cr.accept(new TestTracingProcessor.TracingClassVisitor(writer, i), 0);
transformedClasses.put(i.replace('.', '/') + ".class", writer.toByteArray());
transformedClasses.put(resourceName, writer.toByteArray());
} catch (Exception e) {
log.error("Failed to instrument " + i + " for usage tracking", e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.deployment.index;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
Expand Down Expand Up @@ -313,7 +315,7 @@ static boolean index(Indexer indexer, String className, ClassLoader classLoader)
return false;
}
try (InputStream stream = classLoader
.getResourceAsStream(className.replace('.', '/') + ".class")) {
.getResourceAsStream(fromClassNameToResourceName(className))) {
if (stream != null) {
indexer.index(stream);
result = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.deployment.pkg.steps;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;
import static io.quarkus.deployment.pkg.PackageConfig.JarConfig.JarType.*;

import java.io.BufferedInputStream;
Expand Down Expand Up @@ -649,7 +650,7 @@ private JarBuildItem buildThinJar(CurateOutcomeBuildItem curateOutcomeBuildItem,
fastJarJarsBuilder.setGenerated(generatedZip);
try (FileSystem out = createNewZip(generatedZip, packageConfig)) {
for (GeneratedClassBuildItem i : generatedClasses) {
String fileName = i.getName().replace('.', '/') + ".class";
String fileName = fromClassNameToResourceName(i.getName());
Path target = out.getPath(fileName);
if (target.getParent() != null) {
Files.createDirectories(target.getParent());
Expand Down Expand Up @@ -1181,7 +1182,7 @@ private void copyCommonContent(FileSystem runnerZipFs, Map<String, List<byte[]>>
}
}
for (GeneratedClassBuildItem i : generatedClasses) {
String fileName = i.getName().replace('.', '/') + ".class";
String fileName = fromClassNameToResourceName(i.getName());
seen.put(fileName, "Current Application");
Path target = runnerZipFs.getPath(fileName);
handleParent(runnerZipFs, fileName, seen);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.deployment.steps;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -31,7 +33,7 @@ void appendAdditionalClassloaderResources(BuildProducer<AdditionalIndexedClasses

collected.put(entry.getKey(), entry.getValue());
// add it also as resources to allow index to work properly
collected.put(entry.getKey().replace('.', '/') + ".class", entry.getValue());
collected.put(fromClassNameToResourceName(entry.getKey()), entry.getValue());

}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.deployment.steps;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
Expand Down Expand Up @@ -113,7 +115,7 @@ protected boolean isQuarkusCoreBanner(URL url) {
// We determine whether the banner is the default by checking to see if the jar that contains it also
// contains this class. This way although somewhat complicated guarantees that any rename of artifacts
// won't affect the check
Path resolved = p.resolve("/" + thisClassName.replace('.', '/') + ".class");
Path resolved = p.resolve("/" + fromClassNameToResourceName(thisClassName));
return Files.exists(resolved);
});
} catch (UncheckedIOException ex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.deployment.steps;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
Expand Down Expand Up @@ -145,7 +147,7 @@ public byte[] apply(String className, byte[] originalBytes) {
ClassLoader old = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(transformCl);
String classFileName = className.replace('.', '/') + ".class";
String classFileName = fromClassNameToResourceName(className);
List<ClassPathElement> archives = cl.getElementsWithResource(classFileName);
if (!archives.isEmpty()) {
ClassPathElement classPathElement = archives.get(0);
Expand Down Expand Up @@ -195,7 +197,7 @@ public byte[] apply(String className, byte[] originalBytes) {
}
}
}
String classFileName = className.replace('.', '/') + ".class";
String classFileName = fromClassNameToResourceName(className);
List<ClassPathElement> archives = cl.getElementsWithResource(classFileName);
if (!archives.isEmpty()) {
ClassPathElement classPathElement = archives.get(0);
Expand Down Expand Up @@ -372,7 +374,7 @@ private byte[] transformClass(String className, List<BiFunction<String, ClassVis
if (!debugPath.exists()) {
debugPath.mkdir();
}
File classFile = new File(debugPath, className.replace('.', '/') + ".class");
File classFile = new File(debugPath, fromClassNameToResourceName(className));
classFile.getParentFile().mkdirs();
try (FileOutputStream classWriter = new FileOutputStream(classFile)) {
classWriter.write(data);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.quarkus.deployment.util;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;

import java.io.IOException;
import java.io.InputStream;

public class IoUtil {
public static InputStream readClass(ClassLoader classLoader, String className) {
return classLoader.getResourceAsStream(className.replace('.', '/') + ".class");
return classLoader.getResourceAsStream(fromClassNameToResourceName(className));
}

public static byte[] readClassAsBytes(ClassLoader classLoader, String className) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.runner.bootstrap;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;

import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
Expand Down Expand Up @@ -362,7 +364,7 @@ private static Map<String, byte[]> extractGeneratedResources(BuildResult buildRe
Map<String, byte[]> data = new HashMap<>();
for (GeneratedClassBuildItem i : buildResult.consumeMulti(GeneratedClassBuildItem.class)) {
if (i.isApplicationClass() == applicationClasses) {
data.put(i.getName().replace('.', '/') + ".class", i.getClassData());
data.put(fromClassNameToResourceName(i.getName()), i.getClassData());
if (BootstrapDebug.DEBUG_CLASSES_DIR != null) {
try {
File debugPath = new File(BootstrapDebug.DEBUG_CLASSES_DIR);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.deployment.pkg.steps;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

Expand Down Expand Up @@ -40,7 +41,7 @@ public void checkQuarkusCoreBannerOnFilesystemWithSpecialCharacters(@TempDir Pat
}

try (final FileSystem fs = ZipUtils.newFileSystem(zipPath)) {
Path classFile = fs.getPath(MyBannerProcessor.class.getName().replace('.', '/') + ".class");
Path classFile = fs.getPath(fromClassNameToResourceName(MyBannerProcessor.class.getName()));
Files.createDirectories(classFile.getParent());
Files.write(classFile, "".getBytes(StandardCharsets.UTF_8));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import org.jboss.jandex.Type.Kind;
import org.junit.jupiter.api.Test;

import io.quarkus.commons.classloading.ClassloadHelper;

public class JandexUtilTest {

private static final DotName SIMPLE = DotName.createSimple(Single.class.getName());
Expand Down Expand Up @@ -307,7 +309,7 @@ private static Index index(Class<?>... classes) {
for (Class<?> clazz : classes) {
try {
try (InputStream stream = JandexUtilTest.class.getClassLoader()
.getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) {
.getResourceAsStream(ClassloadHelper.fromClassNameToResourceName(clazz.getName()))) {
indexer.index(stream);
}
} catch (IOException e) {
Expand Down
2 changes: 2 additions & 0 deletions core/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
</lesserPriorityArtifacts>
<parentFirstArtifacts>
<parentFirstArtifact>io.quarkus:quarkus-bootstrap-runner</parentFirstArtifact>
<parentFirstArtifact>io.quarkus:quarkus-classloader-commons</parentFirstArtifact>
<parentFirstArtifact>io.smallrye.common:smallrye-common-constraint</parentFirstArtifact>
<parentFirstArtifact>io.smallrye.common:smallrye-common-cpu</parentFirstArtifact>
<parentFirstArtifact>io.smallrye.common:smallrye-common-expression</parentFirstArtifact>
Expand Down Expand Up @@ -237,6 +238,7 @@
<runnerParentFirstArtifact>org.graalvm.sdk:native-bridge</runnerParentFirstArtifact>
<!-- /support for GraalVM js -->
<runnerParentFirstArtifact>io.quarkus:quarkus-bootstrap-runner</runnerParentFirstArtifact>
<runnerParentFirstArtifact>io.quarkus:quarkus-classloader-commons</runnerParentFirstArtifact>
<runnerParentFirstArtifact>io.quarkus:quarkus-development-mode-spi</runnerParentFirstArtifact>
<!-- logging dependencies always need to be loaded by the JDK ClassLoader -->
<runnerParentFirstArtifact>org.jboss.logging:jboss-logging</runnerParentFirstArtifact>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.hibernate.orm.runtime.boot.scan;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
Expand Down Expand Up @@ -149,8 +151,9 @@ public Categorization getCategorization() {

@Override
public InputStreamAccess getStreamAccess() {
final String resourceName = fromClassNameToResourceName(name);
return new UrlInputStreamAccess(
Thread.currentThread().getContextClassLoader().getResource(name.replace('.', '/') + ".class"));
Thread.currentThread().getContextClassLoader().getResource(resourceName));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.jboss.jandex.ParameterizedType;
import org.junit.jupiter.api.Test;

import io.quarkus.commons.classloading.ClassloadHelper;
import io.quarkus.qute.Engine;
import io.quarkus.qute.Expression;
import io.quarkus.qute.deployment.TypeInfos.Info;
Expand Down Expand Up @@ -94,8 +95,9 @@ private void assertHints(String hintStr, String... expectedHints) {
private static Index index(Class<?>... classes) throws IOException {
Indexer indexer = new Indexer();
for (Class<?> clazz : classes) {
final String resourceName = ClassloadHelper.fromClassNameToResourceName(clazz.getName());
try (InputStream stream = TypeInfosTest.class.getClassLoader()
.getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) {
.getResourceAsStream(resourceName)) {
indexer.index(stream);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.jboss.jandex.Type;
import org.junit.jupiter.api.Test;

import io.quarkus.commons.classloading.ClassloadHelper;
import io.quarkus.qute.EvalContext;
import io.quarkus.qute.ValueResolver;
import io.quarkus.qute.deployment.Types.AssignabilityCheck;
Expand Down Expand Up @@ -78,8 +79,9 @@ public void testIsImplementorOf() throws IOException {
private static Index index(Class<?>... classes) throws IOException {
Indexer indexer = new Indexer();
for (Class<?> clazz : classes) {
final String resourceName = ClassloadHelper.fromClassNameToResourceName(clazz.getName());
try (InputStream stream = TypesTest.class.getClassLoader()
.getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) {
.getResourceAsStream(resourceName)) {
indexer.index(stream);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.resteasy.reactive.server.deployment;

import static io.quarkus.commons.classloading.ClassloadHelper.fromClassNameToResourceName;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
Expand All @@ -24,7 +26,8 @@ public FilterClassIntrospector(ClassLoader classLoader) {

public boolean usesGetResourceMethod(MethodInfo methodInfo) {
String className = methodInfo.declaringClass().name().toString();
try (InputStream is = classLoader.getResourceAsStream(className.replace('.', '/') + ".class")) {
final String resourceName = fromClassNameToResourceName(className);
try (InputStream is = classLoader.getResourceAsStream(resourceName)) {
ClassReader configClassReader = new ClassReader(is);
FilterClassVisitor classVisitor = new FilterClassVisitor(methodInfo.descriptor());
configClassReader.accept(classVisitor, 0);
Expand Down
Loading

0 comments on commit 096f6a2

Please sign in to comment.