diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/RemoteCompilationCanceledStatusClient.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/RemoteCompilationCanceledStatusClient.kt index 163b1a763d4..62ff144fa78 100644 --- a/compiler/daemon/src/org/jetbrains/kotlin/daemon/RemoteCompilationCanceledStatusClient.kt +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/RemoteCompilationCanceledStatusClient.kt @@ -23,12 +23,23 @@ import org.jetbrains.kotlin.daemon.common.Profiler import org.jetbrains.kotlin.daemon.common.RmiFriendlyCompilationCancelledException import org.jetbrains.kotlin.progress.CompilationCanceledException import java.util.concurrent.TimeUnit +import java.util.logging.Logger val CANCELED_STATUS_CHECK_THRESHOLD_NS = TimeUnit.MILLISECONDS.toNanos(100) class RemoteCompilationCanceledStatusClient(val facade: CompilerCallbackServicesFacade, val profiler: Profiler = DummyProfiler()): CompilationCanceledStatus { + + private val log by lazy { Logger.getLogger("compiler") } + @Volatile var lastChecked: Long = System.nanoTime() + override fun checkCanceled() { + + fun cancelOnError(e: Exception) { + log.warning("error communicating with host, assuming compilation cancelled (${e.message})") + throw CompilationCanceledException() + } + val curNanos = System.nanoTime() if (curNanos - lastChecked > CANCELED_STATUS_CHECK_THRESHOLD_NS) { profiler.withMeasure(this) { @@ -38,6 +49,19 @@ class RemoteCompilationCanceledStatusClient(val facade: CompilerCallbackServices catch (e: RmiFriendlyCompilationCancelledException) { throw CompilationCanceledException() } + catch (e: java.rmi.ConnectIOException) { + cancelOnError(e) + } + catch (e: java.rmi.ConnectException) { + cancelOnError(e) + } + catch (e: java.rmi.NoSuchObjectException) { + // this was added mostly for tests since others are more difficult to emulate + cancelOnError(e) + } + catch (e: java.rmi.UnmarshalException) { + cancelOnError(e) + } } lastChecked = curNanos } diff --git a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt index 1a16ccad4df..42e92e825f7 100644 --- a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt @@ -17,13 +17,17 @@ package org.jetbrains.kotlin.daemon import org.jetbrains.kotlin.cli.AbstractCliTest +import org.jetbrains.kotlin.daemon.client.CompilerCallbackServicesFacadeServer import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets import org.jetbrains.kotlin.daemon.client.KotlinCompilerClient +import org.jetbrains.kotlin.daemon.client.RemoteOutputStreamServer import org.jetbrains.kotlin.daemon.common.* import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase +import org.jetbrains.kotlin.progress.CompilationCanceledStatus import org.jetbrains.kotlin.test.KotlinTestUtils import java.io.ByteArrayOutputStream import java.io.File +import java.rmi.server.UnicastRemoteObject import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.concurrent.thread @@ -389,6 +393,101 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { } } } + + fun testDaemonConnectionProblems() { + withFlagFile(getTestName(true), ".alive") { flagFile -> + val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath) + KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) + + val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false) + val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) + assertNotNull("failed to connect daemon", daemon) + daemon?.registerClient(flagFile.absolutePath) + + KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) + Thread.sleep(200) + + val exception: Exception? = try { + daemon!!.getUsedMemory() + null + } + catch (e: java.rmi.ConnectException) { + e + } + catch (e: java.rmi.UnmarshalException) { + e + } + assertNotNull(exception) + } + } + + fun testDaemonCallbackConnectionProblems() { + withFlagFile(getTestName(true), ".alive") { flagFile -> + val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath) + KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) + + val logFile = createTempFile("kotlin-daemon-test", ".log") + val daemonJVMOptions = + configureDaemonJVMOptions("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"${logFile.loggerCompatiblePath}\"", + inheritMemoryLimits = false, inheritAdditionalProperties = false) + + val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) + assertNotNull("failed to connect daemon", daemon) + daemon?.registerClient(flagFile.absolutePath) + + val file = File(tmpdir, "largeKotlinFile.kt") + file.writeText(generateLargeKotlinFile(10)) + val jar = tmpdir.absolutePath + File.separator + "largeKotlinFile.jar" + + var callbackServices: CompilerCallbackServicesFacadeServer? = null + callbackServices = CompilerCallbackServicesFacadeServer(compilationCancelledStatus = object : CompilationCanceledStatus { + override fun checkCanceled() { + thread { + Thread.sleep(10) + UnicastRemoteObject.unexportObject(callbackServices, true) + } + } + }, port = SOCKET_ANY_FREE_PORT) + val strm = ByteArrayOutputStream() + val code = daemon!!.remoteCompile(CompileService.NO_SESSION, + CompileService.TargetPlatform.JVM, + arrayOf("-include-runtime", file.absolutePath, "-d", jar), + callbackServices, + RemoteOutputStreamServer(strm, SOCKET_ANY_FREE_PORT), + CompileService.OutputFormat.XML, + RemoteOutputStreamServer(strm, SOCKET_ANY_FREE_PORT), + null).get() + + val compilerOutput = strm.toString() + assertTrue("Expecting cancelation message in:\n$compilerOutput", compilerOutput.contains("Compilation was canceled")) + logFile.assertLogContainsSequence("error communicating with host, assuming compilation cancelled") + + logFile.delete() + } + } +} + +// stolen from CompilerFileLimitTest +internal fun generateLargeKotlinFile(size: Int): String { + return buildString { + append("package large\n\n") + (0..size).forEach { + appendln("class Class$it") + appendln("{") + appendln("\tfun foo(): Long = $it") + appendln("}") + appendln("\n") + repeat(2000) { + appendln("// kotlin rules ... and stuff") + } + } + appendln("fun main(args: Array)") + appendln("{") + appendln("\tval result = Class5().foo() + Class$size().foo()") + appendln("\tprintln(result)") + appendln("}") + } + } diff --git a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.kt b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.kt index 6ea568067ad..a65d1cdeee1 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.kt @@ -38,9 +38,11 @@ import org.jetbrains.kotlin.daemon.common.* import org.jetbrains.kotlin.jps.build.KotlinBuilder import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents import org.jetbrains.kotlin.progress.CompilationCanceledStatus +import org.jetbrains.kotlin.utils.rethrow import java.io.* import java.lang.reflect.Field import java.lang.reflect.Modifier +import java.rmi.ConnectException import java.util.* import java.util.concurrent.TimeUnit @@ -180,7 +182,8 @@ object KotlinCompilerRunner { argsArray: Array, environment: CompilerEnvironment, messageCollector: MessageCollector, - collector: OutputItemsCollector): Boolean { + collector: OutputItemsCollector, + retryOnConnectionError: Boolean = true): Boolean { if (isDaemonEnabled()) { @@ -202,7 +205,25 @@ object KotlinCompilerRunner { K2JS_COMPILER -> CompileService.TargetPlatform.JS else -> throw IllegalArgumentException("Unknown compiler type $compilerClassName") } - val res = KotlinCompilerClient.incrementalCompile(connection.daemon, connection.sessionId, targetPlatform, argsArray, services, compilerOut, daemonOut) + + fun retryOrFalse(e: Exception): Boolean { + if (retryOnConnectionError) { + KotlinBuilder.LOG.debug("retrying once on daemon connection error: ${e.message}") + return tryCompileWithDaemon(compilerClassName, argsArray, environment, messageCollector, collector, retryOnConnectionError = false) + } + KotlinBuilder.LOG.info("daemon connection error: ${e.message}") + return false + } + + val res: Int = try { + KotlinCompilerClient.incrementalCompile(connection.daemon, connection.sessionId, targetPlatform, argsArray, services, compilerOut, daemonOut) + } + catch (e: java.rmi.ConnectException) { + return retryOrFalse(e) + } + catch (e: java.rmi.UnmarshalException) { + return retryOrFalse(e) + } processCompilerOutput(messageCollector, collector, compilerOut, res.toString()) BufferedReader(StringReader(daemonOut.toString())).forEachLine {