Cleaning jar cache after build in gradle plugin, fixes KT-9440. Some refactorings on the way to fix.
This commit is contained in:
+52
-27
@@ -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)})")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user