Refactor daemon tests, some minor fixes in the client and daemon

Tests refactored for better readability and code reuse
Some shutdown livelock is fixed
Some logging added
This commit is contained in:
Ilya Chernikov
2017-04-06 12:56:59 +02:00
parent 7600b6de52
commit bec2ae7cab
3 changed files with 269 additions and 235 deletions
@@ -145,6 +145,7 @@ object KotlinCompilerClient {
compilerService.releaseCompileSession(sessionId) compilerService.releaseCompileSession(sessionId)
} }
@Deprecated("Use other compile method", ReplaceWith("compile"))
fun compile(compilerService: CompileService, fun compile(compilerService: CompileService,
sessionId: Int, sessionId: Int,
targetPlatform: CompileService.TargetPlatform, targetPlatform: CompileService.TargetPlatform,
@@ -158,6 +159,7 @@ object KotlinCompilerClient {
} }
@Deprecated("Use non-deprecated compile method", ReplaceWith("compile"))
fun incrementalCompile(compileService: CompileService, fun incrementalCompile(compileService: CompileService,
sessionId: Int, sessionId: Int,
targetPlatform: CompileService.TargetPlatform, targetPlatform: CompileService.TargetPlatform,
@@ -308,6 +310,7 @@ object KotlinCompilerClient {
// --- Implementation --------------------------------------- // --- Implementation ---------------------------------------
@Synchronized
private inline fun <R> connectLoop(reportingTargets: DaemonReportingTargets, body: () -> R?): R? { private inline fun <R> connectLoop(reportingTargets: DaemonReportingTargets, body: () -> R?): R? {
try { try {
var attempts = 0 var attempts = 0
@@ -335,6 +335,7 @@ class CompileServiceImpl(
val daemonReporter = DaemonMessageReporter(servicesFacade, compilationOptions) val daemonReporter = DaemonMessageReporter(servicesFacade, compilationOptions)
val compilerMode = compilationOptions.compilerMode val compilerMode = compilationOptions.compilerMode
val targetPlatform = compilationOptions.targetPlatform val targetPlatform = compilationOptions.targetPlatform
log.info("Starting compilation with args: " + compilerArguments.joinToString(" "))
val k2PlatformArgs = try { val k2PlatformArgs = try {
when (targetPlatform) { when (targetPlatform) {
CompileService.TargetPlatform.JVM -> K2JVMCompilerArguments().apply { K2JVMCompiler().parseArguments(compilerArguments, this) } CompileService.TargetPlatform.JVM -> K2JVMCompilerArguments().apply { K2JVMCompiler().parseArguments(compilerArguments, this) }
@@ -709,6 +710,10 @@ class CompileServiceImpl(
private fun shutdownImpl() { private fun shutdownImpl() {
log.info("Shutdown started") log.info("Shutdown started")
fun Long.mb() = this / (1024 * 1024)
with (Runtime.getRuntime()) {
log.info("Memory stats: total: ${totalMemory().mb()}mb, free: ${freeMemory().mb()}mb, max: ${maxMemory().mb()}mb")
}
state.alive.set(Aliveness.Dying.ordinal) state.alive.set(Aliveness.Dying.ordinal)
UnicastRemoteObject.unexportObject(this, true) UnicastRemoteObject.unexportObject(this, true)
@@ -869,9 +874,18 @@ class CompileServiceImpl(
ifAliveChecksImpl(minAliveness, ignoreCompilerChanged, body) ifAliveChecksImpl(minAliveness, ignoreCompilerChanged, body)
} }
inline private fun<R> ifAliveChecksImpl(minAliveness: Aliveness = Aliveness.Alive, ignoreCompilerChanged: Boolean = false, body: () -> CompileService.CallResult<R>): CompileService.CallResult<R> = inline private fun<R> ifAliveChecksImpl(minAliveness: Aliveness = Aliveness.Alive, ignoreCompilerChanged: Boolean = false, body: () -> CompileService.CallResult<R>): CompileService.CallResult<R> {
when { val curState = state.alive.get()
state.alive.get() < minAliveness.ordinal -> CompileService.CallResult.Dying() return when {
curState < minAliveness.ordinal -> {
log.info("Cannot perform operation, requested state: ${minAliveness.name} > actual: ${try {
Aliveness.values()[curState].name
}
catch (_: Throwable) {
"invalid($curState)"
}}")
CompileService.CallResult.Dying()
}
!ignoreCompilerChanged && classpathWatcher.isChanged -> { !ignoreCompilerChanged && classpathWatcher.isChanged -> {
log.info("Compiler changed, scheduling shutdown") log.info("Compiler changed, scheduling shutdown")
shutdownWithDelay() shutdownWithDelay()
@@ -887,6 +901,7 @@ class CompileServiceImpl(
} }
} }
} }
}
private inline fun<R> withValidClientOrSessionProxy(sessionId: Int, private inline fun<R> withValidClientOrSessionProxy(sessionId: Int,
body: (ClientOrSessionProxy<Any>?) -> CompileService.CallResult<R> body: (ClientOrSessionProxy<Any>?) -> CompileService.CallResult<R>
@@ -18,6 +18,8 @@ package org.jetbrains.kotlin.daemon
import junit.framework.TestCase import junit.framework.TestCase
import org.jetbrains.kotlin.cli.AbstractCliTest import org.jetbrains.kotlin.cli.AbstractCliTest
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.common.repl.* import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.daemon.client.* import org.jetbrains.kotlin.daemon.client.*
import org.jetbrains.kotlin.daemon.common.* import org.jetbrains.kotlin.daemon.common.*
@@ -26,6 +28,7 @@ import org.jetbrains.kotlin.progress.CompilationCanceledStatus
import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.io.PrintStream
import java.net.URL import java.net.URL
import java.net.URLClassLoader import java.net.URLClassLoader
import java.rmi.server.UnicastRemoteObject import java.rmi.server.UnicastRemoteObject
@@ -58,7 +61,9 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
assertNotNull("failed to connect daemon", daemon) assertNotNull("failed to connect daemon", daemon)
daemon?.registerClient(clientAliveFile.absolutePath) daemon?.registerClient(clientAliveFile.absolutePath)
val strm = ByteArrayOutputStream() val strm = ByteArrayOutputStream()
val code = KotlinCompilerClient.compile(daemon!!, CompileService.NO_SESSION, CompileService.TargetPlatform.JVM, args, strm) val code = KotlinCompilerClient.compile(daemon!!, CompileService.NO_SESSION, CompileService.TargetPlatform.JVM,
args, PrintingMessageCollector(PrintStream(strm), MessageRenderer.WITHOUT_PATHS, true),
reportSeverity = ReportSeverity.DEBUG)
return CompilerResults(code, strm.toString()) return CompilerResults(code, strm.toString())
} }
@@ -75,46 +80,61 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
private fun run(logName: String, vararg args: String): Int = runJava(getTestBaseDir(), logName, *args) private fun run(logName: String, vararg args: String): Int = runJava(getTestBaseDir(), logName, *args)
fun makeTestDaemonOptions(testName: String, shutdownDelay: Int = 5) =
DaemonOptions(runFilesPath = File(tmpdir, testName).absolutePath,
shutdownDelayMilliseconds = shutdownDelay.toLong(),
verbose = true,
reportPerf = true)
fun makeTestDaemonJvmOptions(logFile: File? = null, xmx: Int = 256): DaemonJVMOptions {
val additionalArgs = arrayListOf<String>()
if (logFile != null) {
additionalArgs.add("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"")
}
val baseOpts = if (xmx > 0) DaemonJVMOptions(maxMemory = "${xmx}m") else DaemonJVMOptions()
return configureDaemonJVMOptions(
baseOpts,
*additionalArgs.toTypedArray(),
inheritMemoryLimits = xmx > 0,
inheritAdditionalProperties = false)
}
fun testHelloApp() { fun testHelloApp() {
withFlagFile(getTestName(true), ".alive") { flagFile -> withFlagFile(getTestName(true), ".alive") { flagFile ->
val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath, val daemonOptions = makeTestDaemonOptions(getTestName(true))
shutdownDelayMilliseconds = 1,
verbose = true,
reportPerf = true)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
val logFile = createTempFile("kotlin-daemon-test.", ".log") withLogFile("kotlin-daemon-test") { logFile ->
val daemonJVMOptions = makeTestDaemonJvmOptions(logFile)
var daemonShotDown = false
val daemonJVMOptions = configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"", try {
inheritMemoryLimits = false, inheritAdditionalProperties = false) val jar = tmpdir.absolutePath + File.separator + "hello.jar"
var daemonShotDown = false runDaemonCompilerTwice(flagFile, compilerId, daemonJVMOptions, daemonOptions,
"-include-runtime", File(getTestBaseDir(), "hello.kt").absolutePath, "-d", jar)
try {
val jar = tmpdir.absolutePath + File.separator + "hello.jar"
runDaemonCompilerTwice(flagFile, compilerId, daemonJVMOptions, daemonOptions,
"-include-runtime", File(getTestBaseDir(), "hello.kt").absolutePath, "-d", jar)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
Thread.sleep(100)
daemonShotDown = true
var compileTime1 = 0L
var compileTime2 = 0L
logFile.assertLogContainsSequence(
LinePattern("Kotlin compiler daemon version"),
LinePattern("Starting compilation with args: "),
LinePattern("Compile on daemon: (\\d+) ms", { it.groups[1]?.value?.toLong()?.let { compileTime1 = it }; true }),
LinePattern("Starting compilation with args: "),
LinePattern("Compile on daemon: (\\d+) ms", { it.groups[1]?.value?.toLong()?.let { compileTime2 = it }; true }),
LinePattern("Shutdown started"))
assertTrue("Expecting that compilation 1 ($compileTime1 ms) is at least two times longer than compilation 2 ($compileTime2 ms)",
compileTime1 > compileTime2 * 2)
logFile.delete()
run("hello.run", "-cp", jar, "Hello.HelloKt")
}
finally {
if (!daemonShotDown)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
Thread.sleep(100)
daemonShotDown = true
var compileTime1 = 0L
var compileTime2 = 0L
logFile.assertLogContainsSequence(
LinePattern("Kotlin compiler daemon version"),
LinePattern("Starting compilation with args: "),
LinePattern("Compile on daemon: (\\d+) ms", { it.groups[1]?.value?.toLong()?.let { compileTime1 = it }; true }),
LinePattern("Starting compilation with args: "),
LinePattern("Compile on daemon: (\\d+) ms", { it.groups[1]?.value?.toLong()?.let { compileTime2 = it }; true }),
LinePattern("Shutdown started"))
assertTrue("Expecting that compilation 1 ($compileTime1 ms) is at least two times longer than compilation 2 ($compileTime2 ms)",
compileTime1 > compileTime2 * 2)
logFile.delete()
run("hello.run", "-cp", jar, "Hello.HelloKt")
}
finally {
if (!daemonShotDown)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
}
} }
} }
} }
@@ -149,45 +169,42 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
fun testDaemonInstancesSimple() { fun testDaemonInstancesSimple() {
withFlagFile(getTestName(true), ".alive") { flagFile -> withFlagFile(getTestName(true), ".alive") { flagFile ->
val daemonOptions = DaemonOptions(shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath) val daemonOptions = makeTestDaemonOptions(getTestName(true))
val compilerId2 = CompilerId.makeCompilerId(compilerClassPath + val compilerId2 = CompilerId.makeCompilerId(compilerClassPath +
File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler-sources.jar")) File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler-sources.jar"))
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
KotlinCompilerClient.shutdownCompileService(compilerId2, daemonOptions) KotlinCompilerClient.shutdownCompileService(compilerId2, daemonOptions)
val logFile1 = createTempFile("kotlin-daemon1-test", ".log") withLogFile("kotlin-daemon1-test") { logFile1 ->
val logFile2 = createTempFile("kotlin-daemon2-test", ".log") withLogFile("kotlin-daemon2-test") { logFile2 ->
val daemonJVMOptions1 = val daemonJVMOptions1 = makeTestDaemonJvmOptions(logFile1)
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile1.loggerCompatiblePath}\"", val daemonJVMOptions2 = makeTestDaemonJvmOptions(logFile2)
inheritMemoryLimits = false, inheritAdditionalProperties = false)
val daemonJVMOptions2 = assertTrue(logFile1.length() == 0L && logFile2.length() == 0L)
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile2.loggerCompatiblePath}\"",
inheritMemoryLimits = false, inheritAdditionalProperties = false)
assertTrue(logFile1.length() == 0L && logFile2.length() == 0L) val jar1 = tmpdir.absolutePath + File.separator + "hello1.jar"
val res1 = compileOnDaemon(flagFile, compilerId, daemonJVMOptions1, daemonOptions, "-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar1)
assertEquals("first compilation failed:\n${res1.out}", 0, res1.resultCode)
val jar1 = tmpdir.absolutePath + File.separator + "hello1.jar" logFile1.assertLogContainsSequence("Starting compilation with args: ")
val res1 = compileOnDaemon(flagFile, compilerId, daemonJVMOptions1, daemonOptions, "-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar1) assertEquals("expecting '${logFile2.absolutePath}' to be empty", 0L, logFile2.length())
assertEquals("first compilation failed:\n${res1.out}", 0, res1.resultCode)
logFile1.assertLogContainsSequence("Starting compilation with args: ") val jar2 = tmpdir.absolutePath + File.separator + "hello2.jar"
assertEquals("expecting '${logFile2.absolutePath}' to be empty", 0L, logFile2.length()) val res2 = compileOnDaemon(flagFile, compilerId2, daemonJVMOptions2, daemonOptions, "-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar2)
assertEquals("second compilation failed:\n${res2.out}", 0, res1.resultCode)
val jar2 = tmpdir.absolutePath + File.separator + "hello2.jar" logFile2.assertLogContainsSequence("Starting compilation with args: ")
val res2 = compileOnDaemon(flagFile, compilerId2, daemonJVMOptions2, daemonOptions, "-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar2)
assertEquals("second compilation failed:\n${res2.out}", 0, res1.resultCode)
logFile2.assertLogContainsSequence("Starting compilation with args: ") KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
KotlinCompilerClient.shutdownCompileService(compilerId2, daemonOptions)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) Thread.sleep(100)
KotlinCompilerClient.shutdownCompileService(compilerId2, daemonOptions)
Thread.sleep(100) logFile1.assertLogContainsSequence("Shutdown started")
logFile2.assertLogContainsSequence("Shutdown started")
logFile1.assertLogContainsSequence("Shutdown started") }
logFile2.assertLogContainsSequence("Shutdown started") }
} }
} }
@@ -196,25 +213,23 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
val daemonOptions = DaemonOptions(autoshutdownUnusedSeconds = 1, shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath) val daemonOptions = DaemonOptions(autoshutdownUnusedSeconds = 1, shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
val logFile = createTempFile("kotlin-daemon-test", ".log") withLogFile("kotlin-daemon-test") { logFile ->
val daemonJVMOptions = val daemonJVMOptions = makeTestDaemonJvmOptions(logFile)
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) val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
assertNotNull("failed to connect daemon", daemon) assertNotNull("failed to connect daemon", daemon)
daemon?.registerClient(flagFile.absolutePath) daemon?.registerClient(flagFile.absolutePath)
// wait up to 4s (more than 1s unused timeout) // wait up to 4s (more than 1s unused timeout)
for (attempts in 1..20) { for (attempts in 1..20) {
if (logFile.isLogContainsSequence("Unused timeout exceeded 1s")) break if (logFile.isLogContainsSequence("Unused timeout exceeded 1s")) break
Thread.sleep(200)
}
Thread.sleep(200) Thread.sleep(200)
}
Thread.sleep(200)
logFile.assertLogContainsSequence("Unused timeout exceeded 1s", logFile.assertLogContainsSequence("Unused timeout exceeded 1s",
"Shutdown started") "Shutdown started")
logFile.delete() }
} }
} }
@@ -223,30 +238,31 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
val daemonOptions = DaemonOptions(autoshutdownIdleSeconds = 1, shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath) val daemonOptions = DaemonOptions(autoshutdownIdleSeconds = 1, shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
val logFile = createTempFile("kotlin-daemon-test", ".log") withLogFile("kotlin-daemon-test") { logFile ->
val daemonJVMOptions = val daemonJVMOptions = makeTestDaemonJvmOptions(logFile)
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) val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
assertNotNull("failed to connect daemon", daemon) assertNotNull("failed to connect daemon", daemon)
daemon?.registerClient(flagFile.absolutePath) daemon?.registerClient(flagFile.absolutePath)
val jar = tmpdir.absolutePath + File.separator + "hello1.jar" val jar = tmpdir.absolutePath + File.separator + "hello1.jar"
val strm = ByteArrayOutputStream() val strm = ByteArrayOutputStream()
val code = KotlinCompilerClient.compile(daemon!!, CompileService.NO_SESSION, CompileService.TargetPlatform.JVM, arrayOf("-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar), strm) val code = KotlinCompilerClient.compile(daemon!!, CompileService.NO_SESSION, CompileService.TargetPlatform.JVM,
assertEquals("compilation failed:\n$strm", 0, code) arrayOf("-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar),
PrintingMessageCollector(PrintStream(strm), MessageRenderer.WITHOUT_PATHS, true),
reportSeverity = ReportSeverity.DEBUG)
assertEquals("compilation failed:\n$strm", 0, code)
logFile.assertLogContainsSequence("Starting compilation with args: ") logFile.assertLogContainsSequence("Starting compilation with args: ")
// wait up to 4s (more than 1s idle timeout) // wait up to 4s (more than 1s idle timeout)
for (attempts in 1..20) { for (attempts in 1..20) {
if (logFile.isLogContainsSequence("Idle timeout exceeded 1s")) break if (logFile.isLogContainsSequence("Idle timeout exceeded 1s")) break
Thread.sleep(200)
}
Thread.sleep(200) Thread.sleep(200)
logFile.assertLogContainsSequence("Idle timeout exceeded 1s",
"Shutdown started")
} }
Thread.sleep(200)
logFile.assertLogContainsSequence("Idle timeout exceeded 1s",
"Shutdown started")
logFile.delete()
} }
} }
@@ -255,35 +271,33 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
val daemonOptions = DaemonOptions(autoshutdownIdleSeconds = 1, shutdownDelayMilliseconds = 1, forceShutdownTimeoutMilliseconds = 60000, runFilesPath = File(tmpdir, getTestName(true)).absolutePath) val daemonOptions = DaemonOptions(autoshutdownIdleSeconds = 1, shutdownDelayMilliseconds = 1, forceShutdownTimeoutMilliseconds = 60000, runFilesPath = File(tmpdir, getTestName(true)).absolutePath)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
val logFile = createTempFile("kotlin-daemon-test", ".log") withLogFile("kotlin-daemon-test") { logFile ->
val daemonJVMOptions = val daemonJVMOptions = makeTestDaemonJvmOptions(logFile)
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) val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
assertNotNull("failed to connect daemon", daemon) assertNotNull("failed to connect daemon", daemon)
daemon?.registerClient(flagFile.absolutePath) daemon?.registerClient(flagFile.absolutePath)
val sessionId = daemon?.leaseCompileSession(null) val sessionId = daemon?.leaseCompileSession(null)
val scheduleShutdownRes = daemon?.scheduleShutdown(true) val scheduleShutdownRes = daemon?.scheduleShutdown(true)
assertTrue("failed to schedule shutdown ($scheduleShutdownRes)", scheduleShutdownRes?.let { it.isGood && it.get() } ?: false ) assertTrue("failed to schedule shutdown ($scheduleShutdownRes)", scheduleShutdownRes?.let { it.isGood && it.get() } ?: false)
Thread.sleep(100) // to allow timer task to run in the daemon Thread.sleep(100) // to allow timer task to run in the daemon
logFile.assertLogContainsSequence("Some sessions are active, waiting for them to finish") logFile.assertLogContainsSequence("Some sessions are active, waiting for them to finish")
val res = daemon?.getUsedMemory() val res = daemon?.getUsedMemory()
assertEquals("Invalid state", CompileService.CallResult.Dying(), res) assertEquals("Invalid state", CompileService.CallResult.Dying(), res)
daemon?.releaseCompileSession(sessionId!!.get()) daemon?.releaseCompileSession(sessionId!!.get())
Thread.sleep(100) // allow after session timed action to run Thread.sleep(100) // allow after session timed action to run
logFile.assertLogContainsSequence("All sessions finished, shutting down", logFile.assertLogContainsSequence("All sessions finished, shutting down",
"Shutdown started") "Shutdown started")
logFile.delete() }
} }
} }
@@ -299,11 +313,11 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
*/ */
fun testDaemonExecutionViaIntermediateProcess() { fun testDaemonExecutionViaIntermediateProcess() {
val clientAliveFile = createTempFile("kotlin-daemon-transitive-run-test", ".run") val clientAliveFile = createTempFile("kotlin-daemon-transitive-run-test", ".run")
val runFilesPath = File(tmpdir, getTestName(true)).absolutePath val daemonOptions = makeTestDaemonOptions(getTestName(true))
val daemonOptions = DaemonOptions(shutdownDelayMilliseconds = 1, runFilesPath = runFilesPath)
val jar = tmpdir.absolutePath + File.separator + "hello.jar" val jar = tmpdir.absolutePath + File.separator + "hello.jar"
val args = listOf( val args = listOf(
File(File(System.getProperty("java.home"), "bin"), "java").absolutePath, File(File(System.getProperty("java.home"), "bin"), "java").absolutePath,
"-Xmx256m",
"-D$COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY", "-D$COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY",
"-cp", "-cp",
daemonClientClassPath.joinToString(File.pathSeparator) { it.absolutePath }, daemonClientClassPath.joinToString(File.pathSeparator) { it.absolutePath },
@@ -354,74 +368,69 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
assertTrue(PARALLEL_THREADS_TO_COMPILE <= LoopbackNetworkInterface.SERVER_SOCKET_BACKLOG_SIZE) assertTrue(PARALLEL_THREADS_TO_COMPILE <= LoopbackNetworkInterface.SERVER_SOCKET_BACKLOG_SIZE)
withFlagFile(getTestName(true), ".alive") { flagFile -> withFlagFile(getTestName(true), ".alive") { flagFile ->
val daemonOptions = DaemonOptions(shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath) val daemonOptions = makeTestDaemonOptions(getTestName(true))
val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = true, inheritAdditionalProperties = false)
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
assertNotNull("failed to connect daemon", daemon)
val (_, port) = findPortAndCreateRegistry(10, 16384, 65535) withLogFile("kotlin-daemon-test") { logFile ->
val tracer = SynchronizationTracer(CountDownLatch(1), CountDownLatch(PARALLEL_THREADS_TO_COMPILE), port) val daemonJVMOptions = makeTestDaemonJvmOptions(logFile, xmx = -1)
val resultCodes = arrayOfNulls<Int>(PARALLEL_THREADS_TO_COMPILE) val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
val localEndSignal = CountDownLatch(PARALLEL_THREADS_TO_COMPILE) assertNotNull("failed to connect daemon", daemon)
val outStreams = Array(PARALLEL_THREADS_TO_COMPILE, { ByteArrayOutputStream() })
fun runCompile(threadNo: Int) = val (_, port) = findPortAndCreateRegistry(10, 16384, 65535)
thread { val resultCodes = arrayOfNulls<Int>(PARALLEL_THREADS_TO_COMPILE)
val jar = tmpdir.absolutePath + File.separator + "hello.$threadNo.jar" val localEndSignal = CountDownLatch(PARALLEL_THREADS_TO_COMPILE)
val res = KotlinCompilerClient.compile( val outStreams = Array(PARALLEL_THREADS_TO_COMPILE, { ByteArrayOutputStream() })
daemon!!,
CompileService.NO_SESSION, fun runCompile(threadNo: Int) =
CompileService.TargetPlatform.JVM, thread {
arrayOf("-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar), val jar = tmpdir.absolutePath + File.separator + "hello.$threadNo.jar"
outStreams[threadNo], val res = KotlinCompilerClient.compile(
port = port, daemon!!,
operationsTracer = tracer as RemoteOperationsTracer) CompileService.NO_SESSION,
synchronized(resultCodes) { CompileService.TargetPlatform.JVM,
resultCodes[threadNo] = res arrayOf("-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar),
PrintingMessageCollector(PrintStream(outStreams[threadNo]), MessageRenderer.WITHOUT_PATHS, true),
port = port)
synchronized(resultCodes) {
resultCodes[threadNo] = res
}
localEndSignal.countDown()
} }
localEndSignal.countDown()
}
(1..PARALLEL_THREADS_TO_COMPILE).forEach { runCompile(it - 1) } (1..PARALLEL_THREADS_TO_COMPILE).forEach { runCompile(it - 1) }
tracer.startSignal.countDown() val succeeded = localEndSignal.await(PARALLEL_WAIT_TIMEOUT_S, TimeUnit.SECONDS)
val succeeded = tracer.doneSignal.await(PARALLEL_WAIT_TIMEOUT_S, TimeUnit.SECONDS) assertTrue("parallel compilation failed to complete in $PARALLEL_WAIT_TIMEOUT_S s, ${localEndSignal.count} unfinished threads", succeeded)
assertTrue("parallel compilation failed to complete in $PARALLEL_WAIT_TIMEOUT_S s, ${tracer.doneSignal.count} unfinished threads", succeeded)
localEndSignal.await(PARALLEL_WAIT_TIMEOUT_S, TimeUnit.SECONDS) (1..PARALLEL_THREADS_TO_COMPILE).forEach {
(1..PARALLEL_THREADS_TO_COMPILE).forEach { assertEquals("Compilation on thread $it failed:\n${outStreams[it - 1]}", 0, resultCodes[it - 1])
assertEquals("Compilation on thread $it failed:\n${outStreams[it - 1]}", 0, resultCodes[it - 1]) }
} }
} }
} }
fun testParallelDaemonStart() { private val PARALLEL_THREADS_TO_START = 8
val PARALLEL_THREADS_TO_START = 8 fun testParallelDaemonStart() {
val doneLatch = CountDownLatch(PARALLEL_THREADS_TO_START) val doneLatch = CountDownLatch(PARALLEL_THREADS_TO_START)
val resultCodes = arrayOfNulls<Int>(PARALLEL_THREADS_TO_START) val resultCodes = arrayOfNulls<Int>(PARALLEL_THREADS_TO_START)
val outStreams = Array(PARALLEL_THREADS_TO_START, { ByteArrayOutputStream() }) val outStreams = Array(PARALLEL_THREADS_TO_START, { ByteArrayOutputStream() })
val logFiles = arrayOfNulls<File>(PARALLEL_THREADS_TO_START) val logFiles = arrayOfNulls<File>(PARALLEL_THREADS_TO_START)
val daemonOptions = makeTestDaemonOptions(getTestName(true), 100)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
fun connectThread(threadNo: Int) = thread(name = "daemonConnect$threadNo") { fun connectThread(threadNo: Int) = thread(name = "daemonConnect$threadNo") {
try { try {
withFlagFile(getTestName(true), ".alive") { flagFile -> withFlagFile(getTestName(true), ".alive") { flagFile ->
withFlagFile(getTestName(true), ".salive") { sessionFlagFile -> withLogFile("kotlin-daemon-test") { logFile ->
val daemonOptions = DaemonOptions(shutdownDelayMilliseconds = 10000, // attempt to avoid immediate shutdown of the non-elected daemons
runFilesPath = File(tmpdir, getTestName(true)).absolutePath,
verbose = true)
val logFile = createTempFile("kotlin-daemon-test", ".log")
logFiles[threadNo] = logFile logFiles[threadNo] = logFile
val daemonJVMOptions = val daemonJVMOptions = makeTestDaemonJvmOptions(logFile)
configureDaemonJVMOptions(DaemonJVMOptions(maxMemory = "2048m"), val compileServiceSession =
"D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.loggerCompatiblePath}\"", KotlinCompilerClient.connectAndLease(compilerId, flagFile, daemonJVMOptions, daemonOptions,
inheritMemoryLimits = false, inheritAdditionalProperties = false) DaemonReportingTargets(out = System.err), autostart = true,
val compileServiceSession = KotlinCompilerClient.connectAndLease(compilerId, flagFile, daemonJVMOptions, daemonOptions, leaseSession = true)
DaemonReportingTargets(out = System.err), autostart = true,
leaseSession = true, sessionAliveFlagFile = sessionFlagFile)
assertNotNull("failed to connect daemon", compileServiceSession?.compileService) assertNotNull("failed to connect daemon", compileServiceSession?.compileService)
val jar = tmpdir.absolutePath + File.separator + "hello.$threadNo.jar" val jar = tmpdir.absolutePath + File.separator + "hello.$threadNo.jar"
val res = KotlinCompilerClient.compile( val res = KotlinCompilerClient.compile(
@@ -429,8 +438,9 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
compileServiceSession.sessionId, compileServiceSession.sessionId,
CompileService.TargetPlatform.JVM, CompileService.TargetPlatform.JVM,
arrayOf(File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar), arrayOf(File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar),
outStreams[threadNo]) PrintingMessageCollector(PrintStream(outStreams[threadNo]), MessageRenderer.WITHOUT_PATHS, true))
resultCodes[threadNo] = res resultCodes[threadNo] = res
assertEquals("Compilation on thread $threadNo failed", 0, res)
} }
} }
} }
@@ -457,22 +467,19 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
val electionLogs = (0..(PARALLEL_THREADS_TO_START - 1)).map { val electionLogs = (0..(PARALLEL_THREADS_TO_START - 1)).map {
val logContents = logFiles[it]?.readLines() val logContents = logFiles[it]?.readLines()
assertEquals("Compilation on thread $it failed:\n${outStreams[it]}\n\n------- daemon log: -------\n${logContents?.joinToString("\n")}\n-------", 0, resultCodes[it])
logContents?.find { it.contains(LOG_PREFIX_ASSUMING_OTHER_DAEMONS_HAVE) } logContents?.find { it.contains(LOG_PREFIX_ASSUMING_OTHER_DAEMONS_HAVE) }
} }
assertTrue("No daemon elected: \n${electionLogs.joinToString("\n")}", assertTrue("No daemon elected: \n${electionLogs.joinToString("\n")}",
electionLogs.any { it != null && (it.contains("lower prio") || it.contains("equal prio")) }) electionLogs.any { it != null && (it.contains("lower prio") || it.contains("equal prio")) })
logFiles.forEach { it?.delete() }
} }
fun testDaemonConnectionProblems() { fun testDaemonConnectionProblems() {
withFlagFile(getTestName(true), ".alive") { flagFile -> withFlagFile(getTestName(true), ".alive") { flagFile ->
val daemonOptions = DaemonOptions(shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath) val daemonOptions = makeTestDaemonOptions(getTestName(true))
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false) val daemonJVMOptions = makeTestDaemonJvmOptions()
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
assertNotNull("failed to connect daemon", daemon) assertNotNull("failed to connect daemon", daemon)
daemon?.registerClient(flagFile.absolutePath) daemon?.registerClient(flagFile.absolutePath)
@@ -500,48 +507,46 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
fun testDaemonCallbackConnectionProblems() { fun testDaemonCallbackConnectionProblems() {
withFlagFile(getTestName(true), ".alive") { flagFile -> withFlagFile(getTestName(true), ".alive") { flagFile ->
val daemonOptions = DaemonOptions(shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath) val daemonOptions = makeTestDaemonOptions(getTestName(true))
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
val logFile = createTempFile("kotlin-daemon-test", ".log") withLogFile("kotlin-daemon-test") { logFile ->
val daemonJVMOptions = val daemonJVMOptions = makeTestDaemonJvmOptions(logFile)
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) val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
assertNotNull("failed to connect daemon", daemon) assertNotNull("failed to connect daemon", daemon)
daemon?.registerClient(flagFile.absolutePath) daemon?.registerClient(flagFile.absolutePath)
val file = File(tmpdir, "largeKotlinFile.kt") val file = File(tmpdir, "largeKotlinFile.kt")
file.writeText(generateLargeKotlinFile(10)) file.writeText(generateLargeKotlinFile(10))
val jar = File(tmpdir, "largeKotlinFile.jar").absolutePath val jar = File(tmpdir, "largeKotlinFile.jar").absolutePath
var callbackServices: CompilerCallbackServicesFacadeServer? = null var callbackServices: CompilerCallbackServicesFacadeServer? = null
callbackServices = CompilerCallbackServicesFacadeServer(compilationCanceledStatus = object : CompilationCanceledStatus { callbackServices = CompilerCallbackServicesFacadeServer(compilationCanceledStatus = object : CompilationCanceledStatus {
override fun checkCanceled() { override fun checkCanceled() {
thread { thread {
Thread.sleep(10) Thread.sleep(10)
UnicastRemoteObject.unexportObject(callbackServices, true) UnicastRemoteObject.unexportObject(callbackServices, true)
}
} }
} }, port = SOCKET_ANY_FREE_PORT)
}, port = SOCKET_ANY_FREE_PORT) val strm = ByteArrayOutputStream()
val strm = ByteArrayOutputStream() val code = daemon!!.remoteCompile(CompileService.NO_SESSION,
val code = daemon!!.remoteCompile(CompileService.NO_SESSION, CompileService.TargetPlatform.JVM,
CompileService.TargetPlatform.JVM, arrayOf("-include-runtime", file.absolutePath, "-d", jar),
arrayOf("-include-runtime", file.absolutePath, "-d", jar), callbackServices,
callbackServices, RemoteOutputStreamServer(strm, SOCKET_ANY_FREE_PORT),
RemoteOutputStreamServer(strm, SOCKET_ANY_FREE_PORT), CompileService.OutputFormat.XML,
CompileService.OutputFormat.XML, RemoteOutputStreamServer(strm, SOCKET_ANY_FREE_PORT),
RemoteOutputStreamServer(strm, SOCKET_ANY_FREE_PORT), null).get()
null).get()
TestCase.assertEquals(0, code) TestCase.assertEquals(0, code)
val compilerOutput = strm.toString() val compilerOutput = strm.toString()
assertTrue("Expecting cancellation message in:\n$compilerOutput", compilerOutput.contains("Compilation was canceled")) assertTrue("Expecting cancellation message in:\n$compilerOutput", compilerOutput.contains("Compilation was canceled"))
logFile.assertLogContainsSequence("error communicating with host, assuming compilation canceled") logFile.assertLogContainsSequence("error communicating with host, assuming compilation canceled")
logFile.delete() }
} }
} }
@@ -608,53 +613,53 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
val daemonOptions = DaemonOptions(autoshutdownIdleSeconds = 1, autoshutdownUnusedSeconds = 1, shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath) val daemonOptions = DaemonOptions(autoshutdownIdleSeconds = 1, autoshutdownUnusedSeconds = 1, shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions) KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
val logFile = createTempFile("kotlin-daemon-test", ".log") withLogFile("kotlin-daemon-test") { logFile ->
val daemonJVMOptions = val daemonJVMOptions = makeTestDaemonJvmOptions(logFile)
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) val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
assertNotNull("failed to connect daemon", daemon) assertNotNull("failed to connect daemon", daemon)
val replCompiler = KotlinRemoteReplCompilerClient(daemon!!, null, CompileService.TargetPlatform.JVM, val replCompiler = KotlinRemoteReplCompilerClient(daemon!!, null, CompileService.TargetPlatform.JVM,
emptyArray(), emptyArray(),
TestMessageCollector(), TestMessageCollector(),
classpathFromClassloader(), classpathFromClassloader(),
ScriptWithNoParam::class.qualifiedName!!) ScriptWithNoParam::class.qualifiedName!!)
val compilerState = replCompiler.createState() val compilerState = replCompiler.createState()
// use repl compiler for >> 1s, making sure that idle/unused timeouts are not firing
for (attempts in 1..10) {
val codeLine1 = ReplCodeLine(attempts, 0, "3 + 5")
val res1 = replCompiler.compile(compilerState, codeLine1)
val res1c = res1 as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $res1", res1c)
Thread.sleep(200)
}
// wait up to 4s (more than 1s idle timeout)
for (attempts in 1..20) {
if (logFile.isLogContainsSequence("Idle timeout exceeded 1s")) break
Thread.sleep(200)
}
replCompiler.dispose()
// use repl compiler for >> 1s, making sure that idle/unused timeouts are not firing
for (attempts in 1..10) {
val codeLine1 = ReplCodeLine(attempts, 0, "3 + 5")
val res1 = replCompiler.compile(compilerState, codeLine1)
val res1c = res1 as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $res1", res1c)
Thread.sleep(200) Thread.sleep(200)
logFile.assertLogContainsSequence("Idle timeout exceeded 1s",
"Shutdown started")
} }
// wait up to 4s (more than 1s idle timeout)
for (attempts in 1..20) {
if (logFile.isLogContainsSequence("Idle timeout exceeded 1s")) break
Thread.sleep(200)
}
replCompiler.dispose()
Thread.sleep(200)
logFile.assertLogContainsSequence("Idle timeout exceeded 1s",
"Shutdown started")
logFile.delete()
} }
} }
internal fun withDaemon(body: (CompileService) -> Unit) { internal fun withDaemon(body: (CompileService) -> Unit) {
withFlagFile(getTestName(true), ".alive") { flagFile -> withFlagFile(getTestName(true), ".alive") { flagFile ->
val daemonOptions = DaemonOptions(shutdownDelayMilliseconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath) val daemonOptions = makeTestDaemonOptions(getTestName(true))
val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false) withLogFile("kotlin-daemon-test") { logFile ->
val daemon: CompileService? = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) val daemonJVMOptions = makeTestDaemonJvmOptions(logFile)
assertNotNull("failed to connect daemon", daemon) val daemon: CompileService? = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
assertNotNull("failed to connect daemon", daemon)
body(daemon!!) body(daemon!!)
}
} }
} }
} }
@@ -727,6 +732,17 @@ internal inline fun withFlagFile(prefix: String, suffix: String? = null, body: (
} }
} }
internal inline fun withLogFile(prefix: String, suffix: String = ".log", body: (File) -> Unit) {
val logFile = createTempFile(prefix, suffix)
try {
body(logFile)
}
catch (e: Exception) {
System.err.println("--- log file ${logFile.name}:\n${logFile.readText()}\n---")
throw e
}
}
// java.util.Logger used in the daemon silently forgets to log into a file specified in the config on Windows, // java.util.Logger used in the daemon silently forgets to log into a file specified in the config on Windows,
// if file path is given in windows form (using backslash as a separator); the reason is unknown // if file path is given in windows form (using backslash as a separator); the reason is unknown
// this function makes a path with forward slashed, that works on windows too // this function makes a path with forward slashed, that works on windows too