Add closeClassLoader setting

There have been a number of issues that have come up because of sbt
1.3.0 aggressively closing classloaders. While these issues have been
quite useful in helping us determine some issues related to classloader
lifecycle, we should give users the option to prevent sbt from closing
the classloaders.

I also noticed that the classloader-cache/spark test has been
occasionally segfaulting on travis so I disable classloader closing in
that test.
This commit is contained in:
Ethan Atkins 2019-12-12 16:52:25 -08:00
parent 18c6264656
commit a177c386c0
10 changed files with 33 additions and 13 deletions

View File

@ -32,9 +32,10 @@ final class BottomClassLoader extends ManagedClassLoader {
final URL[] dynamicClasspath, final URL[] dynamicClasspath,
final ReverseLookupClassLoader reverseLookupClassLoader, final ReverseLookupClassLoader reverseLookupClassLoader,
final File tempDir, final File tempDir,
final boolean close,
final boolean allowZombies, final boolean allowZombies,
final Logger logger) { final Logger logger) {
super(dynamicClasspath, reverseLookupClassLoader, allowZombies, logger); super(dynamicClasspath, reverseLookupClassLoader, close, allowZombies, logger);
setTempDir(tempDir); setTempDir(tempDir);
this.holder = holder; this.holder = holder;
this.parent = reverseLookupClassLoader; this.parent = reverseLookupClassLoader;

View File

@ -20,9 +20,10 @@ final class FlatLoader extends ManagedClassLoader {
final URL[] urls, final URL[] urls,
final ClassLoader parent, final ClassLoader parent,
final File file, final File file,
final boolean close,
final boolean allowZombies, final boolean allowZombies,
final Logger logger) { final Logger logger) {
super(urls, parent, allowZombies, logger); super(urls, parent, close, allowZombies, logger);
setTempDir(file); setTempDir(file);
} }

View File

@ -12,9 +12,9 @@ import java.net.URL;
import sbt.util.Logger; import sbt.util.Logger;
final class LayeredClassLoader extends ManagedClassLoader { final class LayeredClassLoader extends ManagedClassLoader {
LayeredClassLoader(final URL[] classpath, final ClassLoader parent, final File tempDir, final LayeredClassLoader(final URL[] classpath, final ClassLoader parent, final File tempDir, final boolean close, final
boolean allowZombies, final Logger logger) { boolean allowZombies, final Logger logger) {
super(classpath, parent, allowZombies, logger); super(classpath, parent, close, allowZombies, logger);
setTempDir(tempDir); setTempDir(tempDir);
} }

View File

@ -20,6 +20,7 @@ abstract class ManagedClassLoader extends URLClassLoader implements NativeLoader
private final AtomicBoolean closed = new AtomicBoolean(false); private final AtomicBoolean closed = new AtomicBoolean(false);
private final AtomicBoolean printedWarning = new AtomicBoolean(false); private final AtomicBoolean printedWarning = new AtomicBoolean(false);
private final AtomicReference<ZombieClassLoader> zombieLoader = new AtomicReference<>(); private final AtomicReference<ZombieClassLoader> zombieLoader = new AtomicReference<>();
private final boolean close;
private final boolean allowZombies; private final boolean allowZombies;
private final Logger logger; private final Logger logger;
private final NativeLookup nativeLookup = new NativeLookup(); private final NativeLookup nativeLookup = new NativeLookup();
@ -29,8 +30,9 @@ abstract class ManagedClassLoader extends URLClassLoader implements NativeLoader
} }
ManagedClassLoader( ManagedClassLoader(
final URL[] urls, final ClassLoader parent, final boolean allowZombies, final Logger logger) { final URL[] urls, final ClassLoader parent, final boolean close, final boolean allowZombies, final Logger logger) {
super(urls, parent); super(urls, parent);
this.close = close;
this.allowZombies = allowZombies; this.allowZombies = allowZombies;
this.logger = logger; this.logger = logger;
} }
@ -103,8 +105,8 @@ abstract class ManagedClassLoader extends URLClassLoader implements NativeLoader
@Override @Override
public void close() throws IOException { public void close() throws IOException {
final ZombieClassLoader zb = zombieLoader.getAndSet(null); final ZombieClassLoader zb = zombieLoader.getAndSet(null);
if (zb != null) zb.close(); if (zb != null && close) zb.close();
if (closed.compareAndSet(false, true)) super.close(); if (close && closed.compareAndSet(false, true)) super.close();
} }
@Override @Override

View File

@ -36,8 +36,12 @@ import sbt.util.Logger;
*/ */
final class ReverseLookupClassLoader extends ManagedClassLoader { final class ReverseLookupClassLoader extends ManagedClassLoader {
ReverseLookupClassLoader( ReverseLookupClassLoader(
final URL[] urls, final ClassLoader parent, final boolean allowZombies, final Logger logger) { final URL[] urls,
super(urls, parent, allowZombies, logger); final ClassLoader parent,
final boolean close,
final boolean allowZombies,
final Logger logger) {
super(urls, parent, close, allowZombies, logger);
this.parent = parent; this.parent = parent;
} }

View File

@ -205,7 +205,8 @@ object Defaults extends BuildCommon {
bgStop := bgStopTask.evaluated, bgStop := bgStopTask.evaluated,
bgWaitFor := bgWaitForTask.evaluated, bgWaitFor := bgWaitForTask.evaluated,
bgCopyClasspath :== true, bgCopyClasspath :== true,
allowZombieClassLoaders :== !SysProp.closeClassLoaders, closeClassLoaders :== SysProp.closeClassLoaders,
allowZombieClassLoaders :== true,
) )
private[sbt] lazy val globalIvyCore: Seq[Setting[_]] = private[sbt] lazy val globalIvyCore: Seq[Setting[_]] =

View File

@ -327,6 +327,7 @@ object Keys {
val dependencyClasspathAsJars = taskKey[Classpath]("The classpath consisting of internal and external, managed and unmanaged dependencies, all as JARs.") val dependencyClasspathAsJars = taskKey[Classpath]("The classpath consisting of internal and external, managed and unmanaged dependencies, all as JARs.")
val fullClasspathAsJars = taskKey[Classpath]("The exported classpath, consisting of build products and unmanaged and managed, internal and external dependencies, all as JARs.") val fullClasspathAsJars = taskKey[Classpath]("The exported classpath, consisting of build products and unmanaged and managed, internal and external dependencies, all as JARs.")
val internalDependencyConfigurations = settingKey[Seq[(ProjectRef, Set[String])]]("The project configurations that this configuration depends on") val internalDependencyConfigurations = settingKey[Seq[(ProjectRef, Set[String])]]("The project configurations that this configuration depends on")
val closeClassLoaders = settingKey[Boolean]("Close classloaders in run and test when the task completes.").withRank(DSetting)
val allowZombieClassLoaders = settingKey[Boolean]("Allow a classloader that has previously been closed by `run` or `test` to continue loading classes.") val allowZombieClassLoaders = settingKey[Boolean]("Allow a classloader that has previously been closed by `run` or `test` to continue loading classes.")
val useCoursier = settingKey[Boolean]("Use Coursier for dependency resolution.").withRank(BSetting) val useCoursier = settingKey[Boolean]("Use Coursier for dependency resolution.").withRank(BSetting)

View File

@ -44,6 +44,7 @@ private[sbt] object ClassLoaders {
else si.libraryJars.map(j => j -> IO.getModifiedTimeOrZero(j)).toSeq ++ rawCP else si.libraryJars.map(j => j -> IO.getModifiedTimeOrZero(j)).toSeq ++ rawCP
val exclude = dependencyJars(exportedProducts).value.toSet ++ si.libraryJars val exclude = dependencyJars(exportedProducts).value.toSet ++ si.libraryJars
val logger = state.value.globalLogging.full val logger = state.value.globalLogging.full
val close = closeClassLoaders.value
val allowZombies = allowZombieClassLoaders.value val allowZombies = allowZombieClassLoaders.value
buildLayers( buildLayers(
strategy = classLoaderLayeringStrategy.value, strategy = classLoaderLayeringStrategy.value,
@ -55,6 +56,7 @@ private[sbt] object ClassLoaders {
tmp = IO.createUniqueDirectory(taskTemporaryDirectory.value), tmp = IO.createUniqueDirectory(taskTemporaryDirectory.value),
scope = resolvedScoped.value.scope, scope = resolvedScoped.value.scope,
logger = logger, logger = logger,
close = close,
allowZombies = allowZombies, allowZombies = allowZombies,
) )
} }
@ -88,6 +90,7 @@ private[sbt] object ClassLoaders {
val allDeps = dependencyJars(dependencyClasspath).value.filterNot(exclude) val allDeps = dependencyJars(dependencyClasspath).value.filterNot(exclude)
val logger = state.value.globalLogging.full val logger = state.value.globalLogging.full
val allowZombies = allowZombieClassLoaders.value val allowZombies = allowZombieClassLoaders.value
val close = closeClassLoaders.value
val newLoader = val newLoader =
(classpath: Seq[File]) => { (classpath: Seq[File]) => {
val mappings = classpath.map(f => f.getName -> f).toMap val mappings = classpath.map(f => f.getName -> f).toMap
@ -102,6 +105,7 @@ private[sbt] object ClassLoaders {
tmp = taskTemporaryDirectory.value: @sbtUnchecked, tmp = taskTemporaryDirectory.value: @sbtUnchecked,
scope = resolvedScope, scope = resolvedScope,
logger = logger, logger = logger,
close = close,
allowZombies = allowZombies, allowZombies = allowZombies,
) )
} }
@ -136,11 +140,12 @@ private[sbt] object ClassLoaders {
tmp: File, tmp: File,
scope: Scope, scope: Scope,
logger: Logger, logger: Logger,
close: Boolean,
allowZombies: Boolean allowZombies: Boolean
): ClassLoader = { ): ClassLoader = {
val cpFiles = fullCP.map(_._1) val cpFiles = fullCP.map(_._1)
strategy match { strategy match {
case Flat => new FlatLoader(cpFiles.urls, interfaceLoader, tmp, allowZombies, logger) case Flat => new FlatLoader(cpFiles.urls, interfaceLoader, tmp, close, allowZombies, logger)
case _ => case _ =>
val layerDependencies = strategy match { val layerDependencies = strategy match {
case _: AllLibraryJars => true case _: AllLibraryJars => true
@ -206,6 +211,7 @@ private[sbt] object ClassLoaders {
new ReverseLookupClassLoaderHolder( new ReverseLookupClassLoaderHolder(
otherDependencies, otherDependencies,
frameworkLayer, frameworkLayer,
close,
allowZombies, allowZombies,
logger logger
) )
@ -225,7 +231,7 @@ private[sbt] object ClassLoaders {
cl.getParent match { cl.getParent match {
case dl: ReverseLookupClassLoaderHolder => dl.checkout(dynamicClasspath, tmp) case dl: ReverseLookupClassLoaderHolder => dl.checkout(dynamicClasspath, tmp)
case _ => case _ =>
new LayeredClassLoader(dynamicClasspath.urls, cl, tmp, allowZombies, logger) new LayeredClassLoader(dynamicClasspath.urls, cl, tmp, close, allowZombies, logger)
} }
} }
} }

View File

@ -42,6 +42,7 @@ import scala.collection.JavaConverters._
private[internal] final class ReverseLookupClassLoaderHolder( private[internal] final class ReverseLookupClassLoaderHolder(
val classpath: Seq[File], val classpath: Seq[File],
val parent: ClassLoader, val parent: ClassLoader,
val closeThis: Boolean,
val allowZombies: Boolean, val allowZombies: Boolean,
val logger: Logger val logger: Logger
) extends URLClassLoader(Array.empty, null) { ) extends URLClassLoader(Array.empty, null) {
@ -62,7 +63,7 @@ private[internal] final class ReverseLookupClassLoaderHolder(
throw new IllegalStateException(msg) throw new IllegalStateException(msg)
} }
val reverseLookupClassLoader = cached.getAndSet(null) match { val reverseLookupClassLoader = cached.getAndSet(null) match {
case null => new ReverseLookupClassLoader(urls, parent, allowZombies, logger) case null => new ReverseLookupClassLoader(urls, parent, closeThis, allowZombies, logger)
case c => c case c => c
} }
reverseLookupClassLoader.setup(tempDir) reverseLookupClassLoader.setup(tempDir)
@ -71,6 +72,7 @@ private[internal] final class ReverseLookupClassLoaderHolder(
dynamicClasspath.map(_.toURI.toURL).toArray, dynamicClasspath.map(_.toURI.toURL).toArray,
reverseLookupClassLoader, reverseLookupClassLoader,
tempDir, tempDir,
closeThis,
allowZombies, allowZombies,
logger logger
) )

View File

@ -5,3 +5,5 @@ version := "1.0"
scalaVersion := "2.12.10" scalaVersion := "2.12.10"
libraryDependencies += "org.apache.spark" %% "spark-sql" % "2.4.3" libraryDependencies += "org.apache.spark" %% "spark-sql" % "2.4.3"
Compile / closeClassLoaders := false