Permalink
Browse files

Fixed broken parallel classloader registration.

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 4b8efad967b8eff422eb7179729b550e09159d6b
@@ -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 =
@@ -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
@@ -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 {
@@ -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,

0 comments on commit 4b8efad

Please sign in to comment.