Show user-friendly message on OOM compilation error

This should provide a hint to user how to mitigate out-of-memory error
on compilation inside Kotlin daemon or in-process.

^KT-47522 Fixed
This commit is contained in:
Yahor Berdnikau
2022-01-18 17:22:34 +01:00
parent ee180a37a6
commit 35ef8cc6da
6 changed files with 46 additions and 13 deletions
@@ -20,7 +20,9 @@ public enum ExitCode {
OK(0), OK(0),
COMPILATION_ERROR(1), COMPILATION_ERROR(1),
INTERNAL_ERROR(2), INTERNAL_ERROR(2),
SCRIPT_EXECUTION_ERROR(3); SCRIPT_EXECUTION_ERROR(3),
OOM_ERROR(137), // 137 is commonly used to indicate OOM error
;
private final int code; private final int code;
@@ -20,8 +20,7 @@ import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.analyzer.CompilationErrorException import org.jetbrains.kotlin.analyzer.CompilationErrorException
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY
import org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR import org.jetbrains.kotlin.cli.common.ExitCode.*
import org.jetbrains.kotlin.cli.common.ExitCode.INTERNAL_ERROR
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.cli.common.messages.* import org.jetbrains.kotlin.cli.common.messages.*
@@ -122,7 +121,7 @@ abstract class CLICompiler<A : CommonCompilerArguments> : CLITool<A>() {
return COMPILATION_ERROR return COMPILATION_ERROR
} catch (t: Throwable) { } catch (t: Throwable) {
MessageCollectorUtil.reportException(collector, t) MessageCollectorUtil.reportException(collector, t)
return INTERNAL_ERROR return if (t is OutOfMemoryError) OOM_ERROR else INTERNAL_ERROR
} finally { } finally {
collector.flush() collector.flush()
} }
@@ -100,6 +100,25 @@ class KotlinDaemonIT : KGPDaemonsBaseTest() {
} }
} }
@DisplayName("On Kotlin daemon OOM helpful message is displayed")
@GradleTest
fun displaySpecialMessageOnOOM(gradleVersion: GradleVersion) {
project(
"kotlinProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.INFO)
) {
gradleProperties.append(
"\nkotlin.daemon.jvmargs=-Xmx16m"
)
buildAndFail("assemble") {
assertOutputContains("Not enough memory to run compilation. Try to increase it via 'gradle.properties':")
assertOutputContains("kotlin.daemon.jvmargs=-Xmx<size>")
}
}
}
private fun BuildResult.assertGradleClasspathNotLeaked() { private fun BuildResult.assertGradleClasspathNotLeaked() {
assertOutputContains("Kotlin compiler classpath:") assertOutputContains("Kotlin compiler classpath:")
val daemonClasspath = output val daemonClasspath = output
@@ -127,12 +127,12 @@ internal class GradleKotlinCompilerWork @Inject constructor(
override fun run() { override fun run() {
try { try {
val messageCollector = GradlePrintingMessageCollector(log, allWarningsAsErrors) val messageCollector = GradlePrintingMessageCollector(log, allWarningsAsErrors)
val exitCode = compileWithDaemonOrFallbackImpl(messageCollector) val (exitCode, executionStrategy) = compileWithDaemonOrFallbackImpl(messageCollector)
if (incrementalCompilationEnvironment?.disableMultiModuleIC == true) { if (incrementalCompilationEnvironment?.disableMultiModuleIC == true) {
incrementalCompilationEnvironment.multiModuleICSettings.buildHistoryFile.delete() incrementalCompilationEnvironment.multiModuleICSettings.buildHistoryFile.delete()
} }
throwGradleExceptionIfError(exitCode) throwGradleExceptionIfError(exitCode, executionStrategy)
} finally { } finally {
val properties = ArrayList<TaskExecutionProperties>() val properties = ArrayList<TaskExecutionProperties>()
COMPILE_INCREMENTAL_WITH_CLASSPATH_SNAPSHOTS.value.toBooleanLenient()?.let { COMPILE_INCREMENTAL_WITH_CLASSPATH_SNAPSHOTS.value.toBooleanLenient()?.let {
@@ -151,7 +151,7 @@ internal class GradleKotlinCompilerWork @Inject constructor(
} }
} }
private fun compileWithDaemonOrFallbackImpl(messageCollector: MessageCollector): ExitCode { private fun compileWithDaemonOrFallbackImpl(messageCollector: MessageCollector): Pair<ExitCode, KotlinCompilerExecutionStrategy> {
with(log) { with(log) {
kotlinDebug { "Kotlin compiler class: ${compilerClassName}" } kotlinDebug { "Kotlin compiler class: ${compilerClassName}" }
kotlinDebug { "Kotlin compiler classpath: ${compilerFullClasspath.joinToString { it.canonicalPath }}" } kotlinDebug { "Kotlin compiler classpath: ${compilerFullClasspath.joinToString { it.canonicalPath }}" }
@@ -162,7 +162,7 @@ internal class GradleKotlinCompilerWork @Inject constructor(
val daemonExitCode = compileWithDaemon(messageCollector) val daemonExitCode = compileWithDaemon(messageCollector)
if (daemonExitCode != null) { if (daemonExitCode != null) {
return daemonExitCode return daemonExitCode to KotlinCompilerExecutionStrategy.DAEMON
} else { } else {
log.warn("Could not connect to kotlin daemon. Using fallback strategy.") log.warn("Could not connect to kotlin daemon. Using fallback strategy.")
} }
@@ -170,9 +170,9 @@ internal class GradleKotlinCompilerWork @Inject constructor(
val isGradleDaemonUsed = System.getProperty("org.gradle.daemon")?.let(String::toBoolean) val isGradleDaemonUsed = System.getProperty("org.gradle.daemon")?.let(String::toBoolean)
return if (compilerExecutionStrategy == KotlinCompilerExecutionStrategy.IN_PROCESS || isGradleDaemonUsed == false) { return if (compilerExecutionStrategy == KotlinCompilerExecutionStrategy.IN_PROCESS || isGradleDaemonUsed == false) {
compileInProcess(messageCollector) compileInProcess(messageCollector) to KotlinCompilerExecutionStrategy.IN_PROCESS
} else { } else {
compileOutOfProcess() compileOutOfProcess() to KotlinCompilerExecutionStrategy.OUT_OF_PROCESS
} }
} }
@@ -132,7 +132,7 @@ abstract class KotlinJsDce : AbstractKotlinCompileTool<K2JSDceArguments>(), Kotl
buildDir, buildDir,
jvmArgs jvmArgs
) )
throwGradleExceptionIfError(exitCode) throwGradleExceptionIfError(exitCode, KotlinCompilerExecutionStrategy.OUT_OF_PROCESS)
} }
private fun isDceCandidate(file: File): Boolean { private fun isDceCandidate(file: File): Boolean {
@@ -14,13 +14,26 @@ import org.jetbrains.kotlin.incremental.cleanDirectoryContents
import org.jetbrains.kotlin.incremental.forceDeleteRecursively import org.jetbrains.kotlin.incremental.forceDeleteRecursively
import java.io.File import java.io.File
fun throwGradleExceptionIfError(exitCode: ExitCode) { fun throwGradleExceptionIfError(
exitCode: ExitCode,
executionStrategy: KotlinCompilerExecutionStrategy
) {
when (exitCode) { when (exitCode) {
ExitCode.COMPILATION_ERROR -> throw GradleException("Compilation error. See log for more details") ExitCode.COMPILATION_ERROR -> throw GradleException("Compilation error. See log for more details")
ExitCode.INTERNAL_ERROR -> throw GradleException("Internal compiler error. See log for more details") ExitCode.INTERNAL_ERROR -> throw GradleException("Internal compiler error. See log for more details")
ExitCode.SCRIPT_EXECUTION_ERROR -> throw GradleException("Script execution error. See log for more details") ExitCode.SCRIPT_EXECUTION_ERROR -> throw GradleException("Script execution error. See log for more details")
ExitCode.OK -> { ExitCode.OOM_ERROR -> {
var exceptionMessage = "Not enough memory to run compilation."
when (executionStrategy) {
KotlinCompilerExecutionStrategy.DAEMON ->
exceptionMessage += " Try to increase it via 'gradle.properties':\nkotlin.daemon.jvmargs=-Xmx<size>"
KotlinCompilerExecutionStrategy.IN_PROCESS ->
exceptionMessage += " Try to increase it via 'gradle.properties':\norg.gradle.jvmargs=-Xmx<size>"
KotlinCompilerExecutionStrategy.OUT_OF_PROCESS -> Unit
}
throw GradleException(exceptionMessage)
} }
ExitCode.OK -> Unit
else -> throw IllegalStateException("Unexpected exit code: $exitCode") else -> throw IllegalStateException("Unexpected exit code: $exitCode")
} }
} }