Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SD-232 Recycle classloaders to be anti-hostile to JIT #2754

Merged
merged 2 commits into from
Sep 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions compile/src/main/scala/sbt/compiler/AnalyzingCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ import xsbti.compile.{ CachedCompiler, CachedCompilerProvider, DependencyChanges
import java.io.File
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
* 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
* 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) =
this(scalaInstance, provider, cp, _ => ())
this(scalaInstance, provider, cp, _ => (), None)
def this(scalaInstance: ScalaInstance, provider: CompilerInterfaceProvider) = this(scalaInstance, provider, ClasspathOptions.auto)

@deprecated("A Logger is no longer needed.", "0.13.0")
Expand All @@ -26,7 +28,11 @@ final class AnalyzingCompiler private (val scalaInstance: xsbti.compile.ScalaIns
@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 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) {
val arguments = (new CompilerArguments(scalaInstance, cp))(Nil, classpath, None, options)
Expand Down Expand Up @@ -110,17 +116,24 @@ final class AnalyzingCompiler private (val scalaInstance: xsbti.compile.ScalaIns
private[this] def loader(log: Logger) =
{
val interfaceJar = provider(scalaInstance, log)
// this goes to scalaInstance.loader for scala classes and the loader of this class for xsbti classes
val dual = createDualLoader(scalaInstance.loader, getClass.getClassLoader)
new URLClassLoader(Array(interfaceJar.toURI.toURL), dual)
def createInterfaceLoader =
new URLClassLoader(Array(interfaceJar.toURI.toURL), createDualLoader(scalaInstance.loader(), getClass.getClassLoader))

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))

protected def createDualLoader(scalaLoader: ClassLoader, sbtLoader: ClassLoader): ClassLoader =
{
val xsbtiFilter = (name: String) => name.startsWith("xsbti.")
val notXsbtiFilter = (name: String) => !xsbtiFilter(name)
new classpath.DualLoader(scalaLoader, notXsbtiFilter, x => true, sbtLoader, xsbtiFilter, x => false)
}

override def toString = "Analyzing compiler (Scala " + scalaInstance.actualVersion + ")"
}
object AnalyzingCompiler {
Expand Down
3 changes: 3 additions & 0 deletions main/actions/src/main/scala/sbt/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import xsbti.compile.{ CompileOrder, GlobalsCache }
import CompileOrder.{ JavaThenScala, Mixed, ScalaThenJava }
import compiler._
import inc._
import sbt.classpath.ClassLoaderCache
import Locate.DefinesClass
import java.io.File

Expand All @@ -31,6 +32,8 @@ object Compiler {
case x: JavaToolWithNewInterface => Some(x.newJavac)
case _ => None
}
def withClassLoaderCache(classLoaderCache: ClassLoaderCache) =
copy(scalac = scalac.withClassLoaderCache(classLoaderCache))
}
/** The previous source dependency analysis result from compilation. */
final case class PreviousAnalysis(analysis: Analysis, setup: Option[CompileSetup])
Expand Down
12 changes: 10 additions & 2 deletions main/src/main/scala/sbt/Defaults.scala
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,16 @@ object Defaults extends BuildCommon {
if (plugin) scalaBase / ("sbt-" + sbtv) else scalaBase
}

def compilersSetting = compilers := Compiler.compilers(scalaInstance.value, classpathOptions.value, javaHome.value,
bootIvyConfiguration.value, scalaCompilerBridgeSource.value)(appConfiguration.value, streams.value.log)
def compilersSetting = {
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(
compile := compileTask.value,
Expand Down
26 changes: 18 additions & 8 deletions util/classpath/src/main/scala/sbt/classpath/ClassLoaderCache.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,35 @@ private[sbt] final class ClassLoaderCache(val commonParent: ClassLoader) {
* This method is thread-safe.
*/
def apply(files: List[File]): ClassLoader = synchronized {
cachedCustomClassloader(files, () => new URLClassLoader(files.map(_.toURI.toURL).toArray, commonParent))
}

/**
* Returns a ClassLoader, as created by `mkLoader`.
*
* The returned ClassLoader may be cached from a previous call if the last modified time of all `files` is unchanged.
* 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))
getFromReference(files, tstamps, delegate.get(files), mkLoader)
}

private[this] def getFromReference(files: List[File], stamps: List[Long], existingRef: Reference[CachedClassLoader]) =
private[this] def getFromReference(files: List[File], stamps: List[Long], existingRef: Reference[CachedClassLoader], mkLoader: () => ClassLoader) =
if (existingRef eq null)
newEntry(files, stamps)
newEntry(files, stamps, mkLoader)
else
get(files, stamps, existingRef.get)
get(files, stamps, existingRef.get, mkLoader)

private[this] def get(files: List[File], stamps: List[Long], existing: CachedClassLoader): ClassLoader =
private[this] def get(files: List[File], stamps: List[Long], existing: CachedClassLoader, mkLoader: () => ClassLoader): ClassLoader =
if (existing == null || stamps != existing.timestamps) {
newEntry(files, stamps)
newEntry(files, stamps, mkLoader)
} else
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)))
loader
}
Expand Down