[Gradle] Refactor NativeDistributionCommonizerCache to be GCC friendly

Change NativeDistributionCommonizerCache from "decorator" pattern to
be used as composition in related GradleTask. This allows to decouple
cache from actual commonizer instance. Allowing it to be stored in
Gradle Configuration Cache. Since Commonizer instance is created only
during execution phase.

Don't serialise `Lock` instance as Configuration Cache. Instead,
reinstantiate it everytime after deserialization.
This commit is contained in:
Anton Lakotka
2022-06-16 12:13:14 +02:00
committed by Space
parent 6e29075c3c
commit 9a5739555c
2 changed files with 58 additions and 56 deletions
@@ -12,37 +12,35 @@ import org.jetbrains.kotlin.commonizer.*
import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.resolveCommonizedDirectory
import java.io.File
import java.io.FileOutputStream
import java.io.ObjectInputStream
import java.io.Serializable
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
internal class NativeDistributionCommonizationCache(
class NativeDistributionCommonizerCache(
private val outputDirectory: File,
private val konanHome: File,
private val logger: Logger,
private val isCachingEnabled: Boolean,
private val commonizer: NativeDistributionCommonizer
) : NativeDistributionCommonizer {
private val isCachingEnabled: Boolean
) : Serializable {
fun isUpToDate(
konanHome: File, outputDirectory: File, outputTargets: Set<SharedCommonizerTarget>
): Boolean = lock.withLock(outputDirectory) {
todoTargets(konanHome, outputDirectory, outputTargets)
outputTargets: Set<SharedCommonizerTarget>
): Boolean = lock.withLock {
todoTargets(outputTargets)
}.isEmpty()
override fun commonizeNativeDistribution(
konanHome: File,
outputDirectory: File,
/**
* Calls [writeCacheAction] for uncached targets and marks them as cached if it succeeds
*/
fun writeCacheForUncachedTargets(
outputTargets: Set<SharedCommonizerTarget>,
logLevel: CommonizerLogLevel,
additionalSettings: List<AdditionalCommonizerSetting<*>>,
): Unit = lock.withLock(outputDirectory) {
val todoOutputTargets = todoTargets(konanHome, outputDirectory, outputTargets)
writeCacheAction: (todoTargets: Set<SharedCommonizerTarget>) -> Unit
) = lock.withLock {
val todoOutputTargets = todoTargets(outputTargets)
if (todoOutputTargets.isEmpty()) return@withLock
/* Invoke commonizer with only 'to do' targets */
commonizer.commonizeNativeDistribution(
konanHome, outputDirectory, todoOutputTargets, logLevel, additionalSettings
)
writeCacheAction(todoOutputTargets)
/* Mark targets as successfully commonized */
todoOutputTargets
.map { outputTarget -> resolveCommonizedDirectory(outputDirectory, outputTarget) }
.filter { commonizedDirectory -> commonizedDirectory.isDirectory }
@@ -50,14 +48,14 @@ internal class NativeDistributionCommonizationCache(
}
private fun todoTargets(
konanHome: File, outputDirectory: File, outputTargets: Set<SharedCommonizerTarget>
outputTargets: Set<SharedCommonizerTarget>
): Set<SharedCommonizerTarget> {
lock.checkLocked(outputDirectory)
logInfo("Calculating cache state for $outputTargets")
if (!isCachingEnabled) {
logInfo("Cache disabled")
return if (isMissingPlatformLibraries(konanHome, outputTargets)) return emptySet()
return if (isMissingPlatformLibraries(outputTargets)) return emptySet()
else outputTargets
}
@@ -68,7 +66,7 @@ internal class NativeDistributionCommonizationCache(
val todoOutputTargets = outputTargets - cachedOutputTargets
if (todoOutputTargets.isEmpty() || isMissingPlatformLibraries(konanHome, todoOutputTargets)) {
if (todoOutputTargets.isEmpty() || isMissingPlatformLibraries(todoOutputTargets)) {
logInfo("All available targets are commonized already - Nothing to do")
if (todoOutputTargets.isNotEmpty()) {
logInfo("Platforms cannot be commonized, because of missing platform libraries: $todoOutputTargets")
@@ -80,13 +78,8 @@ internal class NativeDistributionCommonizationCache(
return todoOutputTargets
}
private fun isCached(directory: File): Boolean {
val successMarkerFile = directory.resolve(".success")
return successMarkerFile.isFile
}
private fun isMissingPlatformLibraries(
konanHome: File, missingOutputTargets: Set<CommonizerTarget>
missingOutputTargets: Set<CommonizerTarget>
): Boolean {
// If all platform lib dirs are missing, we can also return fast from the cache without invoking
// the commonizer
@@ -96,15 +89,26 @@ internal class NativeDistributionCommonizationCache(
.none { platformLibsDir -> platformLibsDir.exists() }
}
private fun isCached(directory: File): Boolean {
val successMarkerFile = directory.resolve(".success")
return successMarkerFile.isFile
}
/**
* Re-entrant lock implementation capable of locking a given output directory
* even between multiple process (Gradle Daemons)
*/
private val lock = object {
@Transient
private var lock = Lock()
private fun logInfo(message: String) =
logger.info("Native Distribution Commonization: $message")
private inner class Lock {
private val reentrantLock = ReentrantLock()
private val lockedOutputDirectories = mutableSetOf<File>()
fun <T> withLock(outputDirectory: File, action: () -> T): T {
fun <T> withLock(action: () -> T): T {
/* Enter intra-process wide lock */
reentrantLock.withLock {
if (outputDirectory in lockedOutputDirectories) {
@@ -143,6 +147,8 @@ internal class NativeDistributionCommonizationCache(
}
}
private fun logInfo(message: String) =
logger.info("Native Distribution Commonization: $message")
}
private fun readObject(input: ObjectInputStream) {
input.defaultReadObject()
lock = Lock()
}
}
@@ -39,22 +39,25 @@ internal open class NativeDistributionCommonizerTask
private val runnerSettings = KotlinNativeCommonizerToolRunner.Settings(project)
private val isCachingEnabled = project.kotlinPropertiesProvider.enableNativeDistributionCommonizationCache
private val logLevel = project.commonizerLogLevel
private val additionalSettings = project.additionalCommonizerSettings
private val kotlinVersion by lazy { project.getKotlinPluginVersion() }
@get:Internal
internal val rootOutputDirectory: File = project.file {
project.file(konanHome)
project.file(project.konanHome)
.resolve(KONAN_DISTRIBUTION_KLIB_DIR)
.resolve(KONAN_DISTRIBUTION_COMMONIZED_LIBS_DIR)
.resolve(URLEncoder.encode(project.getKotlinPluginVersion(), Charsets.UTF_8.name()))
}
private val commonizerCache = NativeDistributionCommonizerCache(
outputDirectory = rootOutputDirectory,
konanHome = konanHome,
logger = logger,
isCachingEnabled = project.kotlinPropertiesProvider.enableNativeDistributionCommonizationCache
)
@TaskAction
protected fun run() {
val commonizerRunner = KotlinNativeCommonizerToolRunner(
@@ -62,27 +65,20 @@ internal open class NativeDistributionCommonizerTask
settings = runnerSettings
)
val commonizer = NativeDistributionCommonizationCache(
isCachingEnabled = isCachingEnabled,
logger = logger,
commonizer = GradleCliCommonizer(commonizerRunner)
)
commonizer.commonizeNativeDistribution(
konanHome = konanHome,
outputDirectory = rootOutputDirectory,
outputTargets = commonizerTargets,
logLevel = logLevel,
additionalSettings = additionalSettings,
)
commonizerCache.writeCacheForUncachedTargets(commonizerTargets) { todoOutputTargets ->
val commonizer = GradleCliCommonizer(commonizerRunner)
/* Invoke commonizer with only 'to do' targets */
commonizer.commonizeNativeDistribution(
konanHome, rootOutputDirectory, todoOutputTargets, logLevel, additionalSettings
)
}
}
init {
project.registerCommonizerClasspathConfigurationIfNecessary()
// TODO(alakotka): Support upToDate checks
// outputs.upToDateWhen {
// commonizer.isUpToDate(konanHome, rootOutputDirectory, commonizerTargets)
// }
outputs.upToDateWhen {
commonizerCache.isUpToDate(commonizerTargets)
}
}
}