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 29, 2024
1 parent 844a541 commit a5c83d0
Show file tree
Hide file tree
Showing 27 changed files with 595 additions and 269 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ public static <T> T readConfig(ApplicationModel appModel, LaunchMode launchMode,
final QuarkusClassLoader.Builder configClBuilder = QuarkusClassLoader.builder("CodeGenerator Config ClassLoader",
deploymentClassLoader, false);
if (!allowedConfigServices.isEmpty()) {
configClBuilder.addElement(new MemoryClassPathElement(allowedConfigServices, true));
configClBuilder.addNormalPriorityElement(new MemoryClassPathElement(allowedConfigServices, true));
}
if (!bannedConfigServices.isEmpty()) {
configClBuilder.addBannedElement(new MemoryClassPathElement(bannedConfigServices, true));
Expand Down
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 @@ -221,7 +221,7 @@ public void init() {
+ curatedApplication.getClassLoaderNameSuffix(),
getClass().getClassLoader().getParent(), false);
}
clBuilder.addElement(ClassPathElement.fromDependency(d));
clBuilder.addNormalPriorityElement(ClassPathElement.fromDependency(d));
}
}

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.addNormalPriorityElement(element);
} else if (dep.isFlagSet(DependencyFlags.CLASSLOADER_LESSER_PRIORITY)) {
builder.addLesserPriorityElement(element);
} else {
builder.addNormalPriorityElement(element);
}
builder.addElement(element);
}

public synchronized QuarkusClassLoader getOrCreateAugmentClassLoader() {
Expand All @@ -212,7 +214,7 @@ public synchronized QuarkusClassLoader getOrCreateAugmentClassLoader() {
}

for (Path i : quarkusBootstrap.getAdditionalDeploymentArchives()) {
builder.addElement(ClassPathElement.fromPath(i, false));
builder.addNormalPriorityElement(ClassPathElement.fromPath(i, false));
}
Map<String, byte[]> banned = new HashMap<>();
for (Collection<String> i : configuredClassLoading.getRemovedResources().values()) {
Expand Down Expand Up @@ -256,7 +258,7 @@ public synchronized QuarkusClassLoader getOrCreateBaseRuntimeClassLoader() {
//there is no need to restart so there is no need for an additional CL

for (Path root : quarkusBootstrap.getApplicationRoot()) {
builder.addElement(ClassPathElement.fromPath(root, true));
builder.addNormalPriorityElement(ClassPathElement.fromPath(root, true));
}
} else {
for (Path root : quarkusBootstrap.getApplicationRoot()) {
Expand All @@ -269,7 +271,7 @@ public synchronized QuarkusClassLoader getOrCreateBaseRuntimeClassLoader() {
for (AdditionalDependency i : quarkusBootstrap.getAdditionalApplicationArchives()) {
if (!i.isHotReloadable()) {
for (Path root : i.getResolvedPaths()) {
builder.addElement(ClassPathElement.fromPath(root, true));
builder.addNormalPriorityElement(ClassPathElement.fromPath(root, true));
}
} else {
for (Path root : i.getResolvedPaths()) {
Expand Down Expand Up @@ -339,15 +341,15 @@ public QuarkusClassLoader createDeploymentClassLoader() {
.setAggregateParentResources(true);

for (Path root : quarkusBootstrap.getApplicationRoot()) {
builder.addElement(ClassPathElement.fromPath(root, true));
builder.addNormalPriorityElement(ClassPathElement.fromPath(root, true));
}

builder.setResettableElement(new MemoryClassPathElement(Collections.emptyMap(), false));

//additional user class path elements first
for (AdditionalDependency i : quarkusBootstrap.getAdditionalApplicationArchives()) {
for (Path root : i.getResolvedPaths()) {
builder.addElement(ClassPathElement.fromPath(root, true));
builder.addNormalPriorityElement(ClassPathElement.fromPath(root, true));
}
}
for (ResolvedDependency dependency : appModel.getDependencies()) {
Expand All @@ -361,7 +363,7 @@ public QuarkusClassLoader createDeploymentClassLoader() {
}
}
for (Path root : configuredClassLoading.getAdditionalClasspathElements()) {
builder.addElement(ClassPathElement.fromPath(root, true));
builder.addNormalPriorityElement(ClassPathElement.fromPath(root, true));
}
return builder.build();
}
Expand All @@ -387,15 +389,15 @@ public QuarkusClassLoader createRuntimeClassLoader(ClassLoader base, Map<String,
.setAggregateParentResources(true);
builder.setTransformedClasses(transformedClasses);

builder.addElement(new MemoryClassPathElement(resources, true));
builder.addNormalPriorityElement(new MemoryClassPathElement(resources, true));
for (Path root : quarkusBootstrap.getApplicationRoot()) {
builder.addElement(ClassPathElement.fromPath(root, true));
builder.addNormalPriorityElement(ClassPathElement.fromPath(root, true));
}

for (AdditionalDependency i : getQuarkusBootstrap().getAdditionalApplicationArchives()) {
if (i.isHotReloadable()) {
for (Path root : i.getResolvedPaths()) {
builder.addElement(ClassPathElement.fromPath(root, true));
builder.addNormalPriorityElement(ClassPathElement.fromPath(root, true));
}
}
}
Expand All @@ -410,7 +412,7 @@ public QuarkusClassLoader createRuntimeClassLoader(ClassLoader base, Map<String,
}
}
for (Path root : configuredClassLoading.getAdditionalClasspathElements()) {
builder.addElement(ClassPathElement.fromPath(root, true));
builder.addNormalPriorityElement(ClassPathElement.fromPath(root, true));
}
return builder.build();
}
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
Loading

0 comments on commit a5c83d0

Please sign in to comment.