From 9be4ba4b96760fe73118e6aabdaa808c9fbaa996 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 19 Oct 2018 12:39:00 +1100 Subject: [PATCH] Allow bytecode transformers to run in parallel when using shamrock:run This drops startup time from 17s to 12s in the 1k entity test on my laptop --- .../deployment/BuildTimeGenerator.java | 12 ++-- .../shamrock/deployment/ProcessorContext.java | 44 ++++++------- .../shamrock/runner/RuntimeClassLoader.java | 65 ++++++++++++------- .../jboss/shamrock/runner/RuntimeRunner.java | 32 ++++++++- .../ClassTransformerProcessor.java | 55 +++++++--------- .../shamrock/jpa/HibernateEntityEnhancer.java | 28 ++------ .../jpa/HibernateResourceProcessor.java | 5 +- .../enhancer/HibernateEntityEnhancerTest.java | 2 +- .../org/jboss/shamrock/maven/BuildMojo.java | 19 ++---- .../shamrock/maven/runner/RunMojoMain.java | 4 ++ 10 files changed, 144 insertions(+), 122 deletions(-) diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/BuildTimeGenerator.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/BuildTimeGenerator.java index 33f5815bfbc7d..b2c0375216fc3 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/BuildTimeGenerator.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/BuildTimeGenerator.java @@ -34,6 +34,7 @@ import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; @@ -86,7 +87,7 @@ public class BuildTimeGenerator { private final DeploymentProcessorInjection injection; private final ClassLoader classLoader; private final boolean useStaticInit; - private final List>> bytecodeTransformers = new ArrayList<>(); + private final Map>> byteCodeTransformers = new HashMap<>(); private final Set applicationArchiveMarkers; private final ArchiveContextBuilder archiveContextBuilder; private final Set capabilities; @@ -110,8 +111,8 @@ public BuildTimeGenerator(ClassOutput classOutput, ClassLoader cl, boolean useSt this.capabilities = new HashSet<>(setupContext.capabilities); } - public List>> getBytecodeTransformers() { - return bytecodeTransformers; + public Map>> getByteCodeTransformers() { + return byteCodeTransformers; } public void run(Path root) throws IOException { @@ -342,8 +343,8 @@ public void createResource(String name, byte[] data) throws IOException { } @Override - public void addByteCodeTransformer(Function> visitorFunction) { - bytecodeTransformers.add(visitorFunction); + public void addByteCodeTransformer(String className, BiFunction visitorFunction) { + byteCodeTransformers.computeIfAbsent(className, (e) -> new ArrayList<>()).add(visitorFunction); } @Override @@ -438,7 +439,6 @@ void writeMainClass() throws IOException { ResultHandle dup = mv.newInstance(ofConstructor(holder.className)); mv.invokeInterfaceMethod(ofMethod(StartupTask.class, "deploy", void.class, StartupContext.class), dup, startupContext); } - mv.invokeStaticMethod(ofMethod(Timing.class, "printStartupTime", void.class)); mv.returnValue(null); diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java index 09b2e8c34c0c1..06a49ef12d8d8 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.function.BiFunction; import java.util.function.Function; import org.jboss.jandex.FieldInfo; @@ -41,9 +42,9 @@ public interface ProcessorContext { * This method is used to indicate that a given class requires reflection. *

* It is used in the graal output to allow the class to be reflected when running on substrate VM - * + *

* This will add all constructors, as well as all methods and fields if the appropriate fields are set. - * + *

* Where possible consider using the more fine grained addReflective* variants * * @param className The class name @@ -60,36 +61,35 @@ public interface ProcessorContext { /** * Attempts to register a complete type hierarchy for reflection. - * + *

* This is intended to be used to register types that are going to be serialized, * e.g. by Jackson or some other JSON mapper. - * + *

* This will do 'smart discovery' and in addition to registering the type itself it will also attempt to * register the following: - * + *

* - Superclasses * - Component types of collections * - Types used in bean properties if (if method reflection is enabled) * - Field types (if field reflection is enabled) - * + *

* This discovery is applied recursively, so any additional types that are registered will also have their dependencies * discovered - * */ void addReflectiveHierarchy(Type type); /** - * * @param applicationClass If this class should be loaded by the application class loader when in runtime mode - * @param name The class name - * @param classData The class bytes + * @param name The class name + * @param classData The class bytes * @throws IOException */ void addGeneratedClass(boolean applicationClass, String name, byte[] classData) throws IOException; /** * Creates a resources with the provided contents + * * @param name * @param data * @throws IOException @@ -97,19 +97,20 @@ public interface ProcessorContext { void createResource(String name, byte[] data) throws IOException; /** - * Adds a bytecode transformer that can transform application classes. + * Adds a bytecode transformer that can transform application classes + *

+ * This is added on a per-class basis, by specifying the class name. The transformer is a function that + * can be used to wrap an ASM {@link ClassVisitor}. *

- * This takes the form of a function that takes the class name as a String, and returns a Function that wraps an - * ASM {@link ClassVisitor}. - * - * If this function returns null then no transform is applied. If it returns a function then it will be transformed. - * * The transformation is applied by calling each function that has been registered it turn to create a chain * of visitors. These visitors are then applied and the result is saved to the output. - * + *

* At present these transformations are only applied to application classes, not classes provided by dependencies + *

+ * These transformations may be run concurrently in multiple threads, so if a function is registered for multiple + * classes it must be thread safe */ - void addByteCodeTransformer(Function> visitorFunction); + void addByteCodeTransformer(String classToTransform, BiFunction visitorFunction); /** * Adds a resource to the image that will be accessible when running under substrate. @@ -126,25 +127,24 @@ public interface ProcessorContext { * * @param classes The classes to lazily init */ - void addRuntimeInitializedClasses(String ... classes); + void addRuntimeInitializedClasses(String... classes); /** * Adds a proxy definition to allow proxies to be created using {@link java.lang.reflect.Proxy} * * @param proxyClasses The interface names that this proxy will implement */ - void addProxyDefinition(String ... proxyClasses); + void addProxyDefinition(String... proxyClasses); /** * Set a system property to be passed in to the native image tool. * - * @param name the property name (must not be {@code null}) + * @param name the property name (must not be {@code null}) * @param value the property value */ void addNativeImageSystemProperty(String name, String value); /** - * * @param capability * @return if the given capability is present * @see Capabilities diff --git a/core/deployment/src/main/java/org/jboss/shamrock/runner/RuntimeClassLoader.java b/core/deployment/src/main/java/org/jboss/shamrock/runner/RuntimeClassLoader.java index f18fd5cfb9ea1..6a1e4c56127d4 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/runner/RuntimeClassLoader.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/runner/RuntimeClassLoader.java @@ -11,7 +11,6 @@ import java.net.URLStreamHandler; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; @@ -19,25 +18,26 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Consumer; -import java.util.function.Function; import org.jboss.shamrock.deployment.ClassOutput; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; -public class RuntimeClassLoader extends ClassLoader implements ClassOutput, Consumer>>> { +public class RuntimeClassLoader extends ClassLoader implements ClassOutput, Consumer>>> { private final Map appClasses = new HashMap<>(); private final Set frameworkClasses = new HashSet<>(); private final Map resources = new HashMap<>(); - private volatile List>> functions = null; + private volatile Map>> bytecodeTransformers = null; private final Path applicationClasses; private final Path frameworkClassesPath; + static { registerAsParallelCapable(); } @@ -51,7 +51,7 @@ public RuntimeClassLoader(ClassLoader parent, Path applicationClasses, Path fram @Override public Enumeration getResources(String nm) throws IOException { String name; - if(nm.startsWith("/")) { + if (nm.startsWith("/")) { name = nm.substring(1); } else { name = nm; @@ -83,7 +83,7 @@ public InputStream getInputStream() throws IOException { @Override public URL getResource(String nm) { String name; - if(nm.startsWith("/")) { + if (nm.startsWith("/")) { name = nm.substring(1); } else { name = nm; @@ -94,7 +94,7 @@ public URL getResource(String nm) { @Override public InputStream getResourceAsStream(String nm) { String name; - if(nm.startsWith("/")) { + if (nm.startsWith("/")) { name = nm.substring(1); } else { name = nm; @@ -111,7 +111,7 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE if (appClasses.containsKey(name)) { return findClass(name); } - if(frameworkClasses.contains(name)) { + if (frameworkClasses.contains(name)) { return super.loadClass(name, resolve); } @@ -130,31 +130,33 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE } byte[] bytes = out.toByteArray(); bytes = handleTransform(name, bytes); - return defineClass(name, bytes, 0, bytes.length); + try { + return defineClass(name, bytes, 0, bytes.length); + } catch (Error e) { + //potential race conditions if another thread is loading the same class + ex = findLoadedClass(name); + if(ex != null) { + return ex; + } + throw e; + } } return super.loadClass(name, resolve); } private byte[] handleTransform(String name, byte[] bytes) { - if (functions == null || functions.isEmpty()) { + if (bytecodeTransformers == null || bytecodeTransformers.isEmpty()) { return bytes; } - List> transformers = new ArrayList<>(); - for (Function> function : this.functions) { - Function res = function.apply(name); - if (res != null) { - transformers.add(res); - } - } - if (transformers.isEmpty()) { + List> transformers = bytecodeTransformers.get(name); + if (transformers == null) { return bytes; } - ClassReader cr = new ClassReader(bytes); ClassWriter writer = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); ClassVisitor visitor = writer; - for (Function i : transformers) { - visitor = i.apply(visitor); + for (BiFunction i : transformers) { + visitor = i.apply(name, visitor); } cr.accept(visitor, 0); byte[] data = writer.toByteArray(); @@ -163,11 +165,24 @@ private byte[] handleTransform(String name, byte[] bytes) { @Override protected Class findClass(String name) throws ClassNotFoundException { + Class existing = findLoadedClass(name); + if(existing != null) { + return existing; + } byte[] bytes = appClasses.get(name); if (bytes == null) { throw new ClassNotFoundException(name); } - return defineClass(name, bytes, 0, bytes.length); + try { + return defineClass(name, bytes, 0, bytes.length); + } catch (Error e) { + //potential race conditions if another thread is loading the same class + existing = findLoadedClass(name); + if(existing != null) { + return existing; + } + throw e; + } } @Override @@ -184,7 +199,7 @@ public void writeClass(boolean applicationClass, String className, byte[] data) Path fileName = frameworkClassesPath.resolve(className.replace(".", "/") + ".class"); try { Files.createDirectories(fileName.getParent()); - try(FileOutputStream out = new FileOutputStream(fileName.toFile())) { + try (FileOutputStream out = new FileOutputStream(fileName.toFile())) { out.write(data); } } catch (IOException e) { @@ -193,8 +208,8 @@ public void writeClass(boolean applicationClass, String className, byte[] data) } } - public void accept(List>> functions) { - this.functions = functions; + public void accept(Map>> functions) { + this.bytecodeTransformers = functions; } public void writeResource(String name, byte[] data) throws IOException { diff --git a/core/deployment/src/main/java/org/jboss/shamrock/runner/RuntimeRunner.java b/core/deployment/src/main/java/org/jboss/shamrock/runner/RuntimeRunner.java index 812d97c552120..82f144656e1c5 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/runner/RuntimeRunner.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/runner/RuntimeRunner.java @@ -5,9 +5,17 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.BiFunction; +import java.util.function.Function; import org.jboss.shamrock.deployment.ArchiveContextBuilder; import org.jboss.shamrock.deployment.BuildTimeGenerator; +import org.jboss.shamrock.runtime.Timing; +import org.objectweb.asm.ClassVisitor; /** * Class that can be used to run shamrock directly, ececuting the build and runtime @@ -38,7 +46,29 @@ public void run() { try { BuildTimeGenerator buildTimeGenerator = new BuildTimeGenerator(loader, loader, false, archiveContextBuilder); buildTimeGenerator.run(target); - loader.accept(buildTimeGenerator.getBytecodeTransformers()); + loader.accept(buildTimeGenerator.getByteCodeTransformers()); + if(!buildTimeGenerator.getByteCodeTransformers().isEmpty()) { + //transformation can be slow, and classes that are transformed are generally always loaded on startup + //to speed this along we eagerly load the classes in parallel + + ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + for(Map.Entry>> entry : buildTimeGenerator.getByteCodeTransformers().entrySet()) { + executorService.submit(new Runnable() { + @Override + public void run() { + try { + loader.loadClass(entry.getKey(), true); + } catch (ClassNotFoundException e) { + //ignore + //this will show up at runtime anyway + } + } + }); + } + executorService.shutdown(); + + } + Class mainClass = loader.findClass(BuildTimeGenerator.MAIN_CLASS); ClassLoader old = Thread.currentThread().getContextClassLoader(); try { diff --git a/examples/class-transformer/src/main/java/org/jboss/shamrock/example/classtransformer/ClassTransformerProcessor.java b/examples/class-transformer/src/main/java/org/jboss/shamrock/example/classtransformer/ClassTransformerProcessor.java index d568f8f5c25d9..126b20c40410a 100644 --- a/examples/class-transformer/src/main/java/org/jboss/shamrock/example/classtransformer/ClassTransformerProcessor.java +++ b/examples/class-transformer/src/main/java/org/jboss/shamrock/example/classtransformer/ClassTransformerProcessor.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Function; import org.jboss.jandex.AnnotationInstance; @@ -39,39 +40,33 @@ public void process(ArchiveContext archiveContext, ProcessorContext processorCon } } if (!pathAnnotatedClasses.isEmpty()) { - processorContext.addByteCodeTransformer(new Function>() { - @Override - public Function apply(String s) { - if (!pathAnnotatedClasses.contains(s)) { - return null; - } - return new Function() { - @Override - public ClassVisitor apply(ClassVisitor classVisitor) { - ClassVisitor cv = new ClassVisitor(Opcodes.ASM6, classVisitor) { + for (String i : pathAnnotatedClasses) { + processorContext.addByteCodeTransformer(i, new BiFunction() { + @Override + public ClassVisitor apply(String className, ClassVisitor classVisitor) { + ClassVisitor cv = new ClassVisitor(Opcodes.ASM6, classVisitor) { - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - super.visit(version, access, name, signature, superName, interfaces); - MethodVisitor mv = visitMethod(Modifier.PUBLIC, "transformed", "()Ljava/lang/String;", null, null); + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + MethodVisitor mv = visitMethod(Modifier.PUBLIC, "transformed", "()Ljava/lang/String;", null, null); - AnnotationVisitor annotation = mv.visitAnnotation("Ljavax/ws/rs/Path;", true); - annotation.visit("value", "/transformed"); - annotation.visitEnd(); - annotation = mv.visitAnnotation("Ljavax/ws/rs/GET;", true); - annotation.visitEnd(); + AnnotationVisitor annotation = mv.visitAnnotation("Ljavax/ws/rs/Path;", true); + annotation.visit("value", "/transformed"); + annotation.visitEnd(); + annotation = mv.visitAnnotation("Ljavax/ws/rs/GET;", true); + annotation.visitEnd(); - mv.visitLdcInsn("Transformed Endpoint"); - mv.visitInsn(Opcodes.ARETURN); - mv.visitMaxs(1, 1); - mv.visitEnd(); - } - }; - return cv; - } - }; - } - }); + mv.visitLdcInsn("Transformed Endpoint"); + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + }; + return cv; + } + }); + } } } diff --git a/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/HibernateEntityEnhancer.java b/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/HibernateEntityEnhancer.java index 70f9dc3cd77c5..6c193fed36ef3 100644 --- a/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/HibernateEntityEnhancer.java +++ b/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/HibernateEntityEnhancer.java @@ -1,6 +1,7 @@ package org.jboss.shamrock.jpa; import java.util.Objects; +import java.util.function.BiFunction; import java.util.function.Function; import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; @@ -22,7 +23,7 @@ * * @author Sanne Grinovero */ -public final class HibernateEntityEnhancer implements Function> { +public final class HibernateEntityEnhancer implements BiFunction { private final Enhancer enhancer; private final KnownDomainObjects classnameWhitelist; @@ -41,29 +42,8 @@ public ClassLoader getLoadingClassLoader() { } @Override - public Function apply(String classname) { - if (classnameWhitelist.contains(classname)) - return new HibernateTransformingVisitorFunction(classname); - else - return null; - } - - /** - * Having to convert a ClassVisitor into another, this allows visitor chaining: the returned ClassVisitor needs to - * refer to the previous ClassVisitor in the chain to forward input events (optionally transformed). - */ - private class HibernateTransformingVisitorFunction implements Function { - - private final String className; - - public HibernateTransformingVisitorFunction(String className) { - this.className = className; - } - - @Override - public ClassVisitor apply(ClassVisitor outputClassVisitor) { - return new HibernateEnhancingClassVisitor(className, outputClassVisitor); - } + public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { + return new HibernateEnhancingClassVisitor(className, outputClassVisitor); } private class HibernateEnhancingClassVisitor extends ClassVisitor { diff --git a/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/HibernateResourceProcessor.java b/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/HibernateResourceProcessor.java index ef52387fdd1e8..776811b0d7755 100644 --- a/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/HibernateResourceProcessor.java +++ b/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/HibernateResourceProcessor.java @@ -71,7 +71,10 @@ public void process(final ArchiveContext archiveContext, final ProcessorContext } private void enhanceEntities(final KnownDomainObjects domainObjects, ArchiveContext archiveContext, ProcessorContext processorContext) { - processorContext.addByteCodeTransformer(new HibernateEntityEnhancer(domainObjects)); + HibernateEntityEnhancer hibernateEntityEnhancer = new HibernateEntityEnhancer(domainObjects); + for(String i : domainObjects.getClassNames()) { + processorContext.addByteCodeTransformer(i, hibernateEntityEnhancer); + } } @Override diff --git a/jpa/deployment/src/test/java/org/jboss/shamrock/jpa/enhancer/HibernateEntityEnhancerTest.java b/jpa/deployment/src/test/java/org/jboss/shamrock/jpa/enhancer/HibernateEntityEnhancerTest.java index e00b24cf3d85f..4e456b0b0b6f9 100644 --- a/jpa/deployment/src/test/java/org/jboss/shamrock/jpa/enhancer/HibernateEntityEnhancerTest.java +++ b/jpa/deployment/src/test/java/org/jboss/shamrock/jpa/enhancer/HibernateEntityEnhancerTest.java @@ -38,7 +38,7 @@ public void testBytecodeEnhancement() throws IOException, ClassNotFoundException ClassWriter writer = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); ClassVisitor visitor = writer; HibernateEntityEnhancer hibernateEntityEnhancer = new HibernateEntityEnhancer(new TestingKnownDomainObjects()); - visitor = hibernateEntityEnhancer.apply(TEST_CLASSNAME).apply(visitor); + visitor = hibernateEntityEnhancer.apply(TEST_CLASSNAME, visitor); classReader.accept(visitor, 0); final byte[] modifiedBytecode = writer.toByteArray(); diff --git a/maven/src/main/java/org/jboss/shamrock/maven/BuildMojo.java b/maven/src/main/java/org/jboss/shamrock/maven/BuildMojo.java index 42d48a8cd1f50..f203b01a63ced 100644 --- a/maven/src/main/java/org/jboss/shamrock/maven/BuildMojo.java +++ b/maven/src/main/java/org/jboss/shamrock/maven/BuildMojo.java @@ -2,7 +2,6 @@ import java.io.BufferedOutputStream; import java.io.BufferedReader; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -27,6 +26,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.jar.Attributes; @@ -280,16 +280,11 @@ public void accept(Path path) { // if (!pathName.isEmpty()) { // out.putNextEntry(new ZipEntry(pathName + "/")); // } - } else if (pathName.endsWith(".class") && !buildTimeGenerator.getBytecodeTransformers().isEmpty()) { + } else if (pathName.endsWith(".class") && !buildTimeGenerator.getByteCodeTransformers().isEmpty()) { String className = pathName.substring(0, pathName.length() - 6).replace("/", "."); - List> visitors = new ArrayList<>(); - for (Function> t : buildTimeGenerator.getBytecodeTransformers()) { - Function visitor = t.apply(className); - if (visitor != null) { - visitors.add(visitor); - } - } - if (visitors.isEmpty()) { + List> visitors = buildTimeGenerator.getByteCodeTransformers().get(className); + + if (visitors == null || visitors.isEmpty()) { runner.putNextEntry(new ZipEntry(pathName)); try (FileInputStream in = new FileInputStream(path.toFile())) { doCopy(runner, in); @@ -302,8 +297,8 @@ public FutureEntry call() throws Exception { ClassReader cr = new ClassReader(fileContent); ClassWriter writer = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); ClassVisitor visitor = writer; - for (Function i : visitors) { - visitor = i.apply(visitor); + for (BiFunction i : visitors) { + visitor = i.apply(className, visitor); } cr.accept(visitor, 0); return new FutureEntry(writer.toByteArray(), pathName); diff --git a/maven/src/main/java/org/jboss/shamrock/maven/runner/RunMojoMain.java b/maven/src/main/java/org/jboss/shamrock/maven/runner/RunMojoMain.java index f71b98fea425a..e1515c819c625 100644 --- a/maven/src/main/java/org/jboss/shamrock/maven/runner/RunMojoMain.java +++ b/maven/src/main/java/org/jboss/shamrock/maven/runner/RunMojoMain.java @@ -8,6 +8,7 @@ import java.net.URLClassLoader; import java.nio.file.Path; +import org.jboss.logging.Logger; import org.jboss.shamrock.deployment.ArchiveContextBuilder; import org.jboss.shamrock.runtime.Timing; @@ -16,6 +17,8 @@ */ public class RunMojoMain { + private static final Logger log = Logger.getLogger(RunMojoMain.class); + private static volatile boolean keepCl = false; private static volatile ClassLoader currentAppClassLoader; private static volatile URLClassLoader runtimeCl; @@ -71,6 +74,7 @@ private static synchronized void doStart() { } } catch (Throwable t) { deploymentProblem = t; + log.error("Failed to start shamrock", t); } }