diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt index 2158e137aa9..dbdcef00f5a 100644 --- a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt @@ -619,7 +619,7 @@ class CompileServiceImpl( shutdownWithDelay() return } - state.aliveClientsCount == 0 && compilationsCounter.get() > 0 -> { + state.aliveClientsCount == 0 -> { log.info("No more clients left") shutdownWithDelay() return diff --git a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt index 6065afa9419..eb34fc99cb5 100644 --- a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt @@ -85,11 +85,12 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { verbose = true, reportPerf = true) - fun makeTestDaemonJvmOptions(logFile: File? = null, xmx: Int = 384): DaemonJVMOptions { + fun makeTestDaemonJvmOptions(logFile: File? = null, xmx: Int = 384, args: Iterable = listOf()): DaemonJVMOptions { val additionalArgs = arrayListOf() if (logFile != null) { additionalArgs.add("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"") } + args.forEach { additionalArgs.add(it) } val baseOpts = if (xmx > 0) DaemonJVMOptions(maxMemory = "${xmx}m") else DaemonJVMOptions() return configureDaemonJVMOptions( baseOpts, @@ -346,6 +347,61 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { } } + fun testDaemonExitsOnClientFlagDeletedWithActiveSessions() { + val daemonOptions = DaemonOptions(autoshutdownIdleSeconds = 1000, shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath) + val clientFlag = createTempFile(getTestName(true), "-client.alive") + val sessionFlag = createTempFile(getTestName(true), "-session.alive") + try { + withLogFile("kotlin-daemon-test") { logFile -> + val daemonJVMOptions = makeTestDaemonJvmOptions(logFile) + val daemon = KotlinCompilerClient.connectToCompileService(compilerId, clientFlag, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) + assertNotNull("failed to connect daemon", daemon) + daemon?.leaseCompileSession(sessionFlag.canonicalPath) + + clientFlag.delete() + + Thread.sleep(2100) // allow deleted file detection, should be 2 * DAEMON_PERIODIC_CHECK_INTERVAL_MS + o + // TODO: consider possibility to set DAEMON_PERIODIC_CHECK_INTERVAL_MS from tests, to allow shorter sleeps + + logFile.assertLogContainsSequence("No more clients left", + "Shutdown started") + } + } + finally { + sessionFlag.delete() + clientFlag.delete() + } + } + + fun testDaemonExitsOnClientFlagDeletedWithAllSessionsReleased() { + val daemonOptions = DaemonOptions(autoshutdownIdleSeconds = 1000, shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath) + val clientFlag = createTempFile(getTestName(true), "-client.alive") + val sessionFlag = createTempFile(getTestName(true), "-session.alive") + try { + withLogFile("kotlin-daemon-test") { logFile -> + val daemonJVMOptions = makeTestDaemonJvmOptions(logFile) + val daemon = KotlinCompilerClient.connectToCompileService(compilerId, clientFlag, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) + assertNotNull("failed to connect daemon", daemon) + daemon?.leaseCompileSession(sessionFlag.canonicalPath) + + sessionFlag.delete() + + Thread.sleep(2100) // allow deleted file detection, should be 2 * DAEMON_PERIODIC_CHECK_INTERVAL_MS + o + + clientFlag.delete() + + Thread.sleep(2100) // allow deleted file detection, should be 2 * DAEMON_PERIODIC_CHECK_INTERVAL_MS + o + + logFile.assertLogContainsSequence("No more clients left", + "Shutdown started") + } + } + finally { + sessionFlag.delete() + clientFlag.delete() + } + } + /** Testing that running daemon in the child process doesn't block on s child process.waitFor() * that may happen on windows if simple processBuilder.start is used due to handles inheritance: * - process A starts process B using ProcessBuilder and waits for it using process.waitFor()