Skip to content

Commit

Permalink
Simplify runtime class transformation
Browse files Browse the repository at this point in the history
The tranformation now uses the same code as the production
app, which removes complexity and the ASM dependency from
the bootstrap.
  • Loading branch information
stuartwdouglas committed Jun 18, 2020
1 parent b139fa8 commit 18f1ca2
Show file tree
Hide file tree
Showing 9 changed files with 42 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@ public Map<Path, Set<String>> getTransformedFilesByJar() {

public static class TransformedClass {

private final String className;
private final byte[] data;
private final String fileName;
private final boolean eager;

public TransformedClass(byte[] data, String fileName) {
public TransformedClass(String className, byte[] data, String fileName, boolean eager) {
this.className = className;
this.data = data;
this.fileName = fileName;
this.eager = eager;
}

public byte[] getData() {
Expand All @@ -52,6 +56,14 @@ public String getFileName() {
return fileName;
}

public String getClassName() {
return className;
}

public boolean isEager() {
return eager;
}

@Override
public boolean equals(Object o) {
if (this == o)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ TransformedClassesBuildItem handleClassTransformation(List<BytecodeTransformerBu
bytecodeTransformerBuildItems.size());
Set<String> noConstScanning = new HashSet<>();
Map<String, Set<String>> constScanning = new HashMap<>();
Set<String> eager = new HashSet<>();
for (BytecodeTransformerBuildItem i : bytecodeTransformerBuildItems) {
bytecodeTransformers.computeIfAbsent(i.getClassToTransform(), (h) -> new ArrayList<>())
.add(i.getVisitorFunction());
Expand All @@ -54,6 +55,9 @@ TransformedClassesBuildItem handleClassTransformation(List<BytecodeTransformerBu
constScanning.computeIfAbsent(i.getClassToTransform(), (s) -> new HashSet<>())
.addAll(i.getRequireConstPoolEntry());
}
if (i.isEager()) {
eager.add(i.getClassToTransform());
}
}
QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader();
Map<String, Path> transformedToArchive = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -100,8 +104,8 @@ public TransformedClassesBuildItem.TransformedClass call() throws Exception {
visitor = i.apply(className, visitor);
}
cr.accept(visitor, 0);
return new TransformedClassesBuildItem.TransformedClass(writer.toByteArray(),
classFileName);
return new TransformedClassesBuildItem.TransformedClass(className, writer.toByteArray(),
classFileName, eager.contains(className));
} finally {
Thread.currentThread().setContextClassLoader(old);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import io.quarkus.deployment.ExtensionLoader;
import io.quarkus.deployment.QuarkusAugmentor;
import io.quarkus.deployment.builditem.ApplicationClassNameBuildItem;
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.deployment.builditem.ConfigDescriptionBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
Expand All @@ -41,6 +40,7 @@
import io.quarkus.deployment.builditem.MainClassBuildItem;
import io.quarkus.deployment.builditem.RawCommandLineArgumentsBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.builditem.TransformedClassesBuildItem;
import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
import io.quarkus.deployment.pkg.builditem.JarBuildItem;
import io.quarkus.deployment.pkg.builditem.NativeImageBuildItem;
Expand Down Expand Up @@ -125,7 +125,7 @@ public StartupActionImpl createInitialRuntimeApplication() {
}
ClassLoader classLoader = curatedApplication.createDeploymentClassLoader();
BuildResult result = runAugment(true, Collections.emptySet(), classLoader, GeneratedClassBuildItem.class,
GeneratedResourceBuildItem.class, BytecodeTransformerBuildItem.class, ApplicationClassNameBuildItem.class,
GeneratedResourceBuildItem.class, TransformedClassesBuildItem.class, ApplicationClassNameBuildItem.class,
MainClassBuildItem.class);
return new StartupActionImpl(curatedApplication, result, classLoader);
}
Expand All @@ -137,7 +137,7 @@ public StartupActionImpl reloadExistingApplication(boolean hasStartedSuccessfull
}
ClassLoader classLoader = curatedApplication.createDeploymentClassLoader();
BuildResult result = runAugment(!hasStartedSuccessfully, changedResources, classLoader, GeneratedClassBuildItem.class,
GeneratedResourceBuildItem.class, BytecodeTransformerBuildItem.class, ApplicationClassNameBuildItem.class,
GeneratedResourceBuildItem.class, TransformedClassesBuildItem.class, ApplicationClassNameBuildItem.class,
MainClassBuildItem.class);
return new StartupActionImpl(curatedApplication, result, classLoader);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,15 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Predicate;

import org.jboss.logging.Logger;
import org.objectweb.asm.ClassVisitor;

import io.quarkus.bootstrap.BootstrapDebug;
import io.quarkus.bootstrap.app.CuratedApplication;
Expand All @@ -32,12 +27,11 @@
import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.builder.BuildResult;
import io.quarkus.deployment.builditem.ApplicationClassNameBuildItem;
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
import io.quarkus.deployment.builditem.MainClassBuildItem;
import io.quarkus.deployment.builditem.TransformedClassesBuildItem;
import io.quarkus.deployment.configuration.RunTimeConfigurationGenerator;
import io.quarkus.deployment.index.ConstPoolScanner;
import io.quarkus.dev.appstate.ApplicationStateNotification;
import io.quarkus.runtime.Quarkus;

Expand All @@ -54,30 +48,27 @@ public StartupActionImpl(CuratedApplication curatedApplication, BuildResult buil
this.curatedApplication = curatedApplication;
this.buildResult = buildResult;
Set<String> eagerClasses = new HashSet<>();
Map<String, Predicate<byte[]>> transformerPredicates = new HashMap<>();
Map<String, List<BiFunction<String, ClassVisitor, ClassVisitor>>> bytecodeTransformers = extractTransformers(
eagerClasses, transformerPredicates);
Map<String, byte[]> bytecodeTransformers = extractTransformers(eagerClasses);
QuarkusClassLoader baseClassLoader = curatedApplication.getBaseRuntimeClassLoader();
QuarkusClassLoader runtimeClassLoader;

//so we have some differences between dev and test mode here.
//test mode only has a single class loader, while dev uses a disposable runtime class loader
//that is discarded between restarts
Map<String, byte[]> resources = new HashMap<>();
resources.putAll(extractGeneratedResources(true));
resources.putAll(bytecodeTransformers);
if (curatedApplication.getQuarkusBootstrap().getMode() == QuarkusBootstrap.Mode.DEV) {
baseClassLoader.reset(extractGeneratedResources(false), bytecodeTransformers, transformerPredicates,
baseClassLoader.reset(extractGeneratedResources(false),
deploymentClassLoader);
runtimeClassLoader = curatedApplication.createRuntimeClassLoader(baseClassLoader,
bytecodeTransformers, transformerPredicates,
deploymentClassLoader, extractGeneratedResources(true));
deploymentClassLoader, resources);
} else {
Map<String, byte[]> resources = new HashMap<>();
resources.putAll(extractGeneratedResources(false));
resources.putAll(extractGeneratedResources(true));
baseClassLoader.reset(resources, bytecodeTransformers, transformerPredicates, deploymentClassLoader);
baseClassLoader.reset(resources, deploymentClassLoader);
runtimeClassLoader = baseClassLoader;
}
this.runtimeClassLoader = runtimeClassLoader;
handleEagerClasses(runtimeClassLoader, eagerClasses);
}

private void handleEagerClasses(QuarkusClassLoader runtimeClassLoader, Set<String> eagerClasses) {
Expand Down Expand Up @@ -262,40 +253,18 @@ public ClassLoader getClassLoader() {
return runtimeClassLoader;
}

private Map<String, List<BiFunction<String, ClassVisitor, ClassVisitor>>> extractTransformers(Set<String> eagerClasses,
Map<String, Predicate<byte[]>> transformerPredicates) {
Map<String, List<BiFunction<String, ClassVisitor, ClassVisitor>>> bytecodeTransformers = new HashMap<>();
Set<String> noConstScanning = new HashSet<>();
Map<String, Set<String>> constScanning = new HashMap<>();
List<BytecodeTransformerBuildItem> transformers = buildResult.consumeMulti(BytecodeTransformerBuildItem.class);
for (BytecodeTransformerBuildItem i : transformers) {
List<BiFunction<String, ClassVisitor, ClassVisitor>> list = bytecodeTransformers.get(i.getClassToTransform());
if (list == null) {
bytecodeTransformers.put(i.getClassToTransform(), list = new ArrayList<>());
}
list.add(i.getVisitorFunction());
if (i.isEager()) {
eagerClasses.add(i.getClassToTransform());
}
if (i.getRequireConstPoolEntry() == null || i.getRequireConstPoolEntry().isEmpty()) {
noConstScanning.add(i.getClassToTransform());
} else {
constScanning.computeIfAbsent(i.getClassToTransform(), (s) -> new HashSet<>())
.addAll(i.getRequireConstPoolEntry());
}
}
for (String i : noConstScanning) {
constScanning.remove(i);
}
for (Map.Entry<String, Set<String>> entry : constScanning.entrySet()) {
transformerPredicates.put(entry.getKey(), new Predicate<byte[]>() {
@Override
public boolean test(byte[] bytes) {
return ConstPoolScanner.constPoolEntryPresent(bytes, entry.getValue());
private Map<String, byte[]> extractTransformers(Set<String> eagerClasses) {
Map<String, byte[]> ret = new HashMap<>();
TransformedClassesBuildItem transformers = buildResult.consume(TransformedClassesBuildItem.class);
for (Set<TransformedClassesBuildItem.TransformedClass> i : transformers.getTransformedClassesByJar().values()) {
for (TransformedClassesBuildItem.TransformedClass clazz : i) {
ret.put(clazz.getFileName(), clazz.getData());
if (clazz.isEager()) {
eagerClasses.add(clazz.getClassName());
}
});
}
}
return bytecodeTransformers;
return ret;
}

private Map<String, byte[]> extractGeneratedResources(boolean applicationClasses) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class VerticleWithClassNameDeploymentTest {
public void testDeploymentOfVerticleUsingClassName() {
String resp1 = RestAssured.get("http://localhost:8080").asString();
String resp2 = RestAssured.get("http://localhost:8080").asString();
Assertions.assertTrue(resp1.startsWith("OK"));
Assertions.assertTrue(resp1.startsWith("OK"), resp1);
Assertions.assertTrue(resp2.startsWith("OK"));
Assertions.assertNotEquals(resp1, resp2);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import org.objectweb.asm.ClassVisitor;

/**
* The result of the curate step that is done by QuarkusBootstrap.
Expand Down Expand Up @@ -268,19 +265,16 @@ public QuarkusClassLoader createDeploymentClassLoader() {
}

public QuarkusClassLoader createRuntimeClassLoader(QuarkusClassLoader loader,
Map<String, List<BiFunction<String, ClassVisitor, ClassVisitor>>> bytecodeTransformers,
Map<String, Predicate<byte[]>> transformerPredicates,
ClassLoader deploymentClassLoader, Map<String, byte[]> resources) {
QuarkusClassLoader.Builder builder = QuarkusClassLoader.builder("Quarkus Runtime ClassLoader",
loader, false)
.setAggregateParentResources(true);
builder.setTransformerPredicates(transformerPredicates);
builder.setTransformerClassLoader(deploymentClassLoader);

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

for (AdditionalDependency i : getQuarkusBootstrap().getAdditionalApplicationArchives()) {
if (i.isHotReloadable()) {
Expand All @@ -289,7 +283,6 @@ public QuarkusClassLoader createRuntimeClassLoader(QuarkusClassLoader loader,
}
}
}
builder.setBytecodeTransformers(bytecodeTransformers);
return builder.build();
}

Expand Down
Loading

0 comments on commit 18f1ca2

Please sign in to comment.