diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt index e24ab663b10..99749b82246 100644 --- a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt @@ -137,6 +137,8 @@ class CompileServiceImpl( private val sessions: MutableMap> = hashMapOf() private val sessionsIdCounter = AtomicInteger(0) + val lastSessionId get() = sessionsIdCounter.get() + fun leaseSession(session: ClientOrSessionProxy): Int = lock.write { val newId = getValidId(sessionsIdCounter) { it != CompileService.NO_SESSION && !sessions.containsKey(it) @@ -182,8 +184,13 @@ class CompileServiceImpl( val aliveClientsCount: Int get() = clientProxies.size + private val _clientsCounter = AtomicInteger(0) + + val clientsCounter get() = _clientsCounter.get() + fun addClient(aliveFlagPath: String?) { clientsLock.write { + _clientsCounter.incrementAndGet() clientProxies.add(ClientOrSessionProxy(aliveFlagPath)) } } @@ -352,70 +359,72 @@ class CompileServiceImpl( servicesFacade: CompilerServicesFacadeBase, compilationResults: CompilationResults? ): CompileService.CallResult = ifAlive { - val messageCollector = CompileServicesFacadeMessageCollector(servicesFacade, compilationOptions) - val daemonReporter = DaemonMessageReporter(servicesFacade, compilationOptions) - val targetPlatform = compilationOptions.targetPlatform - log.info("Starting compilation with args: " + compilerArguments.joinToString(" ")) + withValidClientOrSessionProxy(sessionId) { + val messageCollector = CompileServicesFacadeMessageCollector(servicesFacade, compilationOptions) + val daemonReporter = DaemonMessageReporter(servicesFacade, compilationOptions) + val targetPlatform = compilationOptions.targetPlatform + log.info("Starting compilation with args: " + compilerArguments.joinToString(" ")) - @Suppress("UNCHECKED_CAST") - val compiler = when (targetPlatform) { - CompileService.TargetPlatform.JVM -> K2JVMCompiler() - CompileService.TargetPlatform.JS -> K2JSCompiler() - CompileService.TargetPlatform.METADATA -> K2MetadataCompiler() - } as CLICompiler + @Suppress("UNCHECKED_CAST") + val compiler = when (targetPlatform) { + CompileService.TargetPlatform.JVM -> K2JVMCompiler() + CompileService.TargetPlatform.JS -> K2JSCompiler() + CompileService.TargetPlatform.METADATA -> K2MetadataCompiler() + } as CLICompiler - val k2PlatformArgs = compiler.createArguments() - parseCommandLineArguments(compilerArguments.asList(), k2PlatformArgs) - val argumentParseError = validateArguments(k2PlatformArgs.errors) - if (argumentParseError != null) { - messageCollector.report(CompilerMessageSeverity.ERROR, argumentParseError) - CompileService.CallResult.Good(ExitCode.COMPILATION_ERROR.code) - } - else when (compilationOptions.compilerMode) { - CompilerMode.JPS_COMPILER -> { - val jpsServicesFacade = servicesFacade as JpsCompilerServicesFacade - - withIC(enabled = servicesFacade.hasIncrementalCaches()) { - doCompile(sessionId, daemonReporter, tracer = null) { eventManger, profiler -> - val services = createCompileServices(jpsServicesFacade, eventManger, profiler) - compiler.exec(messageCollector, services, k2PlatformArgs) - } - } + val k2PlatformArgs = compiler.createArguments() + parseCommandLineArguments(compilerArguments.asList(), k2PlatformArgs) + val argumentParseError = validateArguments(k2PlatformArgs.errors) + if (argumentParseError != null) { + messageCollector.report(CompilerMessageSeverity.ERROR, argumentParseError) + CompileService.CallResult.Good(ExitCode.COMPILATION_ERROR.code) } - CompilerMode.NON_INCREMENTAL_COMPILER -> { - doCompile(sessionId, daemonReporter, tracer = null) { _, _ -> - compiler.exec(messageCollector, Services.EMPTY, k2PlatformArgs) - } - } - CompilerMode.INCREMENTAL_COMPILER -> { - val gradleIncrementalArgs = compilationOptions as IncrementalCompilationOptions - val gradleIncrementalServicesFacade = servicesFacade as IncrementalCompilerServicesFacade + else when (compilationOptions.compilerMode) { + CompilerMode.JPS_COMPILER -> { + val jpsServicesFacade = servicesFacade as JpsCompilerServicesFacade - when (targetPlatform) { - CompileService.TargetPlatform.JVM -> { - val k2jvmArgs = k2PlatformArgs as K2JVMCompilerArguments - - withIC { - doCompile(sessionId, daemonReporter, tracer = null) { _, _ -> - execIncrementalCompiler(k2jvmArgs, gradleIncrementalArgs, gradleIncrementalServicesFacade, compilationResults!!, - messageCollector, daemonReporter) - } + withIC(enabled = servicesFacade.hasIncrementalCaches()) { + doCompile(sessionId, daemonReporter, tracer = null) { eventManger, profiler -> + val services = createCompileServices(jpsServicesFacade, eventManger, profiler) + compiler.exec(messageCollector, services, k2PlatformArgs) } } - CompileService.TargetPlatform.JS -> { - val k2jsArgs = k2PlatformArgs as K2JSCompilerArguments + } + CompilerMode.NON_INCREMENTAL_COMPILER -> { + doCompile(sessionId, daemonReporter, tracer = null) { _, _ -> + compiler.exec(messageCollector, Services.EMPTY, k2PlatformArgs) + } + } + CompilerMode.INCREMENTAL_COMPILER -> { + val gradleIncrementalArgs = compilationOptions as IncrementalCompilationOptions + val gradleIncrementalServicesFacade = servicesFacade as IncrementalCompilerServicesFacade - withJsIC { - doCompile(sessionId, daemonReporter, tracer = null) { _, _ -> - execJsIncrementalCompiler(k2jsArgs, gradleIncrementalArgs, gradleIncrementalServicesFacade, compilationResults!!, messageCollector) + when (targetPlatform) { + CompileService.TargetPlatform.JVM -> { + val k2jvmArgs = k2PlatformArgs as K2JVMCompilerArguments + + withIC { + doCompile(sessionId, daemonReporter, tracer = null) { _, _ -> + execIncrementalCompiler(k2jvmArgs, gradleIncrementalArgs, gradleIncrementalServicesFacade, compilationResults!!, + messageCollector, daemonReporter) + } } } - } - else -> throw IllegalStateException("Incremental compilation is not supported for target platform: $targetPlatform") + CompileService.TargetPlatform.JS -> { + val k2jsArgs = k2PlatformArgs as K2JSCompilerArguments + withJsIC { + doCompile(sessionId, daemonReporter, tracer = null) { _, _ -> + execJsIncrementalCompiler(k2jsArgs, gradleIncrementalArgs, gradleIncrementalServicesFacade, compilationResults!!, messageCollector) + } + } + } + else -> throw IllegalStateException("Incremental compilation is not supported for target platform: $targetPlatform") + + } } + else -> throw IllegalStateException("Unknown compilation mode ${compilationOptions.compilerMode}") } - else -> throw IllegalStateException("Unknown compilation mode ${compilationOptions.compilerMode}") } } @@ -801,18 +810,23 @@ class CompileServiceImpl( private fun shutdownWithDelay() { state.delayedShutdownQueued.set(true) + val currentClientsCount = state.clientsCounter + val currentSessionId = state.sessions.lastSessionId val currentCompilationsCount = compilationsCounter.get() log.info("Delayed shutdown in ${daemonOptions.shutdownDelayMilliseconds}ms") timer.schedule(daemonOptions.shutdownDelayMilliseconds) { state.delayedShutdownQueued.set(false) - if (currentCompilationsCount == compilationsCounter.get()) { + if (currentClientsCount == state.clientsCounter && + currentCompilationsCount == compilationsCounter.get() && + currentSessionId == state.sessions.lastSessionId) + { ifAliveExclusiveUnit(minAliveness = Aliveness.LastSession) { log.fine("Execute delayed shutdown") shutdownNow() } } else { - log.info("Cancel delayed shutdown due to new client") + log.info("Cancel delayed shutdown due to a new activity") } } } diff --git a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt index fc7d69ce0eb..067c4b9c874 100644 --- a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt @@ -403,6 +403,39 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { } } + fun testDaemonCancelShutdownOnANewClient() { + val daemonOptions = DaemonOptions(autoshutdownIdleSeconds = 1000, shutdownDelayMilliseconds = 3000, runFilesPath = File(tmpdir, getTestName(true)).absolutePath) + val clientFlag = createTempFile(getTestName(true), "-client.alive") + val clientFlag2 = createTempFile(getTestName(true), "-client.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(null) + + 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 + + val daemon2 = KotlinCompilerClient.connectToCompileService(compilerId, clientFlag2, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) + assertNotNull("failed to connect daemon", daemon2) + daemon2?.leaseCompileSession(null) + + Thread.sleep(3000) // allow to trigger delayed shutdown timer + + logFile.assertLogContainsSequence("No more clients left", + "Delayed shutdown in 3000ms", + "Cancel delayed shutdown due to a new activity") + } + } + finally { + clientFlag.delete() + clientFlag2.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()