Skip to content

Commit

Permalink
Speedup ORM enhancement of large models
Browse files Browse the repository at this point in the history
  • Loading branch information
Sanne committed Apr 19, 2024
1 parent 03a8994 commit d2b2b42
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import java.util.function.BiFunction;

import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
import org.hibernate.bytecode.enhance.spi.Enhancer;
import org.hibernate.bytecode.enhance.spi.UnloadedField;
import org.hibernate.bytecode.spi.BytecodeProvider;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
Expand All @@ -13,6 +11,7 @@
import io.quarkus.deployment.QuarkusClassVisitor;
import io.quarkus.deployment.QuarkusClassWriter;
import io.quarkus.gizmo.Gizmo;
import io.quarkus.hibernate.orm.deployment.integration.QuarkusEnhancementContext;
import net.bytebuddy.ClassFileVersion;

/**
Expand All @@ -32,9 +31,11 @@ public final class HibernateEntityEnhancer implements BiFunction<String, ClassVi
private static final BytecodeProvider PROVIDER = new org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl(
ClassFileVersion.JAVA_V11);

private final Enhancer enhancer = PROVIDER.getEnhancer(new QuarkusEnhancementContext());

@Override
public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) {
return new HibernateEnhancingClassVisitor(className, outputClassVisitor);
return new HibernateEnhancingClassVisitor(className, outputClassVisitor, enhancer);
}

private static class HibernateEnhancingClassVisitor extends QuarkusClassVisitor {
Expand All @@ -43,30 +44,14 @@ private static class HibernateEnhancingClassVisitor extends QuarkusClassVisitor
private final ClassVisitor outputClassVisitor;
private final Enhancer enhancer;

public HibernateEnhancingClassVisitor(String className, ClassVisitor outputClassVisitor) {
public HibernateEnhancingClassVisitor(String className, ClassVisitor outputClassVisitor,
Enhancer enhancer) {
//Careful: the ASM API version needs to match the ASM version of Gizmo, not the one from Byte Buddy.
//Most often these match - but occasionally they will diverge which is acceptable as Byte Buddy is shading ASM.
super(Gizmo.ASM_API_VERSION, new QuarkusClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS));
this.className = className;
this.outputClassVisitor = outputClassVisitor;
//note that as getLoadingClassLoader is resolved immediately this can't be created until transform time

DefaultEnhancementContext enhancementContext = new DefaultEnhancementContext() {

@Override
public boolean doBiDirectionalAssociationManagement(final UnloadedField field) {
//Don't enable automatic association management as it's often too surprising.
//Also, there's several cases in which its semantics are of unspecified,
//such as what should happen when dealing with ordered collections.
return false;
}

@Override
public ClassLoader getLoadingClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
};
this.enhancer = PROVIDER.getEnhancer(enhancementContext);
this.enhancer = enhancer;
}

@Override
Expand All @@ -90,14 +75,7 @@ private byte[] hibernateEnhancement(final String className, final byte[] origina
}

public byte[] enhance(String className, byte[] bytes) {
DefaultEnhancementContext enhancementContext = new DefaultEnhancementContext() {
@Override
public ClassLoader getLoadingClassLoader() {
return Thread.currentThread().getContextClassLoader();
}

};
Enhancer enhancer = PROVIDER.getEnhancer(enhancementContext);
return enhancer.enhance(className, bytes);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package io.quarkus.hibernate.orm.deployment.integration;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.stream.Stream;

/**
* This is meant to be passed exclusively to the ByteBuddy based entity Enhancer of Hibernate ORM.
*
* High couple warning: we rely on the knowledge that ByteBuddy will exclusively invoke method
* {@link ClassLoader#getResourceAsStream(String)} on the passed classloader; to verify that
* this assumption doesn't break in the future we throw exceptions on all other operations.
*
* Why do we do this?
* We need the Enhancer to source such resources from the thread context classloader which is set during the
* enhancement process, but we can't capture this yet as it hasn't been created at the point in which we
* need to pass a ClassLoader to the Hibernate ORM context.
* On the other hand, deferring the creation of the ORM enhancer implies needing to create a new enhancer
* instance for each processed entity, which very significantly slows down the process.
*/
final class DeferredClassLoader extends ClassLoader {

public static final ClassLoader INSTANCE = new DeferredClassLoader();

protected DeferredClassLoader() {
super();
}

@Override
public String getName() {
return "Defferred Enhancement Classloader";
}

@Override
public Class<?> loadClass(String name) {
dont();
return null;
}

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
dont();
return null;
}

@Override
protected Object getClassLoadingLock(String className) {
dont();
return null;
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
dont();
return null;
}

@Override
protected Class<?> findClass(String moduleName, String name) {
dont();
return null;
}

@Override
protected URL findResource(String moduleName, String name) throws IOException {
dont();
return null;
}

@Override
public URL getResource(String name) {
dont();
return null;
}

@Override
public Enumeration<URL> getResources(String name) {
dont();
return null;
}

@Override
public Stream<URL> resources(String name) {
dont();
return null;
}

@Override
protected URL findResource(String name) {
dont();
return null;
}

@Override
protected Enumeration<URL> findResources(String name) throws IOException {
dont();
return null;
}

/**
* The only implemented method: delegate to the currently set context classloader.
*/
@Override
public InputStream getResourceAsStream(String name) {
return Thread.currentThread().getContextClassLoader().getResourceAsStream(name);
}

@Override
protected Package definePackage(
String name,
String specTitle,
String specVersion,
String specVendor,
String implTitle,
String implVersion,
String implVendor,
URL sealBase) {
dont();
return null;
}

@Override
protected Package getPackage(String name) {
dont();
return null;
}

@Override
protected Package[] getPackages() {
dont();
return null;
}

@Override
protected String findLibrary(String libname) {
dont();
return null;
}

private void dont() {
throw new UnsupportedOperationException("This classloader only supports the #getResourceAsStream() operation");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.hibernate.orm.deployment.integration;

import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
import org.hibernate.bytecode.enhance.spi.UnloadedField;

public final class QuarkusEnhancementContext extends DefaultEnhancementContext {

@Override
public boolean doBiDirectionalAssociationManagement(final UnloadedField field) {
//Don't enable automatic association management as it's often too surprising.
//Also, there's several cases in which its semantics are of unspecified,
//such as what should happen when dealing with ordered collections.
return false;
}

@Override
public ClassLoader getLoadingClassLoader() {
return DeferredClassLoader.INSTANCE;
}

}

0 comments on commit d2b2b42

Please sign in to comment.