diff --git a/build-common/src/org/jetbrains/kotlin/incremental/CachesManagerCloseException.kt b/build-common/src/org/jetbrains/kotlin/incremental/CachesManagerCloseException.kt new file mode 100644 index 00000000000..61d842634ee --- /dev/null +++ b/build-common/src/org/jetbrains/kotlin/incremental/CachesManagerCloseException.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.incremental + +class CachesManagerCloseException(cause: Throwable) : Exception("Failed to close caches", cause) \ No newline at end of file diff --git a/build-common/src/org/jetbrains/kotlin/incremental/CompilationTransaction.kt b/build-common/src/org/jetbrains/kotlin/incremental/CompilationTransaction.kt index 874789812b8..98387108e0e 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/CompilationTransaction.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/CompilationTransaction.kt @@ -35,6 +35,16 @@ interface CompilationTransaction : Closeable { * Marks the transaction as successful, so it should not revert changes if it is able to perform revert. */ fun markAsSuccessful() + + /** + * A closeable object (caches manager) that is had to be closed on the transaction close + */ + var cachesManager: Closeable? + + /** + * A throwable that was thrown during the transaction execution + */ + var executionThrowable: Throwable? } fun CompilationTransaction.write(file: Path, writeAction: () -> Unit) { @@ -55,6 +65,22 @@ fun CompilationTransaction.writeBytes(file: Path, array: ByteArray) { } } +inline fun CompilationTransaction.runWithin( + exceptionTransformer: (Throwable) -> R = { throw it }, + body: (CompilationTransaction) -> R +): R { + return runCatching { + use { + try { + body(this) + } catch (t: Throwable) { + executionThrowable = t + throw t + } + } + }.recover { exceptionTransformer(it) }.getOrThrow() // some exceptions may be transformed into results +} + /** * A dummy implementation of compilation transaction */ @@ -73,10 +99,21 @@ class DummyCompilationTransaction : CompilationTransaction { // do nothing } - override fun close() { - // do nothing - } + override var cachesManager: Closeable? = null + override var executionThrowable: Throwable? = null + + override fun close() { + try { + cachesManager?.close() + } catch (t: Throwable) { + if (executionThrowable != null) { + executionThrowable?.addSuppressed(CachesManagerCloseException(t)) + } else { + throw CachesManagerCloseException(t) + } + } + } } /** @@ -173,12 +210,30 @@ class RecoverableCompilationTransaction( successful = true } + override var cachesManager: Closeable? = null + + override var executionThrowable: Throwable? = null + override fun close() { - if (successful) { - cleanupStash() - } else { - revertChanges() - cleanupStash() + val mainException = runCatching { + cachesManager?.close() + }.exceptionOrNull()?.run { + successful = false + val exception = CachesManagerCloseException(this) + executionThrowable?.addSuppressed(exception) + executionThrowable ?: exception + } + + try { + if (successful) { + cleanupStash() + } else { + revertChanges() + cleanupStash() + } + } catch (t: Throwable) { + mainException?.addSuppressed(t) + throw mainException ?: t } } } diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt index c8371891b6e..8a96f1ca4e5 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt @@ -160,6 +160,11 @@ abstract class IncrementalCompilerRunner< class Failed(val reason: BuildAttribute, val cause: Throwable) : ICResult } + private fun incrementalCompilationExceptionTransformer(t: Throwable): ICResult = when (t) { + is CachesManagerCloseException -> ICResult.Failed(IC_FAILED_TO_CLOSE_CACHES, t) + else -> throw t + } + /** * Attempts to compile incrementally and returns either [ICResult.Completed], [ICResult.RequiresRebuild], or [ICResult.Failed]. * @@ -178,9 +183,12 @@ abstract class IncrementalCompilerRunner< } changedFiles as ChangedFiles.Known? - createTransaction().use { transaction -> + return createTransaction().runWithin(::incrementalCompilationExceptionTransformer) { transaction -> val icContext = createIncrementalCompilationContext(projectDir, transaction) - val caches = createCacheManager(icContext, args) + val caches = createCacheManager(icContext, args).also { + // this way we make the transaction to be responsible for closing the caches manager + transaction.cachesManager = it + } fun compile(): ICResult { // Step 1: Get changed files @@ -235,41 +243,11 @@ abstract class IncrementalCompilerRunner< return ICResult.Completed(exitCode) } - fun closeCaches(caches: CacheManager, activeException: Throwable) { - try { - caches.close() - } catch (e: Throwable) { - activeException.addSuppressed(e) + compile().also { icResult -> + if (icResult is ICResult.Completed && icResult.exitCode == ExitCode.OK) { + transaction.markAsSuccessful() } } - - // Because `caches` is a Closeable resource, it is important to close them in both cases: - // 1. in the event of an exception - // 2. after a normal execution - // Note: Historically, closing caches used to throw exceptions sometimes, so currently we want to collect those exceptions. In the - // future, if closing caches is safe, we can simplify the code by using Kotlin's `Closable.use` function (similar to the code in - // `compileNonIncrementally`). - val icResult = try { - compile() - } catch (e: Throwable) { - // Case 1 - Close the caches upon an exception - closeCaches(caches, e) - throw e - } - - // Case 2 - Close the caches after a normal execution - try { - caches.close() - } catch (e: Throwable) { - return ICResult.Failed( - IC_FAILED_TO_CLOSE_CACHES, - RuntimeException("Failed to close caches, previous ICResult `$icResult` was discarded", e) - ) - } - if (icResult is ICResult.Completed && icResult.exitCode == ExitCode.OK) { - transaction.markAsSuccessful() - } - return icResult } }