Skip to content

Commit

Permalink
Create a lossy index of resource -> ClassPathElement mapping
Browse files Browse the repository at this point in the history
The size of ClassLoaderState can be extremely problematic so the idea of
this patch is to have a lossy index instead and try to find a good
compromise between speed and memory usage.
  • Loading branch information
gsmet committed Aug 14, 2024
1 parent 1466fcc commit 19ce97b
Show file tree
Hide file tree
Showing 21 changed files with 506 additions and 214 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
package io.quarkus.deployment;

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

import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;

import io.quarkus.bootstrap.BootstrapDebug;
import io.quarkus.bootstrap.classloading.ClassPathElement;
import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
Expand Down Expand Up @@ -75,12 +71,7 @@ public Writer getSourceWriter(String className) {
}

public static boolean isApplicationClass(String className) {
QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread()
.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(fromClassNameToResourceName(className), true);
return !res.isEmpty();
return QuarkusClassLoader.isApplicationClass(className);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,12 @@
import io.quarkus.bootstrap.app.CuratedApplication;
import io.quarkus.bootstrap.app.RunningQuarkusApplication;
import io.quarkus.bootstrap.app.StartupAction;
import io.quarkus.bootstrap.classloading.ClassPathElement;
import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.bootstrap.logging.InitialConfigurator;
import io.quarkus.bootstrap.runner.Timing;
import io.quarkus.builder.BuildChainBuilder;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.commons.classloading.ClassLoaderHelper;
import io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem;
import io.quarkus.deployment.console.ConsoleCommand;
import io.quarkus.deployment.console.ConsoleStateManager;
Expand Down Expand Up @@ -478,14 +476,8 @@ public void execute(BuildContext context) {
//we need to make sure all hot reloadable classes are application classes
context.produce(new ApplicationClassPredicateBuildItem(new Predicate<String>() {
@Override
public boolean test(String s) {
QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread()
.getContextClassLoader();
String resourceName = ClassLoaderHelper.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(resourceName, true);
return !res.isEmpty();
public boolean test(String className) {
return QuarkusClassLoader.isApplicationClass(className);
}
}));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import io.quarkus.bootstrap.classloading.ClassPathElement;
import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.IsNormal;
Expand Down Expand Up @@ -155,13 +154,8 @@ public ServiceStartBuildItem searchForTags(CombinedIndexBuildItem combinedIndexB
return null;
}

public boolean isAppClass(String theClassName) {
QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread()
.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(theClassName.replace(".", "/") + ".class", true);
return !res.isEmpty();
public boolean isAppClass(String className) {
return QuarkusClassLoader.isApplicationClass(className);
}

public static class TracingClassVisitor extends ClassVisitor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ static ArchivePathTree forPath(Path path, PathFilter filter, boolean manifestEna
this.pathFilter = pathFilter;
}

@Override
public boolean providesLocalResources() {
return false;
}

@Override
public Collection<Path> getRoots() {
return List.of(archive);
Expand Down Expand Up @@ -240,6 +245,11 @@ protected OpenArchivePathTree(FileSystem fs) {
this.rootPath = fs.getPath("/");
}

@Override
public boolean providesLocalResources() {
return false;
}

@Override
protected Path getContainerPath() {
return ArchivePathTree.this.archive;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ protected DirectoryPathTree(Path dir, PathFilter pathFilter, PathTreeWithManifes
this.dir = dir;
}

@Override
public boolean providesLocalResources() {
return true;
}

@Override
protected Path getRootPath() {
return dir;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public static EmptyPathTree getInstance() {
return INSTANCE;
}

@Override
public boolean providesLocalResources() {
return false;
}

@Override
public Collection<Path> getRoots() {
return Collections.emptyList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ class FilePathTree implements OpenPathTree {
this.pathFilter = pathFilter;
}

@Override
public boolean providesLocalResources() {
return true;
}

@Override
public Collection<Path> getRoots() {
return Collections.singletonList(file);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ public FilteredPathTree(PathTree tree, PathFilter filter) {
this.filter = Objects.requireNonNull(filter, "filter is null");
}

@Override
public boolean providesLocalResources() {
return original.providesLocalResources();
}

@Override
public Collection<Path> getRoots() {
return original.getRoots();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ public MultiRootPathTree(PathTree... trees) {
roots = tmp;
}

/**
* If at least one of the PathTrees contains local resources, we return true.
*/
@Override
public boolean providesLocalResources() {
for (PathTree tree : trees) {
if (tree.providesLocalResources()) {
return true;
}
}

return false;
}

@Override
public Collection<Path> getRoots() {
return roots;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ static PathTree ofArchive(Path archive, PathFilter filter) {
return ArchivePathTree.forPath(archive, filter);
}

/**
* Whether the PathTree provides local resources.
* <p>
* For instance a directory or a file provides local resources. A jar does not.
*/
boolean providesLocalResources();

/**
* The roots of the path tree.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ private CallerOpenPathTree(SharedOpenArchivePathTree delegate) {
this.delegate = delegate;
}

@Override
public boolean providesLocalResources() {
return delegate.providesLocalResources();
}

@Override
public PathTree getOriginalTree() {
return delegate.getOriginalTree();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ public final class ClassLoaderHelper {
private static final String JDK_INTERNAL = "jdk.internal.";
private static final String SUN_MISC = "sun.misc.";

private static final String CLASS_SUFFIX = ".class";

private ClassLoaderHelper() {
//Not meant to be instantiated
}
Expand All @@ -19,7 +21,23 @@ private ClassLoaderHelper() {
*/
public static String fromClassNameToResourceName(final String className) {
//Important: avoid indy!
return className.replace('.', '/').concat(".class");
return className.replace('.', '/').concat(CLASS_SUFFIX);
}

/**
* Helper method to convert a resource name into the corresponding class name:
* replace all "/" with "." and remove the ".class" postfix.
*
* @param resourceName
* @return the name of the respective class
*/
public static String fromResourceNameToClassName(final String resourceName) {
if (!resourceName.endsWith(CLASS_SUFFIX)) {
throw new IllegalArgumentException(
String.format("%s is not a valid resource name as it doesn't end with .class", resourceName));
}

return resourceName.substring(0, resourceName.length() - CLASS_SUFFIX.length()).replace('/', '.');
}

public static boolean isInJdkPackage(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,12 @@ private void addCpElement(QuarkusClassLoader.Builder builder, ResolvedDependency
//we always load this from the parent if it is available, as this acts as a bridge between the running
//app and the dev mode code
builder.addParentFirstElement(element);
builder.addElement(element);
} else if (dep.isFlagSet(DependencyFlags.CLASSLOADER_LESSER_PRIORITY)) {
builder.addLesserPriorityElement(element);
} else {
builder.addElement(element);
}
builder.addElement(element);
}

public synchronized QuarkusClassLoader getOrCreateAugmentClassLoader() {
Expand Down Expand Up @@ -493,6 +495,11 @@ public Set<String> getProvidedResources() {
return delegate.getProvidedResources().stream().filter(s -> s.endsWith(".class")).collect(Collectors.toSet());
}

@Override
public boolean providesLocalResources() {
return delegate.providesLocalResources();
}

@Override
public ProtectionDomain getProtectionDomain() {
return delegate.getProtectionDomain();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.io.Closeable;
import java.nio.file.Path;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
Expand Down Expand Up @@ -74,6 +73,11 @@ default ResolvedDependency getResolvedDependency() {
*/
Set<String> getProvidedResources();

/**
* Whether this class path element contains local resources.
*/
boolean providesLocalResources();

/**
*
* @return The protection domain that should be used to define classes from this element
Expand Down Expand Up @@ -102,6 +106,7 @@ static ClassPathElement fromDependency(ResolvedDependency dep) {
}

static ClassPathElement EMPTY = new ClassPathElement() {

@Override
public Path getRoot() {
return null;
Expand All @@ -124,7 +129,12 @@ public ClassPathResource getResource(String name) {

@Override
public Set<String> getProvidedResources() {
return Collections.emptySet();
return Set.of();
}

@Override
public boolean providesLocalResources() {
return false;
}

@Override
Expand Down
Loading

0 comments on commit 19ce97b

Please sign in to comment.