Cleaning jar cache after build in gradle plugin, fixes KT-9440. Some refactorings on the way to fix.

This commit is contained in:
Ilya Chernikov
2015-10-09 16:20:44 +02:00
parent ed5c059cea
commit c3d3160345
2 changed files with 56 additions and 27 deletions
@@ -16,13 +16,11 @@
package org.jetbrains.kotlin.gradle.plugin package org.jetbrains.kotlin.gradle.plugin
import org.apache.commons.lang.SystemUtils
import org.gradle.BuildAdapter import org.gradle.BuildAdapter
import org.gradle.BuildResult import org.gradle.BuildResult
import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.Logging import org.gradle.api.logging.Logging
import java.lang.ref.Reference
import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledExecutorService
import kotlin.text.MatchGroup
internal fun getUsedMemoryKb(): Long { internal fun getUsedMemoryKb(): Long {
System.gc() System.gc()
@@ -42,7 +40,7 @@ private fun comparableVersionStr(version: String) =
?.joinToString(".", transform = { it!!.value.padStart(3, '0') }) ?.joinToString(".", transform = { it!!.value.padStart(3, '0') })
class FinishBuildListener(pluginClassLoader: ClassLoader?, val startMemory: Long) : BuildAdapter() { class FinishBuildListener(pluginClassLoader: ClassLoader, val startMemory: Long) : BuildAdapter() {
val log = Logging.getLogger(this.javaClass) val log = Logging.getLogger(this.javaClass)
private var threadTracker: ThreadTracker? = ThreadTracker() private var threadTracker: ThreadTracker? = ThreadTracker()
@@ -52,26 +50,11 @@ class FinishBuildListener(pluginClassLoader: ClassLoader?, val startMemory: Long
override fun buildFinished(result: BuildResult?) { override fun buildFinished(result: BuildResult?) {
log.kotlinDebug("Build finished listener") log.kotlinDebug("Build finished listener")
val gradle = result?.getGradle() val gradle = result?.gradle
if (gradle != null) { if (gradle != null) {
// making cleanup only on recognized versions of gradle and if version < 2.4 cleanup(gradle.gradleVersion)
// otherwise it may cause problems e.g. with JobScheduler on subsequent runs // checking thread leaks only then cleaning up
// this strategy may lead to memory leaks, but prevent crashes due to destroyed JobScheduler threadTracker?.checkThreadLeak(gradle)
// the reason for the strategy is the following:
// gradle < 2.4 has problems with plugin reuse in the daemon: new calls to the plugin are made with a new classloader
// for every new build. With statically initialized daemons like JobScheduler that leads to big leaks of classloaders and classes,
// therefore to reduce leaks JobScheduler (and deprecated ZipFileCache for now) should be stopped)
// It should be noted that because of this behavior there are no benefits of using daemon in these versions.
// Starting from 2.4 gradle using cached classloaders, that leads to effective class reusing in the daemon, but
// in that case premature stopping of the static daemons may lead to crashes.
comparableVersionStr(gradle.getGradleVersion())?.let {
log.kotlinDebug("detected gradle version $it")
if (it < comparableVersionStr("2.4")!!) {
cleanup()
// checking thread leaks only then cleaning up
threadTracker?.checkThreadLeak(gradle)
}
}
threadTracker = null threadTracker = null
gradle.removeListener(this) gradle.removeListener(this)
@@ -87,14 +70,36 @@ class FinishBuildListener(pluginClassLoader: ClassLoader?, val startMemory: Long
class CompilerServicesCleanup(private var pluginClassLoader: ClassLoader?) { class CompilerServicesCleanup(private var pluginClassLoader: ClassLoader?) {
val log = Logging.getLogger(this.javaClass) val log = Logging.getLogger(this.javaClass)
fun invoke() { operator fun invoke(gradleVersion: String) {
assert(pluginClassLoader != null) assert(pluginClassLoader != null)
log.kotlinDebug("compiler services cleanup") log.kotlinDebug("compiler services cleanup")
// TODO: remove ZipFileCache cleanup after switching to recent idea libs // clearing jar cache to avoid problems like KT-9440 (unable to clean/rebuild a project due to locked jar file)
stopZipFileCache() // problem is known to happen only on windows - the reason (seems) related to http://bugs.java.com/view_bug.do?bug_id=6357433
stopJobScheduler() // clean cache only when running on windows
if (SystemUtils.IS_OS_WINDOWS) {
cleanJarCache()
}
// making cleanup of static objects only on recognized versions of gradle and if version < 2.4
// otherwise it may cause problems e.g. with JobScheduler on subsequent runs
// this strategy may lead to memory leaks, but prevent crashes due to destroyed JobScheduler
// the reason for the strategy is the following:
// gradle < 2.4 has problems with plugin reuse in the daemon: new calls to the plugin are made with a new classloader
// for every new build. With statically initialized daemons like JobScheduler that leads to big leaks of classloaders and classes,
// therefore to reduce leaks JobScheduler (and deprecated ZipFileCache for now) should be stopped)
// It should be noted that because of this behavior there are no benefits of using daemon in these versions.
// Starting from 2.4 gradle using cached classloaders, that leads to effective class reusing in the daemon, but
// in that case premature stopping of the static daemons may lead to crashes.
comparableVersionStr(gradleVersion)?.let {
log.kotlinDebug("detected gradle version $it")
if (it < comparableVersionStr("2.4")!!) {
// TODO: remove ZipFileCache cleanup after switching to recent idea libs
stopZipFileCache()
stopJobScheduler()
}
}
pluginClassLoader = null pluginClassLoader = null
} }
@@ -128,4 +133,24 @@ class CompilerServicesCleanup(private var pluginClassLoader: ClassLoader?) {
log.kotlinDebug("Call $shortName.$methodName()") log.kotlinDebug("Call $shortName.$methodName()")
shutdownMethod.invoke(null) shutdownMethod.invoke(null)
} }
private fun cleanJarCache() {
val zipHandlerClass = pluginClassLoader!!.loadClass("com.intellij.openapi.vfs.impl.ZipHandler")
val privateCacheField = zipHandlerClass.getDeclaredField("ourZipFileFileAccessorCache")
privateCacheField.isAccessible = true
privateCacheField.get(null)?.let {
val innerCache = privateCacheField.type.getDeclaredField("myCache")
innerCache.isAccessible = true
innerCache.get(it)?.let {
val clearMethod = innerCache.type.getMethod("clear")
if (clearMethod != null) {
clearMethod.invoke(it)
log.kotlinDebug("successfuly cleared ZipHandler.ourZipFileFileAccessorCache.myCache")
}
else {
log.kotlinDebug("unable to access ZipHandler.ourZipFileFileAccessorCache.myCache.clear")
}
} ?: log.kotlinDebug("unable to access ZipHandler.ourZipFileFileAccessorCache.myCache (${innerCache.get(it)})")
} ?: log.kotlinDebug("unable to access ZipHandler.ourZipFileFileAccessorCache (${privateCacheField.get(null)})")
}
} }
@@ -71,6 +71,10 @@ class KotlinGradleIT: BaseGradleIT() {
} }
} }
// testing that nothing remains locked by daemon, see KT-9440
project.build(userVariantArg, "clean", options = BaseGradleIT.BuildOptions(withDaemon = true)) {
assertSuccessful()
}
} }
finally { finally {
exitTestDaemon() exitTestDaemon()