From 21f7e16ee61354a33c8cc217ea8c718a2fa1c312 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Wed, 18 Aug 2021 09:22:46 +0000 Subject: [PATCH] [Gradle] NativeDistributionCommonizationCache: Acquire FileLock A `.lock` file will be acquired before running the native distribution commonizer. This is done to protect data corruption when multiple process try to commonize at the same time. This could happen when e.g. two new projects are opened that both trigger the commonizer during syncing. ^KT-46343 Verification Pending --- .../jetbrains/kotlin/gradle/CommonizerIT.kt | 2 + .../NativeDistributionCommonizationCache.kt | 65 ++++++++++++------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CommonizerIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CommonizerIT.kt index c93f4218266..070c2de5571 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CommonizerIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CommonizerIT.kt @@ -43,6 +43,8 @@ class CommonizerIT : BaseGradleIT() { assertTasksExecuted(":commonizeNativeDistribution") assertContains("Native Distribution Commonization: Cache hit") assertContains("Native Distribution Commonization: All available targets are commonized already") + assertContains("Native Distribution Commonization: Lock acquired") + assertContains("Native Distribution Commonization: Lock released") assertNotContains(commonizerOutput) assertSuccessful() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/NativeDistributionCommonizationCache.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/NativeDistributionCommonizationCache.kt index a7ae463602f..c283ffce3a1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/NativeDistributionCommonizationCache.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/NativeDistributionCommonizationCache.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.commonizer.* import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.resolveCommonizedDirectory import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import java.io.File +import java.io.FileOutputStream internal val Project.isNativeDistributionCommonizationCacheEnabled: Boolean get() = PropertiesProvider(this).enableNativeDistributionCommonizationCache @@ -21,6 +22,7 @@ internal class NativeDistributionCommonizationCache( private val commonizer: NativeDistributionCommonizer ) : NativeDistributionCommonizer { + override fun commonizeNativeDistribution( konanHome: File, outputDirectory: File, @@ -31,31 +33,33 @@ internal class NativeDistributionCommonizationCache( logInfo("Cache disabled") } - val cachedOutputTargets = outputTargets - .filter { outputTarget -> isCached(resolveCommonizedDirectory(outputDirectory, outputTarget)) } - .onEach { outputTarget -> logInfo("Cache hit: $outputTarget already commonized") } - .toSet() + withLock(outputDirectory) { + val cachedOutputTargets = outputTargets + .filter { outputTarget -> isCached(resolveCommonizedDirectory(outputDirectory, outputTarget)) } + .onEach { outputTarget -> logInfo("Cache hit: $outputTarget already commonized") } + .toSet() - val enqueuedOutputTargets = if (project.isNativeDistributionCommonizationCacheEnabled) outputTargets - cachedOutputTargets - else outputTargets + val enqueuedOutputTargets = if (project.isNativeDistributionCommonizationCacheEnabled) outputTargets - cachedOutputTargets + else outputTargets - if (canReturnFast(konanHome, enqueuedOutputTargets)) { - logInfo("All available targets are commonized already - Nothing to do") - return + if (canReturnFast(konanHome, enqueuedOutputTargets)) { + logInfo("All available targets are commonized already - Nothing to do") + return + } + + enqueuedOutputTargets + .map { outputTarget -> resolveCommonizedDirectory(outputDirectory, outputTarget) } + .forEach { commonizedDirectory -> if (commonizedDirectory.exists()) commonizedDirectory.deleteRecursively() } + + commonizer.commonizeNativeDistribution( + konanHome, outputDirectory, enqueuedOutputTargets, logLevel + ) + + enqueuedOutputTargets + .map { outputTarget -> resolveCommonizedDirectory(outputDirectory, outputTarget) } + .filter { commonizedDirectory -> commonizedDirectory.isDirectory } + .forEach { commonizedDirectory -> commonizedDirectory.resolve(".success").createNewFile() } } - - enqueuedOutputTargets - .map { outputTarget -> resolveCommonizedDirectory(outputDirectory, outputTarget) } - .forEach { commonizedDirectory -> if (commonizedDirectory.exists()) commonizedDirectory.deleteRecursively() } - - commonizer.commonizeNativeDistribution( - konanHome, outputDirectory, enqueuedOutputTargets, logLevel - ) - - enqueuedOutputTargets - .map { outputTarget -> resolveCommonizedDirectory(outputDirectory, outputTarget) } - .filter { commonizedDirectory -> commonizedDirectory.isDirectory } - .forEach { commonizedDirectory -> commonizedDirectory.resolve(".success").createNewFile() } } private fun isCached(directory: File): Boolean { @@ -76,9 +80,26 @@ internal class NativeDistributionCommonizationCache( .none { platformLibsDir -> platformLibsDir.exists() } } + private inline fun withLock(outputDirectory: File, action: () -> T): T { + val lockfile = outputDirectory.resolve(".lock") + logInfo("Acquire lock: ${lockfile.path} ...") + FileOutputStream(outputDirectory.resolve(".lock")).use { stream -> + val lock = stream.channel.lock() + assert(lock.isValid) + return try { + logInfo("Lock acquired: ${lockfile.path}") + action() + } finally { + lock.release() + logInfo("Lock released: ${lockfile.path}") + } + } + } + private fun logInfo(message: String) = project.logger.info("${Logging.prefix}: $message") private object Logging { const val prefix = "Native Distribution Commonization" } } +