mirror of https://github.com/sbt/sbt.git
Add the ability to resurrect closed classloaders
There have been a number of complaints about the new classloader closing behavior. It is too aggressive about closing classloaders after test and run. This commit softens the behavior by allowing a classloader to be resurrected after close by creating a new zombie classloader that has the same urls as the original classloader. After this commit, we always close the classloaders when we are done, but they can still leak file descriptors if a zombie is created. To configure the behavior, I add the allowZombieClassLoaders key. If it is false (which is default), we will warn but still allow them. If it is true, then we silence the warning. In a later version of sbt, we can change the semantics to be strict. I verified after this change that I could add a shutdown hook in `run` and it would be evaluated so long as I set `bgCopyClasspath := false`. Otherwise the needed jars were deleted before the hooks could run. Bonus: delete unused ResourceLoaderImpl class
This commit is contained in:
parent
46eaa564d5
commit
231d7966d0
|
|
@ -672,12 +672,14 @@ lazy val mainProj = (project in file("main"))
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.internal.KeyIndex.*"),
|
exclude[ReversedMissingMethodProblem]("sbt.internal.KeyIndex.*"),
|
||||||
// internal
|
// internal
|
||||||
exclude[IncompatibleMethTypeProblem]("sbt.internal.server.LanguageServerReporter.*"),
|
exclude[IncompatibleMethTypeProblem]("sbt.internal.server.LanguageServerReporter.*"),
|
||||||
|
|
||||||
// Changed signature or removed private[sbt] methods
|
// Changed signature or removed private[sbt] methods
|
||||||
exclude[DirectMissingMethodProblem]("sbt.Classpaths.unmanagedLibs0"),
|
exclude[DirectMissingMethodProblem]("sbt.Classpaths.unmanagedLibs0"),
|
||||||
exclude[DirectMissingMethodProblem]("sbt.Defaults.allTestGroupsTask"),
|
exclude[DirectMissingMethodProblem]("sbt.Defaults.allTestGroupsTask"),
|
||||||
exclude[DirectMissingMethodProblem]("sbt.Plugins.topologicalSort"),
|
exclude[DirectMissingMethodProblem]("sbt.Plugins.topologicalSort"),
|
||||||
exclude[IncompatibleMethTypeProblem]("sbt.Defaults.allTestGroupsTask"),
|
exclude[IncompatibleMethTypeProblem]("sbt.Defaults.allTestGroupsTask"),
|
||||||
exclude[DirectMissingMethodProblem]("sbt.StandardMain.shutdownHook")
|
exclude[DirectMissingMethodProblem]("sbt.StandardMain.shutdownHook"),
|
||||||
|
exclude[MissingClassProblem]("sbt.internal.ResourceLoaderImpl"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.configure(
|
.configure(
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,23 @@
|
||||||
|
|
||||||
package sbt.internal;
|
package sbt.internal;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import sbt.util.Logger;
|
||||||
|
import scala.collection.Seq;
|
||||||
|
|
||||||
final class FlatLoader extends URLClassLoader {
|
final class FlatLoader extends LayeredClassLoaderImpl {
|
||||||
static {
|
static {
|
||||||
ClassLoader.registerAsParallelCapable();
|
ClassLoader.registerAsParallelCapable();
|
||||||
}
|
}
|
||||||
|
|
||||||
FlatLoader(final URL[] urls, final ClassLoader parent) {
|
FlatLoader(
|
||||||
super(urls, parent);
|
final Seq<File> files,
|
||||||
|
final ClassLoader parent,
|
||||||
|
final File file,
|
||||||
|
final boolean allowZombies,
|
||||||
|
final Logger logger) {
|
||||||
|
super(files, parent, file, allowZombies, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -30,9 +36,4 @@ final class FlatLoader extends URLClassLoader {
|
||||||
}
|
}
|
||||||
return "FlatLoader(\n parent = " + getParent() + "\n jars = " + jars.toString() + ")";
|
return "FlatLoader(\n parent = " + getParent() + "\n jars = " + jars.toString() + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
if (SysProp.closeClassLoaders()) super.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,13 @@
|
||||||
package sbt.internal;
|
package sbt.internal;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import sbt.util.Logger;
|
||||||
import scala.collection.Seq;
|
import scala.collection.Seq;
|
||||||
|
|
||||||
final class LayeredClassLoader extends LayeredClassLoaderImpl {
|
final class LayeredClassLoader extends LayeredClassLoaderImpl {
|
||||||
LayeredClassLoader(
|
LayeredClassLoader(final Seq<File> classpath, final ClassLoader parent, final File tempDir, final
|
||||||
final Seq<File> classpath,
|
boolean allowZombies, final Logger logger) {
|
||||||
final ClassLoader parent,
|
super(classpath, parent, tempDir, allowZombies, logger);
|
||||||
final File tempDir) {
|
|
||||||
super(classpath, parent, tempDir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
package sbt.internal;
|
package sbt.internal;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
|
|
||||||
|
|
@ -32,9 +31,4 @@ final class ScalaLibraryClassLoader extends URLClassLoader {
|
||||||
}
|
}
|
||||||
return "ScalaLibraryClassLoader(" + builder + " parent = " + getParent() + ")";
|
return "ScalaLibraryClassLoader(" + builder + " parent = " + getParent() + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
if (SysProp.closeClassLoaders()) super.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
package sbt.internal;
|
package sbt.internal;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
|
|
||||||
|
|
@ -27,9 +26,4 @@ final class ScalaReflectClassLoader extends URLClassLoader {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ScalaReflectClassLoader(" + jar + " parent = " + getParent() + ")";
|
return "ScalaReflectClassLoader(" + jar + " parent = " + getParent() + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
if (SysProp.closeClassLoaders()) super.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import sbt.internal.CommandStrings.ExportStream
|
||||||
import sbt.internal._
|
import sbt.internal._
|
||||||
import sbt.internal.classpath.AlternativeZincUtil
|
import sbt.internal.classpath.AlternativeZincUtil
|
||||||
import sbt.internal.inc.JavaInterfaceUtil._
|
import sbt.internal.inc.JavaInterfaceUtil._
|
||||||
import sbt.internal.inc.classpath.ClasspathFilter
|
import sbt.internal.inc.classpath.{ ClassLoaderCache, ClasspathFilter }
|
||||||
import sbt.internal.inc.{ ZincLmUtil, ZincUtil }
|
import sbt.internal.inc.{ ZincLmUtil, ZincUtil }
|
||||||
import sbt.internal.io.{ Source, WatchState }
|
import sbt.internal.io.{ Source, WatchState }
|
||||||
import sbt.internal.librarymanagement.mavenint.{
|
import sbt.internal.librarymanagement.mavenint.{
|
||||||
|
|
@ -47,7 +47,6 @@ import sbt.internal.server.{
|
||||||
LanguageServerReporter,
|
LanguageServerReporter,
|
||||||
ServerHandler
|
ServerHandler
|
||||||
}
|
}
|
||||||
import sbt.nio.FileStamp.Formats.seqPathFileStampJsonFormatter
|
|
||||||
import sbt.internal.testing.TestLogger
|
import sbt.internal.testing.TestLogger
|
||||||
import sbt.internal.util.Attributed.data
|
import sbt.internal.util.Attributed.data
|
||||||
import sbt.internal.util.Types._
|
import sbt.internal.util.Types._
|
||||||
|
|
@ -70,6 +69,7 @@ import sbt.librarymanagement.CrossVersion.{ binarySbtVersion, binaryScalaVersion
|
||||||
import sbt.librarymanagement._
|
import sbt.librarymanagement._
|
||||||
import sbt.librarymanagement.ivy._
|
import sbt.librarymanagement.ivy._
|
||||||
import sbt.librarymanagement.syntax._
|
import sbt.librarymanagement.syntax._
|
||||||
|
import sbt.nio.FileStamp.Formats.seqPathFileStampJsonFormatter
|
||||||
import sbt.nio.Keys._
|
import sbt.nio.Keys._
|
||||||
import sbt.nio.file.syntax._
|
import sbt.nio.file.syntax._
|
||||||
import sbt.nio.file.{ FileTreeView, Glob, RecursiveGlob }
|
import sbt.nio.file.{ FileTreeView, Glob, RecursiveGlob }
|
||||||
|
|
@ -205,6 +205,7 @@ object Defaults extends BuildCommon {
|
||||||
bgStop := bgStopTask.evaluated,
|
bgStop := bgStopTask.evaluated,
|
||||||
bgWaitFor := bgWaitForTask.evaluated,
|
bgWaitFor := bgWaitForTask.evaluated,
|
||||||
bgCopyClasspath :== true,
|
bgCopyClasspath :== true,
|
||||||
|
allowZombieClassLoaders :== !SysProp.closeClassLoaders,
|
||||||
)
|
)
|
||||||
|
|
||||||
private[sbt] lazy val globalIvyCore: Seq[Setting[_]] =
|
private[sbt] lazy val globalIvyCore: Seq[Setting[_]] =
|
||||||
|
|
@ -813,7 +814,7 @@ object Defaults extends BuildCommon {
|
||||||
allJars: Seq[File],
|
allJars: Seq[File],
|
||||||
libraryJars: Array[File],
|
libraryJars: Array[File],
|
||||||
compilerJar: File,
|
compilerJar: File,
|
||||||
classLoaderCache: sbt.internal.inc.classpath.ClassLoaderCache
|
classLoaderCache: ClassLoaderCache,
|
||||||
): ScalaInstance = {
|
): ScalaInstance = {
|
||||||
val allJarsDistinct = allJars.distinct
|
val allJarsDistinct = allJars.distinct
|
||||||
val libraryLoader = classLoaderCache(libraryJars.toList)
|
val libraryLoader = classLoaderCache(libraryJars.toList)
|
||||||
|
|
@ -837,7 +838,7 @@ object Defaults extends BuildCommon {
|
||||||
val dummy = ScalaInstance(dir)(state.value.classLoaderCache.apply)
|
val dummy = ScalaInstance(dir)(state.value.classLoaderCache.apply)
|
||||||
Seq(dummy.loader, dummy.loaderLibraryOnly).foreach {
|
Seq(dummy.loader, dummy.loaderLibraryOnly).foreach {
|
||||||
case a: AutoCloseable => a.close()
|
case a: AutoCloseable => a.close()
|
||||||
case cl =>
|
case _ =>
|
||||||
}
|
}
|
||||||
mkScalaInstance(
|
mkScalaInstance(
|
||||||
dummy.version,
|
dummy.version,
|
||||||
|
|
|
||||||
|
|
@ -325,6 +325,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")
|
||||||
|
private[sbt] 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)
|
||||||
val csrCacheDirectory = settingKey[File]("Coursier cache directory. Uses -Dsbt.coursier.home or Coursier's default.").withRank(CSetting)
|
val csrCacheDirectory = settingKey[File]("Coursier cache directory. Uses -Dsbt.coursier.home or Coursier's default.").withRank(CSetting)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import sbt.io.IO
|
||||||
import sbt.nio.FileStamp
|
import sbt.nio.FileStamp
|
||||||
import sbt.nio.FileStamp.LastModified
|
import sbt.nio.FileStamp.LastModified
|
||||||
import sbt.nio.Keys._
|
import sbt.nio.Keys._
|
||||||
|
import sbt.util.Logger
|
||||||
|
|
||||||
private[sbt] object ClassLoaders {
|
private[sbt] object ClassLoaders {
|
||||||
private[this] val interfaceLoader = classOf[sbt.testing.Framework].getClassLoader
|
private[this] val interfaceLoader = classOf[sbt.testing.Framework].getClassLoader
|
||||||
|
|
@ -38,6 +39,8 @@ private[sbt] object ClassLoaders {
|
||||||
if (si.isManagedVersion) rawCP
|
if (si.isManagedVersion) rawCP
|
||||||
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 allowZombies = allowZombieClassLoaders.value
|
||||||
buildLayers(
|
buildLayers(
|
||||||
strategy = classLoaderLayeringStrategy.value,
|
strategy = classLoaderLayeringStrategy.value,
|
||||||
si = si,
|
si = si,
|
||||||
|
|
@ -46,7 +49,9 @@ private[sbt] object ClassLoaders {
|
||||||
cache = extendedClassLoaderCache.value,
|
cache = extendedClassLoaderCache.value,
|
||||||
resources = ClasspathUtilities.createClasspathResources(fullCP.map(_._1), si),
|
resources = ClasspathUtilities.createClasspathResources(fullCP.map(_._1), si),
|
||||||
tmp = IO.createUniqueDirectory(taskTemporaryDirectory.value),
|
tmp = IO.createUniqueDirectory(taskTemporaryDirectory.value),
|
||||||
scope = resolvedScoped.value.scope
|
scope = resolvedScoped.value.scope,
|
||||||
|
logger = logger,
|
||||||
|
allowZombies = allowZombies,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,6 +82,8 @@ private[sbt] object ClassLoaders {
|
||||||
}
|
}
|
||||||
val exclude = dependencyJars(exportedProducts).value.toSet ++ instance.libraryJars
|
val exclude = dependencyJars(exportedProducts).value.toSet ++ instance.libraryJars
|
||||||
val allDeps = dependencyJars(dependencyClasspath).value.filterNot(exclude)
|
val allDeps = dependencyJars(dependencyClasspath).value.filterNot(exclude)
|
||||||
|
val logger = state.value.globalLogging.full
|
||||||
|
val allowZombies = allowZombieClassLoaders.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
|
||||||
|
|
@ -89,7 +96,9 @@ private[sbt] object ClassLoaders {
|
||||||
cache = extendedClassLoaderCache.value: @sbtUnchecked,
|
cache = extendedClassLoaderCache.value: @sbtUnchecked,
|
||||||
resources = ClasspathUtilities.createClasspathResources(classpath, instance),
|
resources = ClasspathUtilities.createClasspathResources(classpath, instance),
|
||||||
tmp = taskTemporaryDirectory.value: @sbtUnchecked,
|
tmp = taskTemporaryDirectory.value: @sbtUnchecked,
|
||||||
scope = resolvedScope
|
scope = resolvedScope,
|
||||||
|
logger = logger,
|
||||||
|
allowZombies = allowZombies,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
new Run(newLoader, trapExit.value)
|
new Run(newLoader, trapExit.value)
|
||||||
|
|
@ -121,11 +130,13 @@ private[sbt] object ClassLoaders {
|
||||||
cache: ClassLoaderCache,
|
cache: ClassLoaderCache,
|
||||||
resources: Map[String, String],
|
resources: Map[String, String],
|
||||||
tmp: File,
|
tmp: File,
|
||||||
scope: Scope
|
scope: Scope,
|
||||||
|
logger: Logger,
|
||||||
|
allowZombies: Boolean
|
||||||
): ClassLoader = {
|
): ClassLoader = {
|
||||||
val cpFiles = fullCP.map(_._1)
|
val cpFiles = fullCP.map(_._1)
|
||||||
strategy match {
|
strategy match {
|
||||||
case Flat => flatLoader(cpFiles, interfaceLoader)
|
case Flat => new FlatLoader(cpFiles, interfaceLoader, tmp, allowZombies, logger)
|
||||||
case _ =>
|
case _ =>
|
||||||
val layerDependencies = strategy match {
|
val layerDependencies = strategy match {
|
||||||
case _: AllLibraryJars => true
|
case _: AllLibraryJars => true
|
||||||
|
|
@ -161,7 +172,13 @@ private[sbt] object ClassLoaders {
|
||||||
cache(
|
cache(
|
||||||
allDependencies.toList.map(f => f -> IO.getModifiedTimeOrZero(f)),
|
allDependencies.toList.map(f => f -> IO.getModifiedTimeOrZero(f)),
|
||||||
scalaReflectLayer,
|
scalaReflectLayer,
|
||||||
() => new ReverseLookupClassLoaderHolder(allDependencies, scalaReflectLayer)
|
() =>
|
||||||
|
new ReverseLookupClassLoaderHolder(
|
||||||
|
allDependencies,
|
||||||
|
scalaReflectLayer,
|
||||||
|
allowZombies,
|
||||||
|
logger
|
||||||
|
)
|
||||||
)
|
)
|
||||||
} else scalaReflectLayer
|
} else scalaReflectLayer
|
||||||
|
|
||||||
|
|
@ -177,7 +194,7 @@ private[sbt] object ClassLoaders {
|
||||||
case cl =>
|
case cl =>
|
||||||
cl.getParent match {
|
cl.getParent match {
|
||||||
case dl: ReverseLookupClassLoaderHolder => dl.checkout(cpFiles, tmp)
|
case dl: ReverseLookupClassLoaderHolder => dl.checkout(cpFiles, tmp)
|
||||||
case _ => new LayeredClassLoader(dynamicClasspath, cl, tmp)
|
case _ => new LayeredClassLoader(dynamicClasspath, cl, tmp, allowZombies, logger)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -187,9 +204,6 @@ private[sbt] object ClassLoaders {
|
||||||
key: sbt.TaskKey[Seq[Attributed[File]]]
|
key: sbt.TaskKey[Seq[Attributed[File]]]
|
||||||
): Def.Initialize[Task[Seq[File]]] = Def.task(data(key.value).filter(_.getName.endsWith(".jar")))
|
): Def.Initialize[Task[Seq[File]]] = Def.task(data(key.value).filter(_.getName.endsWith(".jar")))
|
||||||
|
|
||||||
// helper methods
|
|
||||||
private def flatLoader(classpath: Seq[File], parent: ClassLoader): ClassLoader =
|
|
||||||
new FlatLoader(classpath.map(_.toURI.toURL).toArray, parent)
|
|
||||||
private[this] def modifiedTimes(stamps: Seq[(Path, FileStamp)]): Seq[(File, Long)] = stamps.map {
|
private[this] def modifiedTimes(stamps: Seq[(Path, FileStamp)]): Seq[(File, Long)] = stamps.map {
|
||||||
case (p, LastModified(lm)) => p.toFile -> lm
|
case (p, LastModified(lm)) => p.toFile -> lm
|
||||||
case (p, _) =>
|
case (p, _) =>
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import java.net.{ URL, URLClassLoader }
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
|
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
|
||||||
|
|
||||||
import sbt.internal.inc.classpath._
|
|
||||||
import sbt.io.IO
|
import sbt.io.IO
|
||||||
|
import sbt.util.Logger
|
||||||
|
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
|
@ -27,11 +27,11 @@ import scala.collection.JavaConverters._
|
||||||
private[internal] class LayeredClassLoaderImpl(
|
private[internal] class LayeredClassLoaderImpl(
|
||||||
classpath: Seq[File],
|
classpath: Seq[File],
|
||||||
parent: ClassLoader,
|
parent: ClassLoader,
|
||||||
tempDir: File
|
tempDir: File,
|
||||||
) extends URLClassLoader(classpath.map(_.toURI.toURL).toArray, parent)
|
allowZombies: Boolean,
|
||||||
with NativeLoader {
|
logger: Logger
|
||||||
|
) extends ManagedClassLoader(classpath.toArray.map(_.toURI.toURL), parent, allowZombies, logger) {
|
||||||
setTempDir(tempDir)
|
setTempDir(tempDir)
|
||||||
override def close(): Unit = if (SysProp.closeClassLoaders) super.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -58,10 +58,13 @@ private[internal] class LayeredClassLoaderImpl(
|
||||||
*/
|
*/
|
||||||
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 allowZombies: Boolean,
|
||||||
|
val logger: Logger
|
||||||
) extends URLClassLoader(Array.empty, null) {
|
) extends URLClassLoader(Array.empty, null) {
|
||||||
private[this] val cached: AtomicReference[ReverseLookupClassLoader] = new AtomicReference
|
private[this] val cached: AtomicReference[ReverseLookupClassLoader] = new AtomicReference
|
||||||
private[this] val closed = new AtomicBoolean(false)
|
private[this] val closed = new AtomicBoolean(false)
|
||||||
|
private[this] val urls = classpath.map(_.toURI.toURL).toArray
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a classloader. If there is a loader available in the cache, it will use that loader,
|
* Get a classloader. If there is a loader available in the cache, it will use that loader,
|
||||||
|
|
@ -141,8 +144,9 @@ private[internal] final class ReverseLookupClassLoaderHolder(
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private class ReverseLookupClassLoader
|
private class ReverseLookupClassLoader
|
||||||
extends URLClassLoader(classpath.map(_.toURI.toURL).toArray, parent)
|
extends ManagedClassLoader(urls, parent, allowZombies, logger)
|
||||||
with NativeLoader {
|
with NativeLoader {
|
||||||
|
override def getURLs: Array[URL] = urls
|
||||||
private[this] val directDescendant: AtomicReference[BottomClassLoader] =
|
private[this] val directDescendant: AtomicReference[BottomClassLoader] =
|
||||||
new AtomicReference
|
new AtomicReference
|
||||||
private[this] val dirty = new AtomicBoolean(false)
|
private[this] val dirty = new AtomicBoolean(false)
|
||||||
|
|
@ -179,7 +183,6 @@ private[internal] final class ReverseLookupClassLoaderHolder(
|
||||||
}
|
}
|
||||||
override def loadClass(name: String, resolve: Boolean): Class[_] =
|
override def loadClass(name: String, resolve: Boolean): Class[_] =
|
||||||
loadClass(name, resolve, reverseLookup = true)
|
loadClass(name, resolve, reverseLookup = true)
|
||||||
override def close(): Unit = if (SysProp.closeClassLoaders) super.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -202,7 +205,12 @@ private[internal] final class ReverseLookupClassLoaderHolder(
|
||||||
dynamicClasspath: Seq[File],
|
dynamicClasspath: Seq[File],
|
||||||
parent: ReverseLookupClassLoader,
|
parent: ReverseLookupClassLoader,
|
||||||
tempDir: File
|
tempDir: File
|
||||||
) extends URLClassLoader(dynamicClasspath.map(_.toURI.toURL).toArray, parent)
|
) extends ManagedClassLoader(
|
||||||
|
dynamicClasspath.map(_.toURI.toURL).toArray,
|
||||||
|
parent,
|
||||||
|
allowZombies,
|
||||||
|
logger
|
||||||
|
)
|
||||||
with NativeLoader {
|
with NativeLoader {
|
||||||
parent.setDescendant(this)
|
parent.setDescendant(this)
|
||||||
setTempDir(tempDir)
|
setTempDir(tempDir)
|
||||||
|
|
@ -236,7 +244,7 @@ private[internal] final class ReverseLookupClassLoaderHolder(
|
||||||
}
|
}
|
||||||
override def close(): Unit = {
|
override def close(): Unit = {
|
||||||
checkin(parent)
|
checkin(parent)
|
||||||
if (SysProp.closeClassLoaders) super.close()
|
super.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -294,21 +302,6 @@ private trait NativeLoader extends ClassLoader with AutoCloseable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private[internal] class ResourceLoaderImpl(
|
|
||||||
classpath: Seq[File],
|
|
||||||
parent: ClassLoader,
|
|
||||||
override val resources: Map[String, String]
|
|
||||||
) extends URLClassLoader(classpath.map(_.toURI.toURL).toArray, parent)
|
|
||||||
with RawResources {
|
|
||||||
override def findClass(name: String): Class[_] = throw new ClassNotFoundException(name)
|
|
||||||
override def loadClass(name: String, resolve: Boolean): Class[_] = {
|
|
||||||
val clazz = parent.loadClass(name)
|
|
||||||
if (resolve) resolveClass(clazz)
|
|
||||||
clazz
|
|
||||||
}
|
|
||||||
override def toString: String = "ResourceLoader"
|
|
||||||
}
|
|
||||||
|
|
||||||
private[internal] object NativeLibs {
|
private[internal] object NativeLibs {
|
||||||
private[this] val nativeLibs = new java.util.HashSet[File].asScala
|
private[this] val nativeLibs = new java.util.HashSet[File].asScala
|
||||||
ShutdownHooks.add(() => {
|
ShutdownHooks.add(() => {
|
||||||
|
|
@ -327,3 +320,64 @@ private[internal] object NativeLibs {
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed abstract class ManagedClassLoader(
|
||||||
|
urls: Array[URL],
|
||||||
|
parent: ClassLoader,
|
||||||
|
allowZombies: Boolean,
|
||||||
|
logger: Logger
|
||||||
|
) extends URLClassLoader(urls, parent)
|
||||||
|
with NativeLoader {
|
||||||
|
private[this] val closed = new AtomicBoolean(false)
|
||||||
|
private[this] val printedWarning = new AtomicBoolean(false)
|
||||||
|
private[this] val zombieLoader = new AtomicReference[ZombieClassLoader]
|
||||||
|
private class ZombieClassLoader extends URLClassLoader(urls, this) {
|
||||||
|
def lookupClass(name: String): Class[_] =
|
||||||
|
try findClass(name)
|
||||||
|
catch {
|
||||||
|
case e: ClassNotFoundException =>
|
||||||
|
val deleted = urls.flatMap { u =>
|
||||||
|
val f = new File(u.getPath)
|
||||||
|
if (f.exists) None else Some(f)
|
||||||
|
}
|
||||||
|
if (deleted.toSeq.nonEmpty) {
|
||||||
|
// TODO - add doc link
|
||||||
|
val msg = s"Couldn't load class $name. " +
|
||||||
|
s"The following urls on the classpath do not exist:\n${deleted mkString "\n"}\n" +
|
||||||
|
"This may be due to shutdown hooks added during an invocation of `run`."
|
||||||
|
// logging may be shutdown at this point so we need to print directly to System.err.
|
||||||
|
System.err.println(msg)
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private def getZombieLoader(name: String): ZombieClassLoader = {
|
||||||
|
if (printedWarning.compareAndSet(false, true) && !allowZombies) {
|
||||||
|
// TODO - Need to add link to documentation in website
|
||||||
|
val thread = Thread.currentThread
|
||||||
|
val msg =
|
||||||
|
s"$thread loading $name after test or run has completed. This is a likely resource leak."
|
||||||
|
logger.warn(msg)
|
||||||
|
}
|
||||||
|
zombieLoader.get match {
|
||||||
|
case null =>
|
||||||
|
val zb = new ZombieClassLoader
|
||||||
|
zombieLoader.set(zb)
|
||||||
|
zb
|
||||||
|
case zb => zb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override def findResource(name: String): URL = {
|
||||||
|
if (closed.get) getZombieLoader(name).findResource(name)
|
||||||
|
else super.findResource(name)
|
||||||
|
}
|
||||||
|
override def findClass(name: String): Class[_] = {
|
||||||
|
if (closed.get) getZombieLoader(name).lookupClass(name)
|
||||||
|
else super.findClass(name)
|
||||||
|
}
|
||||||
|
override def close(): Unit = {
|
||||||
|
closed.set(true)
|
||||||
|
Option(zombieLoader.getAndSet(null)).foreach(_.close())
|
||||||
|
super.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue