diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java index 12c3d7012a..907b2ee9c3 100644 --- a/core/src/main/java/hudson/ClassicPluginStrategy.java +++ b/core/src/main/java/hudson/ClassicPluginStrategy.java @@ -38,6 +38,7 @@ import jenkins.plugins.DetachedPluginsUtil; import jenkins.util.AntClassLoader; import jenkins.util.AntWithFindResourceClassLoader; import jenkins.util.SystemProperties; +import jenkins.util.java.ClassNotFoundNoStackTraceException; import org.apache.commons.io.output.NullOutputStream; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; @@ -55,6 +56,9 @@ import org.apache.tools.zip.ZipOutputStream; import org.jenkinsci.bytecode.Transformer; import edu.umd.cs.findbugs.annotations.NonNull; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.Beta; + import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; @@ -92,6 +96,7 @@ public class ClassicPluginStrategy implements PluginStrategy { }; private PluginManager pluginManager; + private boolean fillInStackTracesOnClassNotFoundException; /** * All the plugins eventually delegate this classloader to load core, servlet APIs, and SE runtime. @@ -102,6 +107,19 @@ public class ClassicPluginStrategy implements PluginStrategy { this.pluginManager = pluginManager; } + /** + * Sets whether the classloader should include stacktraces on {@link ClassNotFoundException}s. + * + * @param fillInStackTraces {@code true} to fill in stacktraces. + * The classloader does not inject stacktraces by default. + * @since TODO + */ + @Restricted(Beta.class) + public ClassicPluginStrategy withFillInStackTracesOnClassNotFoundException(boolean fillInStackTraces) { + this.fillInStackTracesOnClassNotFoundException = fillInStackTraces; + return this; + } + @Override public String getShortName(File archive) throws IOException { Manifest manifest; if (!archive.exists()) { @@ -296,11 +314,13 @@ public class ClassicPluginStrategy implements PluginStrategy { classLoader.setParentFirst( false ); classLoader.setParent( parent ); classLoader.addPathFiles( paths ); + classLoader.withFillInStackTracesOnClassNotFound(fillInStackTracesOnClassNotFoundException); return classLoader; } } AntClassLoader2 classLoader = new AntClassLoader2(parent); + classLoader.withFillInStackTracesOnClassNotFound(fillInStackTracesOnClassNotFoundException); classLoader.addPathFiles(paths); return classLoader; } @@ -649,7 +669,8 @@ public class ClassicPluginStrategy implements PluginStrategy { } } - throw new ClassNotFoundException(name); + throw fillInStackTracesOnClassNotFoundException ? + new ClassNotFoundException(name) : new ClassNotFoundNoStackTraceException(name); } @Override diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 1507548052..323692b18e 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -1213,8 +1213,13 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas LOGGER.info("Falling back to ClassicPluginStrategy"); } - // default and fallback - return new ClassicPluginStrategy(this); + // Default and fallback strategy + // The default Plugin Manager implementation does not propagate/log stacktraces for ClassNotFoundExceptions, + // hence stacktraces are omitted by default for better performance. + boolean fillInClassNotFoundExceptionStackTraces =SystemProperties.getBoolean( + ClassicPluginStrategy.class.getName() + ".fillInClassNotFoundExceptionStackTraces", false); + return new ClassicPluginStrategy(this) + .withFillInStackTracesOnClassNotFoundException(fillInClassNotFoundExceptionStackTraces); } public PluginStrategy getPluginStrategy() { diff --git a/core/src/main/java/jenkins/util/AntClassLoader.java b/core/src/main/java/jenkins/util/AntClassLoader.java index 7843abb69a..f14269d391 100644 --- a/core/src/main/java/jenkins/util/AntClassLoader.java +++ b/core/src/main/java/jenkins/util/AntClassLoader.java @@ -18,6 +18,7 @@ package jenkins.util; import jenkins.telemetry.impl.java11.MissingClassTelemetry; +import jenkins.util.java.ClassNotFoundNoStackTraceException; import org.apache.tools.ant.BuildEvent; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; @@ -30,6 +31,7 @@ import org.apache.tools.ant.util.JavaEnvUtils; import org.apache.tools.ant.util.LoaderUtils; import org.apache.tools.ant.util.ReflectUtil; import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.Beta; import org.kohsuke.accmod.restrictions.NoExternalUse; import edu.umd.cs.findbugs.annotations.CheckForNull; @@ -241,6 +243,12 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener { */ private boolean isContextLoaderSaved = false; + /** + * Whether or not to include stacktraces in {@link ClassNotFoundException}. + * @since TODO + */ + private boolean fillInStackTracesOnClassNotFoundException; + /** * Create an Ant ClassLoader for a given project, with * a parent classloader and an initial classpath. @@ -568,6 +576,19 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener { ignoreBase = isolated; } + /** + * Sets whether the classloader should include stacktraces on {@link ClassNotFoundException}s. + * + * @param fillInStackTraces {@code true} to fill in stacktraces. + * The classloader does not add stacktraces by default. + * @since TODO + */ + @Restricted(Beta.class) + public AntClassLoader withFillInStackTracesOnClassNotFound(boolean fillInStackTraces) { + this.fillInStackTracesOnClassNotFoundException = fillInStackTraces; + return this; + } + /** * Forces initialization of a class in a JDK 1.1 compatible, albeit hacky * way. @@ -1384,7 +1405,9 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener { + ioe.getMessage() + ")", Project.MSG_VERBOSE); } } - throw new ClassNotFoundException(name); + + throw fillInStackTracesOnClassNotFoundException + ? new ClassNotFoundException(name) : new ClassNotFoundNoStackTraceException(name); } /** diff --git a/core/src/main/java/jenkins/util/java/ClassNotFoundNoStackTraceException.java b/core/src/main/java/jenkins/util/java/ClassNotFoundNoStackTraceException.java new file mode 100644 index 0000000000..34ade95ad8 --- /dev/null +++ b/core/src/main/java/jenkins/util/java/ClassNotFoundNoStackTraceException.java @@ -0,0 +1,43 @@ +/* + * The MIT License + * + * Copyright (c) 2020 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.util.java; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +/** + * {@link ClassNotFoundException} which does not store the stacktrace on initialization. + * It improves classloading performance when the stacktrace is ignored, + * e.g. like in {@link hudson.ClassicPluginStrategy}. + */ +@Restricted(NoExternalUse.class) +public class ClassNotFoundNoStackTraceException extends ClassNotFoundException { + public ClassNotFoundNoStackTraceException(String className) { + super(className); + } + + public Throwable fillInStackTrace() { + return this; + } +}