Merge pull request #2754 from retronym/ticket/SD-232-2

SD-232 Recycle classloaders to be anti-hostile to JIT
This commit is contained in:
eugene yokota 2016-09-26 18:53:12 -04:00 committed by GitHub
commit b4bc9f13fb
4 changed files with 53 additions and 19 deletions

View File

@ -9,15 +9,17 @@ import xsbti.compile.{ CachedCompiler, CachedCompilerProvider, DependencyChanges
import java.io.File import java.io.File
import java.net.{ URL, URLClassLoader } import java.net.{ URL, URLClassLoader }
import sbt.classpath.ClassLoaderCache
/** /**
* Interface to the Scala compiler that uses the dependency analysis plugin. This class uses the Scala library and compiler * Interface to the Scala compiler that uses the dependency analysis plugin. This class uses the Scala library and compiler
* provided by scalaInstance. This class requires a ComponentManager in order to obtain the interface code to scalac and * provided by scalaInstance. This class requires a ComponentManager in order to obtain the interface code to scalac and
* the analysis plugin. Because these call Scala code for a different Scala version than the one used for this class, they must * the analysis plugin. Because these call Scala code for a different Scala version than the one used for this class, they must
* be compiled for the version of Scala being used. * be compiled for the version of Scala being used.
*/ */
final class AnalyzingCompiler private (val scalaInstance: xsbti.compile.ScalaInstance, val provider: CompilerInterfaceProvider, val cp: xsbti.compile.ClasspathOptions, onArgsF: Seq[String] => Unit) extends CachedCompilerProvider { final class AnalyzingCompiler private (val scalaInstance: xsbti.compile.ScalaInstance, val provider: CompilerInterfaceProvider, val cp: xsbti.compile.ClasspathOptions, onArgsF: Seq[String] => Unit, val classLoaderCache: Option[ClassLoaderCache]) extends CachedCompilerProvider {
def this(scalaInstance: xsbti.compile.ScalaInstance, provider: CompilerInterfaceProvider, cp: xsbti.compile.ClasspathOptions) = def this(scalaInstance: xsbti.compile.ScalaInstance, provider: CompilerInterfaceProvider, cp: xsbti.compile.ClasspathOptions) =
this(scalaInstance, provider, cp, _ => ()) this(scalaInstance, provider, cp, _ => (), None)
def this(scalaInstance: ScalaInstance, provider: CompilerInterfaceProvider) = this(scalaInstance, provider, ClasspathOptions.auto) def this(scalaInstance: ScalaInstance, provider: CompilerInterfaceProvider) = this(scalaInstance, provider, ClasspathOptions.auto)
@deprecated("A Logger is no longer needed.", "0.13.0") @deprecated("A Logger is no longer needed.", "0.13.0")
@ -26,7 +28,11 @@ final class AnalyzingCompiler private (val scalaInstance: xsbti.compile.ScalaIns
@deprecated("A Logger is no longer needed.", "0.13.0") @deprecated("A Logger is no longer needed.", "0.13.0")
def this(scalaInstance: xsbti.compile.ScalaInstance, provider: CompilerInterfaceProvider, cp: xsbti.compile.ClasspathOptions, log: Logger) = this(scalaInstance, provider, cp) def this(scalaInstance: xsbti.compile.ScalaInstance, provider: CompilerInterfaceProvider, cp: xsbti.compile.ClasspathOptions, log: Logger) = this(scalaInstance, provider, cp)
def onArgs(f: Seq[String] => Unit): AnalyzingCompiler = new AnalyzingCompiler(scalaInstance, provider, cp, f) def onArgs(f: Seq[String] => Unit): AnalyzingCompiler =
new AnalyzingCompiler(scalaInstance, provider, cp, f, classLoaderCache)
def withClassLoaderCache(classLoaderCache: ClassLoaderCache) =
new AnalyzingCompiler(scalaInstance, provider, cp, onArgsF, Some(classLoaderCache))
def apply(sources: Seq[File], changes: DependencyChanges, classpath: Seq[File], singleOutput: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, cache: GlobalsCache, log: Logger) { def apply(sources: Seq[File], changes: DependencyChanges, classpath: Seq[File], singleOutput: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, cache: GlobalsCache, log: Logger) {
val arguments = (new CompilerArguments(scalaInstance, cp))(Nil, classpath, None, options) val arguments = (new CompilerArguments(scalaInstance, cp))(Nil, classpath, None, options)
@ -110,17 +116,24 @@ final class AnalyzingCompiler private (val scalaInstance: xsbti.compile.ScalaIns
private[this] def loader(log: Logger) = private[this] def loader(log: Logger) =
{ {
val interfaceJar = provider(scalaInstance, log) val interfaceJar = provider(scalaInstance, log)
// this goes to scalaInstance.loader for scala classes and the loader of this class for xsbti classes def createInterfaceLoader =
val dual = createDualLoader(scalaInstance.loader, getClass.getClassLoader) new URLClassLoader(Array(interfaceJar.toURI.toURL), createDualLoader(scalaInstance.loader(), getClass.getClassLoader))
new URLClassLoader(Array(interfaceJar.toURI.toURL), dual)
classLoaderCache match {
case Some(cache) => cache.cachedCustomClassloader(interfaceJar :: scalaInstance.allJars().toList, () => createInterfaceLoader)
case None => createInterfaceLoader
}
} }
private[this] def getInterfaceClass(name: String, log: Logger) = Class.forName(name, true, loader(log)) private[this] def getInterfaceClass(name: String, log: Logger) = Class.forName(name, true, loader(log))
protected def createDualLoader(scalaLoader: ClassLoader, sbtLoader: ClassLoader): ClassLoader = protected def createDualLoader(scalaLoader: ClassLoader, sbtLoader: ClassLoader): ClassLoader =
{ {
val xsbtiFilter = (name: String) => name.startsWith("xsbti.") val xsbtiFilter = (name: String) => name.startsWith("xsbti.")
val notXsbtiFilter = (name: String) => !xsbtiFilter(name) val notXsbtiFilter = (name: String) => !xsbtiFilter(name)
new classpath.DualLoader(scalaLoader, notXsbtiFilter, x => true, sbtLoader, xsbtiFilter, x => false) new classpath.DualLoader(scalaLoader, notXsbtiFilter, x => true, sbtLoader, xsbtiFilter, x => false)
} }
override def toString = "Analyzing compiler (Scala " + scalaInstance.actualVersion + ")" override def toString = "Analyzing compiler (Scala " + scalaInstance.actualVersion + ")"
} }
object AnalyzingCompiler { object AnalyzingCompiler {

View File

@ -9,6 +9,7 @@ import xsbti.compile.{ CompileOrder, GlobalsCache }
import CompileOrder.{ JavaThenScala, Mixed, ScalaThenJava } import CompileOrder.{ JavaThenScala, Mixed, ScalaThenJava }
import compiler._ import compiler._
import inc._ import inc._
import sbt.classpath.ClassLoaderCache
import Locate.DefinesClass import Locate.DefinesClass
import java.io.File import java.io.File
@ -31,6 +32,8 @@ object Compiler {
case x: JavaToolWithNewInterface => Some(x.newJavac) case x: JavaToolWithNewInterface => Some(x.newJavac)
case _ => None case _ => None
} }
def withClassLoaderCache(classLoaderCache: ClassLoaderCache) =
copy(scalac = scalac.withClassLoaderCache(classLoaderCache))
} }
/** The previous source dependency analysis result from compilation. */ /** The previous source dependency analysis result from compilation. */
final case class PreviousAnalysis(analysis: Analysis, setup: Option[CompileSetup]) final case class PreviousAnalysis(analysis: Analysis, setup: Option[CompileSetup])

View File

@ -265,8 +265,16 @@ object Defaults extends BuildCommon {
if (plugin) scalaBase / ("sbt-" + sbtv) else scalaBase if (plugin) scalaBase / ("sbt-" + sbtv) else scalaBase
} }
def compilersSetting = compilers := Compiler.compilers(scalaInstance.value, classpathOptions.value, javaHome.value, def compilersSetting = {
bootIvyConfiguration.value, scalaCompilerBridgeSource.value)(appConfiguration.value, streams.value.log) compilers := {
val compilers = Compiler.compilers(
scalaInstance.value, classpathOptions.value, javaHome.value, bootIvyConfiguration.value,
scalaCompilerBridgeSource.value)(appConfiguration.value, streams.value.log)
if (java.lang.Boolean.getBoolean("sbt.disable.interface.classloader.cache")) compilers else {
compilers.withClassLoaderCache(state.value.classLoaderCache)
}
}
}
lazy val configTasks = docTaskSettings(doc) ++ inTask(compile)(compileInputsSettings) ++ configGlobal ++ compileAnalysisSettings ++ Seq( lazy val configTasks = docTaskSettings(doc) ++ inTask(compile)(compileInputsSettings) ++ configGlobal ++ compileAnalysisSettings ++ Seq(
compile := compileTask.value, compile := compileTask.value,

View File

@ -15,25 +15,35 @@ private[sbt] final class ClassLoaderCache(val commonParent: ClassLoader) {
* This method is thread-safe. * This method is thread-safe.
*/ */
def apply(files: List[File]): ClassLoader = synchronized { def apply(files: List[File]): ClassLoader = synchronized {
val tstamps = files.map(_.lastModified) cachedCustomClassloader(files, () => new URLClassLoader(files.map(_.toURI.toURL).toArray, commonParent))
getFromReference(files, tstamps, delegate.get(files))
} }
private[this] def getFromReference(files: List[File], stamps: List[Long], existingRef: Reference[CachedClassLoader]) = /**
if (existingRef eq null) * Returns a ClassLoader, as created by `mkLoader`.
newEntry(files, stamps) *
else * The returned ClassLoader may be cached from a previous call if the last modified time of all `files` is unchanged.
get(files, stamps, existingRef.get) * This method is thread-safe.
*/
def cachedCustomClassloader(files: List[File], mkLoader: () => ClassLoader): ClassLoader = synchronized {
val tstamps = files.map(_.lastModified)
getFromReference(files, tstamps, delegate.get(files), mkLoader)
}
private[this] def get(files: List[File], stamps: List[Long], existing: CachedClassLoader): ClassLoader = private[this] def getFromReference(files: List[File], stamps: List[Long], existingRef: Reference[CachedClassLoader], mkLoader: () => ClassLoader) =
if (existingRef eq null)
newEntry(files, stamps, mkLoader)
else
get(files, stamps, existingRef.get, mkLoader)
private[this] def get(files: List[File], stamps: List[Long], existing: CachedClassLoader, mkLoader: () => ClassLoader): ClassLoader =
if (existing == null || stamps != existing.timestamps) { if (existing == null || stamps != existing.timestamps) {
newEntry(files, stamps) newEntry(files, stamps, mkLoader)
} else } else
existing.loader existing.loader
private[this] def newEntry(files: List[File], stamps: List[Long]): ClassLoader = private[this] def newEntry(files: List[File], stamps: List[Long], mkLoader: () => ClassLoader): ClassLoader =
{ {
val loader = new URLClassLoader(files.map(_.toURI.toURL).toArray, commonParent) val loader = mkLoader()
delegate.put(files, new SoftReference(new CachedClassLoader(loader, files, stamps))) delegate.put(files, new SoftReference(new CachedClassLoader(loader, files, stamps)))
loader loader
} }