fixes after review
This commit is contained in:
@@ -20,6 +20,7 @@ import net.rubygrapefruit.platform.ProcessLauncher
|
||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
|
||||
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
||||
import org.jetbrains.kotlin.rmi.*
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.check
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
import java.io.PrintStream
|
||||
@@ -57,9 +58,9 @@ public object KotlinCompilerClient {
|
||||
|
||||
val flagFile = System.getProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY)
|
||||
?.let { it.trimQuotes() }
|
||||
?.ifOrNull { !it.isBlank() }
|
||||
?.check { !it.isBlank() }
|
||||
?.let { File(it) }
|
||||
?.ifOrNull { it.exists() }
|
||||
?.check { it.exists() }
|
||||
?: newFlagFile()
|
||||
return connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, reportingTargets, autostart)
|
||||
}
|
||||
@@ -82,13 +83,11 @@ public object KotlinCompilerClient {
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "connected to the daemon")
|
||||
return service
|
||||
}
|
||||
else {
|
||||
if (!autostart) return null
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "no suitable daemon found, starting a new one")
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "no suitable daemon found")
|
||||
if (autostart) {
|
||||
startDaemon(compilerId, newJVMOptions, daemonOptions, reportingTargets)
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "new daemon started, trying to find it")
|
||||
}
|
||||
|
||||
startDaemon(compilerId, newJVMOptions, daemonOptions, reportingTargets)
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "daemon started, trying to find it")
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
@@ -258,13 +257,13 @@ public object KotlinCompilerClient {
|
||||
.map { Pair(it, it.getDaemonJVMOptions()) }
|
||||
.filter { it.second.isGood }
|
||||
.sortedWith(compareBy(DaemonJVMOptionsMemoryComparator().reversed(), { it.second.get() }))
|
||||
val opts = daemonJVMOptions.copy()
|
||||
val optsCopy = daemonJVMOptions.copy()
|
||||
// if required options fit into fattest running daemon - return the daemon and required options with memory params set to actual ones in the daemon
|
||||
return aliveWithOpts.firstOrNull()?.ifOrNull { daemonJVMOptions memorywiseFitsInto it.second.get() }?.let {
|
||||
Pair(it.first, opts.updateMemoryUpperBounds(it.second.get()))
|
||||
return aliveWithOpts.firstOrNull()?.check { daemonJVMOptions memorywiseFitsInto it.second.get() }?.let {
|
||||
Pair(it.first, optsCopy.updateMemoryUpperBounds(it.second.get()))
|
||||
}
|
||||
// else combine all options from running daemon to get fattest option for a new daemon to run
|
||||
?: Pair(null, aliveWithOpts.fold(daemonJVMOptions, { opts, d -> opts.updateMemoryUpperBounds(d.second.get()) }))
|
||||
?: Pair(null, aliveWithOpts.fold(optsCopy, { opts, d -> opts.updateMemoryUpperBounds(d.second.get()) }))
|
||||
}
|
||||
|
||||
|
||||
@@ -353,6 +352,3 @@ internal fun isProcessAlive(process: Process) =
|
||||
catch (e: IllegalThreadStateException) {
|
||||
true
|
||||
}
|
||||
|
||||
internal inline fun<T> T.ifOrNull(pred: (T) -> Boolean): T? = if (pred(this)) this else null
|
||||
|
||||
|
||||
@@ -29,30 +29,33 @@ enum class DaemonReportCategory {
|
||||
}
|
||||
|
||||
|
||||
fun makeRunFilenameString(timestamp: String, digest: String, port: String, escapeSequence: String = ""): String = "$COMPILE_DAEMON_DEFAULT_FILES_PREFIX$escapeSequence.$timestamp$escapeSequence.$digest$escapeSequence.$port$escapeSequence.run"
|
||||
fun makeRunFilenameString(timestamp: String, digest: String, port: String, escapeSequence: String = ""): String =
|
||||
"$COMPILE_DAEMON_DEFAULT_FILES_PREFIX$escapeSequence.$timestamp$escapeSequence.$digest$escapeSequence.$port$escapeSequence.run"
|
||||
|
||||
|
||||
fun String.extractPortFromRunFilename(digest: String): Int =
|
||||
makeRunFilenameString(timestamp = "[0-9TZ:\\.\\+-]+", digest = digest, port = "(\\d+)", escapeSequence = "\\").toRegex()
|
||||
.find(this)
|
||||
?.groups?.get(1)
|
||||
?.value?.toInt()
|
||||
?: 0
|
||||
fun makePortFromRunFilenameExtractor(digest: String): (String) -> Int? {
|
||||
val regex = makeRunFilenameString(timestamp = "[0-9TZ:\\.\\+-]+", digest = digest, port = "(\\d+)", escapeSequence = "\\").toRegex()
|
||||
return { regex.find(it)
|
||||
?.groups?.get(1)
|
||||
?.value?.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun walkDaemons(registryDir: File,
|
||||
compilerId: CompilerId,
|
||||
filter: (File, Int) -> Boolean = { f,p -> true },
|
||||
report: (DaemonReportCategory, String) -> Unit = { cat, msg -> ; }
|
||||
filter: (File, Int) -> Boolean = { f, p -> true },
|
||||
report: (DaemonReportCategory, String) -> Unit = { cat, msg -> }
|
||||
): Sequence<CompileService> {
|
||||
val classPathDigest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest().toHexString()
|
||||
val portExtractor = makePortFromRunFilenameExtractor(classPathDigest)
|
||||
return registryDir.walk()
|
||||
.map { Pair(it, it.name.extractPortFromRunFilename(classPathDigest)) }
|
||||
.filter { it.second != 0 && filter(it.first, it.second) }
|
||||
.map { Pair(it, portExtractor(it.name)) }
|
||||
.filter { it.second != null && filter(it.first, it.second!!) }
|
||||
.map {
|
||||
assert(it.second > 0 && it.second < MAX_PORT_NUMBER)
|
||||
assert(it.second!! > 0 && it.second!! < MAX_PORT_NUMBER)
|
||||
report(DaemonReportCategory.DEBUG, "found daemon on port ${it.second}, trying to connect")
|
||||
val daemon = tryConnectToDaemon(it.second, report)
|
||||
val daemon = tryConnectToDaemon(it.second!!, report)
|
||||
// cleaning orphaned file; note: daemon should shut itself down if it detects that the run file is deleted
|
||||
if (daemon == null && !it.first.delete()) {
|
||||
report(DaemonReportCategory.INFO, "WARNING: unable to delete seemingly orphaned file '${it.first.absolutePath}', cleanup recommended")
|
||||
|
||||
@@ -313,25 +313,26 @@ public fun configureDaemonOptions(opts: DaemonOptions): DaemonOptions {
|
||||
public fun configureDaemonOptions(): DaemonOptions = configureDaemonOptions(DaemonOptions())
|
||||
|
||||
|
||||
fun String.memToBytes(): Long? =
|
||||
"(\\d+)([kmg]?)".toRegex()
|
||||
.matchEntire(this.trim().toLowerCase())
|
||||
?.groups?.let { match ->
|
||||
match.get(1)?.value?.let {
|
||||
it.toLong() *
|
||||
when (match.get(2)?.value) {
|
||||
"k" -> 1 shl 10
|
||||
"m" -> 1 shl 20
|
||||
"g" -> 1 shl 30
|
||||
else -> 1
|
||||
}
|
||||
private val humanizedMemorySizeRegex = "(\\d+)([kmg]?)".toRegex()
|
||||
|
||||
private fun String.memToBytes(): Long? =
|
||||
humanizedMemorySizeRegex
|
||||
.matchEntire(this.trim().toLowerCase())
|
||||
?.groups?.let { match ->
|
||||
match[1]?.value?.let {
|
||||
it.toLong() *
|
||||
when (match[2]?.value) {
|
||||
"k" -> 1 shl 10
|
||||
"m" -> 1 shl 20
|
||||
"g" -> 1 shl 30
|
||||
else -> 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val daemonJVMOptionsMemoryProps: List<KMutableProperty1<DaemonJVMOptions, String>> by lazy {
|
||||
private val daemonJVMOptionsMemoryProps =
|
||||
listOf(DaemonJVMOptions::maxMemory, DaemonJVMOptions::maxPermSize, DaemonJVMOptions::reservedCodeCacheSize)
|
||||
}
|
||||
|
||||
infix fun DaemonJVMOptions.memorywiseFitsInto(other: DaemonJVMOptions): Boolean =
|
||||
daemonJVMOptionsMemoryProps
|
||||
|
||||
+19
-21
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.config.Services
|
||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
|
||||
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
||||
import org.jetbrains.kotlin.rmi.*
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.check
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
@@ -86,9 +87,9 @@ class CompileServiceImpl(
|
||||
|
||||
private val classpathWatcher = LazyClasspathWatcher(compilerId.compilerClasspath)
|
||||
|
||||
enum class Aliveness(val value: Int) {
|
||||
// ordering of values is used in state comparison
|
||||
Alive(2), LastSession(1), Dying(0)
|
||||
enum class Aliveness {
|
||||
// !!! ordering of values is used in state comparison
|
||||
Dying, LastSession, Alive
|
||||
}
|
||||
|
||||
// TODO: encapsulate operations on state here
|
||||
@@ -99,13 +100,13 @@ class CompileServiceImpl(
|
||||
|
||||
val delayedShutdownQueued = AtomicBoolean(false)
|
||||
|
||||
var alive = AtomicInteger(Aliveness.Alive.value)
|
||||
var alive = AtomicInteger(Aliveness.Alive.ordinal)
|
||||
}
|
||||
|
||||
@Volatile private var _lastUsedSeconds = nowSeconds()
|
||||
public val lastUsedSeconds: Long get() = if (rwlock.isWriteLocked || rwlock.readLockCount - rwlock.readHoldCount > 0) nowSeconds() else _lastUsedSeconds
|
||||
|
||||
val log by lazy { Logger.getLogger("compiler") }
|
||||
private val log by lazy { Logger.getLogger("compiler") }
|
||||
|
||||
private val rwlock = ReentrantReadWriteLock()
|
||||
|
||||
@@ -154,6 +155,7 @@ class CompileServiceImpl(
|
||||
synchronized(state.sessions) {
|
||||
if (!state.sessions.containsKey(newId)) {
|
||||
state.sessions.put(newId, session)
|
||||
log.info("leased a new session $newId, client alive file: $aliveFlagPath")
|
||||
return@ifAlive newId
|
||||
}
|
||||
}
|
||||
@@ -161,13 +163,13 @@ class CompileServiceImpl(
|
||||
// assuming wrap, jumping to random number to reduce probability of further clashes
|
||||
newId = sessionsIdCounter.addAndGet(internalRng.nextInt())
|
||||
}
|
||||
throw Exception("Invalid state or algorithm error")
|
||||
throw IllegalStateException("Invalid state or algorithm error")
|
||||
}
|
||||
|
||||
override fun releaseCompileSession(sessionId: Int) = ifAlive_Nothing(minAliveness = Aliveness.LastSession) {
|
||||
synchronized(state.sessions) {
|
||||
state.sessions.remove(sessionId)
|
||||
log.info("cleaning after session")
|
||||
log.info("cleaning after session $sessionId")
|
||||
clearJarCache()
|
||||
if (state.sessions.isEmpty()) {
|
||||
// TODO: and some goes here
|
||||
@@ -190,7 +192,7 @@ class CompileServiceImpl(
|
||||
}
|
||||
|
||||
override fun scheduleShutdown(graceful: Boolean): CompileService.CallResult<Boolean> = ifAlive(minAliveness = Aliveness.Alive) {
|
||||
if (!graceful || state.alive.compareAndSet(Aliveness.Alive.value, Aliveness.LastSession.value)) {
|
||||
if (!graceful || state.alive.compareAndSet(Aliveness.Alive.ordinal, Aliveness.LastSession.ordinal)) {
|
||||
timer.schedule(0) {
|
||||
ifAliveExclusive(minAliveness = Aliveness.LastSession, ignoreCompilerChanged = true) {
|
||||
if (!graceful || state.sessions.isEmpty()) {
|
||||
@@ -299,13 +301,13 @@ class CompileServiceImpl(
|
||||
}.forEach { releaseCompileSession(it) }
|
||||
|
||||
// 3. check if in graceful shutdown state and all sessions are closed
|
||||
if (shutdownCondition({state.alive.get() == Aliveness.LastSession.value && state.sessions.none()}, "All sessions finished, shutting down")) {
|
||||
if (shutdownCondition({state.alive.get() == Aliveness.LastSession.ordinal && state.sessions.none()}, "All sessions finished, shutting down")) {
|
||||
shutdown()
|
||||
}
|
||||
|
||||
// 4. clean dead clients, then check if any left - conditional shutdown (with small delay)
|
||||
synchronized(state.clientProxies) { state.clientProxies.removeAll(state.clientProxies.filter { !it.isAlive }) }
|
||||
if (state.clientProxies.none() && compilationsCounter.get() > 0 && !state.delayedShutdownQueued.get()) {
|
||||
if (state.clientProxies.isEmpty() && compilationsCounter.get() > 0 && !state.delayedShutdownQueued.get()) {
|
||||
log.info("No more clients left, delayed shutdown in ${daemonOptions.shutdownDelayMilliseconds}ms")
|
||||
shutdownWithDelay()
|
||||
}
|
||||
@@ -338,7 +340,7 @@ class CompileServiceImpl(
|
||||
if (fattestOpts memorywiseFitsInto daemonJVMOptions && !(daemonJVMOptions memorywiseFitsInto fattestOpts)) {
|
||||
// all others are smaller that me, take overs' clients and shut them down
|
||||
aliveWithOpts.forEach {
|
||||
it.first.getClients().ifOrNull { it.isGood }?.let {
|
||||
it.first.getClients().check { it.isGood }?.let {
|
||||
it.get().forEach { registerClient(it) }
|
||||
}
|
||||
it.first.scheduleShutdown(true)
|
||||
@@ -348,14 +350,14 @@ class CompileServiceImpl(
|
||||
// there is at least one bigger, handover my clients to it and shutdown
|
||||
scheduleShutdown(true)
|
||||
aliveWithOpts.first().first.let { fattest ->
|
||||
getClients().ifOrNull { it.isGood }?.let {
|
||||
getClients().check { it.isGood }?.let {
|
||||
it.get().forEach { fattest.registerClient(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
// else - do nothing, all daemons are staying
|
||||
// TODO: implement some behaviour here, e.g.:
|
||||
// - shutdown/takeover smaller daemosn
|
||||
// - shutdown/takeover smaller daemon
|
||||
// - run (or better persuade client to run) a bigger daemon (in fact may be even simple shutdown will do, because of client's daemon choosing logic)
|
||||
}
|
||||
}
|
||||
@@ -363,7 +365,7 @@ class CompileServiceImpl(
|
||||
|
||||
private fun shutdownImpl() {
|
||||
log.info("Shutdown started")
|
||||
state.alive.set(Aliveness.Dying.value)
|
||||
state.alive.set(Aliveness.Dying.ordinal)
|
||||
UnicastRemoteObject.unexportObject(this, true)
|
||||
log.info("Shutdown complete")
|
||||
onShutdown()
|
||||
@@ -384,7 +386,7 @@ class CompileServiceImpl(
|
||||
}
|
||||
}
|
||||
|
||||
private fun shutdownCondition(check: () -> Boolean, message: String): Boolean {
|
||||
private inline fun shutdownCondition(check: () -> Boolean, message: String): Boolean {
|
||||
val res = check()
|
||||
if (res) {
|
||||
log.info(message)
|
||||
@@ -476,7 +478,7 @@ class CompileServiceImpl(
|
||||
}
|
||||
|
||||
private fun clearJarCache() {
|
||||
callVoidStaticMethod("com.intellij.openapi.vfs.impl.ZipHandler", "clearFileAccessorCache")
|
||||
com.intellij.openapi.vfs.impl.ZipHandler.clearFileAccessorCache()
|
||||
val classloader = javaClass.classLoader
|
||||
// TODO: replace the following code with direct call to CoreJarFileSystem.<clearCache> as soon as it will be available (hopefully in 15.02)
|
||||
try {
|
||||
@@ -539,7 +541,7 @@ class CompileServiceImpl(
|
||||
|
||||
inline private fun<R> ifAliveChecksImpl(minAliveness: Aliveness = Aliveness.Alive, ignoreCompilerChanged: Boolean = false, body: () -> CompileService.CallResult<R>): CompileService.CallResult<R> =
|
||||
when {
|
||||
state.alive.get() < minAliveness.value -> CompileService.CallResult.Dying()
|
||||
state.alive.get() < minAliveness.ordinal -> CompileService.CallResult.Dying()
|
||||
!ignoreCompilerChanged && classpathWatcher.isChanged -> {
|
||||
log.info("Compiler changed, scheduling shutdown")
|
||||
timer.schedule(0) { shutdown() }
|
||||
@@ -555,8 +557,4 @@ class CompileServiceImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal inline fun<T> T.ifOrNull(pred: (T) -> Boolean): T? = if (pred(this)) this else null
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.io.File
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.test.fail
|
||||
|
||||
|
||||
val TIMEOUT_DAEMON_RUNNER_EXIT_MS = 10000L
|
||||
@@ -44,7 +45,7 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
|
||||
private fun compileOnDaemon(clientAliveFile: File, compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, vararg args: String): CompilerResults {
|
||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, clientAliveFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
|
||||
TestCase.assertNotNull("failed to connect daemon", daemon)
|
||||
assertNotNull("failed to connect daemon", daemon)
|
||||
daemon?.registerClient(clientAliveFile.absolutePath)
|
||||
val strm = ByteArrayOutputStream()
|
||||
val code = KotlinCompilerClient.compile(daemon!!, CompileService.NO_SESSION, CompileService.TargetPlatform.JVM, args, strm)
|
||||
@@ -53,10 +54,10 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
|
||||
private fun runDaemonCompilerTwice(clientAliveFile: File, compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, vararg args: String): Unit {
|
||||
val res1 = compileOnDaemon(clientAliveFile, compilerId, daemonJVMOptions, daemonOptions, *args)
|
||||
TestCase.assertEquals("first compilation failed:\n${res1.out}", 0, res1.resultCode)
|
||||
assertEquals("first compilation failed:\n${res1.out}", 0, res1.resultCode)
|
||||
val res2 = compileOnDaemon(clientAliveFile, compilerId, daemonJVMOptions, daemonOptions, *args)
|
||||
TestCase.assertEquals("second compilation failed:\n${res2.out}", 0, res2.resultCode)
|
||||
TestCase.assertEquals("build results differ", CliBaseTest.removePerfOutput(res1.out), CliBaseTest.removePerfOutput(res2.out))
|
||||
assertEquals("second compilation failed:\n${res2.out}", 0, res2.resultCode)
|
||||
assertEquals("build results differ", CliBaseTest.removePerfOutput(res1.out), CliBaseTest.removePerfOutput(res2.out))
|
||||
}
|
||||
|
||||
private fun getTestBaseDir(): String = KotlinTestUtils.getTestDataPathBase() + "/integration/smoke/" + getTestName(true)
|
||||
@@ -66,7 +67,7 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
|
||||
|
||||
public fun testHelloApp() {
|
||||
witFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
withFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath,
|
||||
verbose = true,
|
||||
reportPerf = true)
|
||||
@@ -96,12 +97,12 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
LinePattern("Compile on daemon: (\\d+) ms", { it.groups.get(1)?.value?.toLong()?.let { compileTime2 = it }; true }),
|
||||
LinePattern("Shutdown complete"))
|
||||
{ unmatchedPattern, lineNo ->
|
||||
TestCase.fail("pattern not found in the input: " + unmatchedPattern.regex +
|
||||
fail("pattern not found in the input: " + unmatchedPattern.regex +
|
||||
"\nunmatched part of the log file (" + logFile.absolutePath +
|
||||
") from line " + lineNo + ":\n\n" + logFile.reader().useLines { it.drop(lineNo).joinToString("\n") })
|
||||
}
|
||||
}
|
||||
TestCase.assertTrue("Expecting that compilation 1 ($compileTime1 ms) is at least two times longer than compilation 2 ($compileTime2 ms)",
|
||||
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")
|
||||
@@ -118,10 +119,10 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
try {
|
||||
System.setProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY, "-aaa,-bbb\\,ccc,-ddd,-Xmx200m,-XX:MaxPermSize=10k,-XX:ReservedCodeCacheSize=100,-xxx\\,yyy")
|
||||
val opts = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||
TestCase.assertEquals("200m", opts.maxMemory)
|
||||
TestCase.assertEquals("10k", opts.maxPermSize)
|
||||
TestCase.assertEquals("100", opts.reservedCodeCacheSize)
|
||||
TestCase.assertEquals(arrayListOf("aaa", "bbb,ccc", "ddd", "xxx,yyy"), opts.jvmParams)
|
||||
assertEquals("200m", opts.maxMemory)
|
||||
assertEquals("10k", opts.maxPermSize)
|
||||
assertEquals("100", opts.reservedCodeCacheSize)
|
||||
assertEquals(arrayListOf("aaa", "bbb,ccc", "ddd", "xxx,yyy"), opts.jvmParams)
|
||||
}
|
||||
finally {
|
||||
restoreSystemProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY, backupJvmOptions)
|
||||
@@ -133,8 +134,8 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
try {
|
||||
System.setProperty(COMPILE_DAEMON_OPTIONS_PROPERTY, "runFilesPath=abcd,autoshutdownIdleSeconds=1111")
|
||||
val opts = configureDaemonOptions()
|
||||
TestCase.assertEquals("abcd", opts.runFilesPath)
|
||||
TestCase.assertEquals(1111, opts.autoshutdownIdleSeconds)
|
||||
assertEquals("abcd", opts.runFilesPath)
|
||||
assertEquals(1111, opts.autoshutdownIdleSeconds)
|
||||
}
|
||||
finally {
|
||||
restoreSystemProperty(COMPILE_DAEMON_OPTIONS_PROPERTY, backupOptions)
|
||||
@@ -142,9 +143,7 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
}
|
||||
|
||||
public fun testDaemonInstancesSimple() {
|
||||
val jar1 = tmpdir.absolutePath + File.separator + "hello1.jar"
|
||||
val jar2 = tmpdir.absolutePath + File.separator + "hello2.jar"
|
||||
witFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
withFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath)
|
||||
val compilerId2 = CompilerId.makeCompilerId(compilerClassPath +
|
||||
File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler-sources.jar"))
|
||||
@@ -162,16 +161,18 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile2.loggerCompatiblePath}\"",
|
||||
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||
|
||||
TestCase.assertTrue(logFile1.length() == 0L && logFile2.length() == 0L)
|
||||
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)
|
||||
TestCase.assertEquals("first compilation failed:\n${res1.out}", 0, res1.resultCode)
|
||||
assertEquals("first compilation failed:\n${res1.out}", 0, res1.resultCode)
|
||||
|
||||
logFile1.assertLogContainsSequence("Starting compilation with args: ")
|
||||
TestCase.assertEquals("expecting '${logFile2.absolutePath}' to be empty", 0L, logFile2.length())
|
||||
assertEquals("expecting '${logFile2.absolutePath}' to be empty", 0L, logFile2.length())
|
||||
|
||||
val jar2 = tmpdir.absolutePath + File.separator + "hello2.jar"
|
||||
val res2 = compileOnDaemon(flagFile, compilerId2, daemonJVMOptions2, daemonOptions, "-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar2)
|
||||
TestCase.assertEquals("second compilation failed:\n${res2.out}", 0, res1.resultCode)
|
||||
assertEquals("second compilation failed:\n${res2.out}", 0, res1.resultCode)
|
||||
|
||||
logFile2.assertLogContainsSequence("Starting compilation with args: ")
|
||||
|
||||
@@ -186,7 +187,7 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
}
|
||||
|
||||
public fun testDaemonAutoshutdownOnUnused() {
|
||||
witFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
withFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
val daemonOptions = DaemonOptions(autoshutdownUnusedSeconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath)
|
||||
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
|
||||
|
||||
@@ -196,7 +197,7 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||
|
||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
|
||||
TestCase.assertNotNull("failed to connect daemon", daemon)
|
||||
assertNotNull("failed to connect daemon", daemon)
|
||||
daemon?.registerClient(flagFile.absolutePath)
|
||||
|
||||
// wait up to 4s (more than 1s unused timeout)
|
||||
@@ -213,8 +214,7 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
}
|
||||
|
||||
public fun testDaemonAutoshutdownOnIdle() {
|
||||
val jar = tmpdir.absolutePath + File.separator + "hello1.jar"
|
||||
witFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
withFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
val daemonOptions = DaemonOptions(autoshutdownIdleSeconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath)
|
||||
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
|
||||
|
||||
@@ -224,11 +224,12 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||
|
||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
|
||||
TestCase.assertNotNull("failed to connect daemon", daemon)
|
||||
assertNotNull("failed to connect daemon", daemon)
|
||||
daemon?.registerClient(flagFile.absolutePath)
|
||||
val jar = tmpdir.absolutePath + File.separator + "hello1.jar"
|
||||
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)
|
||||
TestCase.assertEquals("compilation failed:\n${strm.toString()}", 0, code)
|
||||
assertEquals("compilation failed:\n${strm.toString()}", 0, code)
|
||||
|
||||
logFile.assertLogContainsSequence("Starting compilation with args: ")
|
||||
|
||||
@@ -245,7 +246,7 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
}
|
||||
|
||||
public fun testDaemonGracefulShutdown() {
|
||||
witFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
withFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
val daemonOptions = DaemonOptions(autoshutdownIdleSeconds = 1, runFilesPath = File(tmpdir, getTestName(true)).absolutePath)
|
||||
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
|
||||
|
||||
@@ -255,13 +256,13 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||
|
||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
|
||||
TestCase.assertNotNull("failed to connect daemon", daemon)
|
||||
assertNotNull("failed to connect daemon", daemon)
|
||||
daemon?.registerClient(flagFile.absolutePath)
|
||||
val sessionId = daemon?.leaseCompileSession(null)
|
||||
|
||||
val scheduleShutdownRes = daemon?.scheduleShutdown(true)
|
||||
|
||||
TestCase.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
|
||||
|
||||
@@ -269,7 +270,7 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
|
||||
val res = daemon?.getUsedMemory()
|
||||
|
||||
TestCase.assertEquals("Invalid state", CompileService.CallResult.Dying(), res)
|
||||
assertEquals("Invalid state", CompileService.CallResult.Dying(), res)
|
||||
|
||||
daemon?.releaseCompileSession(sessionId!!.get())
|
||||
|
||||
@@ -320,8 +321,8 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
}
|
||||
waitThread.join(TIMEOUT_DAEMON_RUNNER_EXIT_MS)
|
||||
|
||||
TestCase.assertFalse("process.waitFor() hangs:\n$resOutput", waitThread.isAlive)
|
||||
TestCase.assertEquals("Compilation failed:\n$resOutput", 0, resCode)
|
||||
assertFalse("process.waitFor() hangs:\n$resOutput", waitThread.isAlive)
|
||||
assertEquals("Compilation failed:\n$resOutput", 0, resCode)
|
||||
}
|
||||
finally {
|
||||
if (clientAliveFile.exists())
|
||||
@@ -345,13 +346,13 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
|
||||
public fun testParallelCompilationOnDaemon() {
|
||||
|
||||
TestCase.assertTrue(PARALLEL_THREADS_TO_COMPILE <= LoopbackNetworkInterface.SERVER_SOCKET_BACKLOG_SIZE)
|
||||
assertTrue(PARALLEL_THREADS_TO_COMPILE <= LoopbackNetworkInterface.SERVER_SOCKET_BACKLOG_SIZE)
|
||||
|
||||
witFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
withFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath)
|
||||
val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
|
||||
TestCase.assertNotNull("failed to connect daemon", daemon)
|
||||
assertNotNull("failed to connect daemon", daemon)
|
||||
|
||||
val (registry, port) = findPortAndCreateRegistry(10, 16384, 65535)
|
||||
val tracer = SynchronizationTracer(CountDownLatch(1), CountDownLatch(PARALLEL_THREADS_TO_COMPILE), port)
|
||||
@@ -381,11 +382,11 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
|
||||
tracer.startSignal.countDown()
|
||||
val succeeded = tracer.doneSignal.await(PARALLEL_WAIT_TIMEOUT_S, TimeUnit.SECONDS)
|
||||
TestCase.assertTrue("parallel compilation failed to complete in $PARALLEL_WAIT_TIMEOUT_S ms, ${tracer.doneSignal.count} unfinished threads", succeeded)
|
||||
assertTrue("parallel compilation failed to complete in $PARALLEL_WAIT_TIMEOUT_S ms, ${tracer.doneSignal.count} unfinished threads", succeeded)
|
||||
|
||||
localEndSignal.await(PARALLEL_WAIT_TIMEOUT_S, TimeUnit.SECONDS)
|
||||
(1..PARALLEL_THREADS_TO_COMPILE).forEach {
|
||||
TestCase.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])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -401,7 +402,7 @@ internal fun File.ifLogNotContainsSequence(vararg patterns: String, body: (LineP
|
||||
internal fun File.assertLogContainsSequence(vararg patterns: String) {
|
||||
ifLogNotContainsSequence(*patterns)
|
||||
{
|
||||
pattern,lineNo -> TestCase.fail("Pattern '${pattern.regex}' is not found in the log file '$absolutePath'")
|
||||
pattern,lineNo -> fail("Pattern '${pattern.regex}' is not found in the log file '$absolutePath'")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +421,7 @@ fun restoreSystemProperty(propertyName: String, backupValue: String?) {
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun witFlagFile(prefix: String, suffix: String? = null, body: (File) -> Unit) {
|
||||
internal inline fun withFlagFile(prefix: String, suffix: String? = null, body: (File) -> Unit) {
|
||||
val file = createTempFile(prefix, suffix)
|
||||
try {
|
||||
body(file)
|
||||
@@ -430,10 +431,10 @@ internal inline fun witFlagFile(prefix: String, suffix: String? = null, body: (F
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// this function makes a path with forward slashed, that works on windows too
|
||||
internal val File.loggerCompatiblePath: String
|
||||
private val File.loggerCompatiblePath: String
|
||||
get() =
|
||||
if (OSKind.current == OSKind.Windows) absolutePath.replace('\\', '/')
|
||||
else absolutePath
|
||||
@@ -137,7 +137,7 @@ public object KotlinCompilerRunner {
|
||||
internal object getDaemonConnection {
|
||||
private @Volatile var connection: DaemonConnection? = null
|
||||
|
||||
@Synchronized operator fun invoke(environment: CompilerEnvironment, messageCollector: MessageCollector): DaemonConnection? {
|
||||
@Synchronized operator fun invoke(environment: CompilerEnvironment, messageCollector: MessageCollector): DaemonConnection {
|
||||
if (connection == null) {
|
||||
val libPath = CompilerRunnerUtil.getLibPath(environment.kotlinPaths, messageCollector)
|
||||
val compilerId = CompilerId.makeCompilerId(File(libPath, "kotlin-compiler.jar"))
|
||||
@@ -159,7 +159,7 @@ public object KotlinCompilerRunner {
|
||||
return flagFile
|
||||
}
|
||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, DaemonReportingTargets(null, daemonReportMessages), true, true)
|
||||
connection = DaemonConnection(daemon, daemon?.leaseCompileSession(newFlagFile().absolutePath)?.get() ?:CompileService.NO_SESSION)
|
||||
connection = DaemonConnection(daemon, daemon?.leaseCompileSession(newFlagFile().absolutePath)?.get() ?: CompileService.NO_SESSION)
|
||||
}
|
||||
|
||||
for (msg in daemonReportMessages) {
|
||||
@@ -170,7 +170,7 @@ public object KotlinCompilerRunner {
|
||||
|
||||
reportTotalAndThreadPerf("Daemon connect", daemonOptions, messageCollector, profiler)
|
||||
}
|
||||
return connection
|
||||
return connection!!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ public class SimpleKotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() {
|
||||
|
||||
// copied from CompilerDaemonTest.kt
|
||||
// TODO: find shared place for this function
|
||||
// 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
|
||||
// this function makes a path with forward slashed, that works on windows too
|
||||
internal val File.loggerCompatiblePath: String
|
||||
|
||||
Reference in New Issue
Block a user