Skip to content

Commit

Permalink
Fixed broken parallel classloader registration.
Browse files Browse the repository at this point in the history
To register a classloader as parallel capable, the protected static caller sensitive method ClassLoader.registerAsParallelCapable() must be called.
As this is not possible in a static way with Scala, we create a dummy instance, call the method from there then drop that dummy instance and only after that, start to use newly created instances of that classloader. We do this for the ProjectClassLoader and the PluginClassLoader.

Backport of efe6869
  • Loading branch information
lefou committed Dec 16, 2014
1 parent 86873ba commit 4b8efad
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 9 deletions.
Expand Up @@ -145,15 +145,42 @@ object PluginClassLoader {
finally InnerRequestGuard.removeInner(classLoader, className)
}
}

class DoNotUseThisInstanceException() extends Exception()

private[this] var parallelRegistered = false

def apply(project: Project, pluginInfo: LoadablePluginInfo, childTrees: Seq[CpTree], parent: ClassLoader): PluginClassLoader = {
if (!parallelRegistered) {
try {
new PluginClassLoader(null, new LoadablePluginInfo(Seq(), true), Seq(), null, true)
} catch {
case e: DoNotUseThisInstanceException => // this is ok
}
parallelRegistered = true
}

new PluginClassLoader(project, pluginInfo, childTrees, parent, false)
}

}

class PluginClassLoader(project: Project, pluginInfo: LoadablePluginInfo, childTrees: Seq[CpTree], parent: ClassLoader)
/**
* IMPORTANT: For this class to work correctly, it is important to create one instance and drop it,
* because it is not well-behaving regarding the classloader specification of Java7+.
* That's why you can create it only via the companion object.
*/
class PluginClassLoader private (project: Project, pluginInfo: LoadablePluginInfo, childTrees: Seq[CpTree], parent: ClassLoader,
initParallelClassloading: Boolean)
extends URLClassLoader(pluginInfo.urls.toArray, parent) {

import PluginClassLoader._

if (ParallelClassLoader.isJava7) {
ClassLoader.registerAsParallelCapable()
if (initParallelClassloading) {
if (ParallelClassLoader.isJava7) {
ClassLoader.registerAsParallelCapable()
}
throw new DoNotUseThisInstanceException()
}

protected def getClassLock(className: String): AnyRef =
Expand All @@ -164,7 +191,7 @@ class PluginClassLoader(project: Project, pluginInfo: LoadablePluginInfo, childT
log.debug(s"Init PluginClassLoader (id: ${System.identityHashCode(this)}) for ${pluginInfo.urls}")

val pluginClassLoaders: Seq[PluginClassLoader] = childTrees.collect {
case cpTree if cpTree.pluginInfo.isDefined => new PluginClassLoader(project, cpTree.pluginInfo.get, cpTree.childs, this)
case cpTree if cpTree.pluginInfo.isDefined => PluginClassLoader(project, cpTree.pluginInfo.get, cpTree.childs, this)
}

// register found plugin classes
Expand Down
Expand Up @@ -8,25 +8,52 @@ import de.tototec.sbuild.Logger
import de.tototec.sbuild.Project
import java.util.concurrent.ConcurrentHashMap

object ProjectClassLoader {

class DoNotUseThisInstanceException() extends Exception()

private[this] var parallelRegistered = false

def apply(project: Project, classpathUrls: Seq[URL], parent: ClassLoader, classpathTrees: Seq[CpTree]): ProjectClassLoader = {
if (!parallelRegistered) {
try {
new ProjectClassLoader(null, Seq(), null, Seq(), true)
} catch {
case e: DoNotUseThisInstanceException => // this is ok
}
parallelRegistered = true
}
new ProjectClassLoader(project, classpathUrls, parent, classpathTrees, false)
}

}

/**
* This classloader first tried to load all classes from the given parent classloader or the classpathUrls.
* If that fails, it tries to load the classes from internally maintained plugin classloader,
* but it will only load those classes which are exported by that plugin. [[de.tototec.sbuild.Constants.SBuildPluginExportPackage]]
*
* IMPORTANT: For this class to work correctly, it is important to create one instance and drop it,
* because it is not well-behaving regarding the classloader specification of Java7+.
* That's why you can create it only via the companion object.
*/
class ProjectClassLoader(project: Project, classpathUrls: Seq[URL], parent: ClassLoader, classpathTrees: Seq[CpTree])
class ProjectClassLoader private (project: Project, classpathUrls: Seq[URL], parent: ClassLoader, classpathTrees: Seq[CpTree],
initParallelClassloading: Boolean)
extends URLClassLoader(classpathUrls.toArray, parent) {
// private[this] val log = Logger[ProjectClassLoader]

if (ParallelClassLoader.isJava7) {
ClassLoader.registerAsParallelCapable()
if (initParallelClassloading) {
if (ParallelClassLoader.isJava7) {
ClassLoader.registerAsParallelCapable()
}
throw new ProjectClassLoader.DoNotUseThisInstanceException()
}

protected def getClassLock(className: String): AnyRef =
ParallelClassLoader.withJava7 { () => getClassLoadingLock(className) }.getOrElse { this }

val pluginClassLoaders: Seq[PluginClassLoader] = classpathTrees.collect {
case cpTree if cpTree.pluginInfo.isDefined => new PluginClassLoader(project, cpTree.pluginInfo.get, cpTree.childs, this)
case cpTree if cpTree.pluginInfo.isDefined => PluginClassLoader(project, cpTree.pluginInfo.get, cpTree.childs, this)
}

override protected def loadClass(className: String, resolve: Boolean): Class[_] = getClassLock(className).synchronized {
Expand Down
Expand Up @@ -290,7 +290,7 @@ class ProjectScript(_scriptFile: File,
val start = System.currentTimeMillis
log.debug("Loading compiled version of build script: " + scriptFile)

val cl = new ProjectClassLoader(
val cl = ProjectClassLoader(
project = project,
classpathUrls = Array(targetDir.toURI.toURL) ++ classpath.map(cp => new File(cp).toURI.toURL),
parent = getClass.getClassLoader,
Expand Down

0 comments on commit 4b8efad

Please sign in to comment.