Implementation of the new daemon management without inter-daemon elections and with few other simplifications

This commit is contained in:
Ilya Chernikov
2015-11-09 19:51:07 +01:00
parent 1643441fdb
commit 375679677c
7 changed files with 413 additions and 219 deletions
@@ -42,8 +42,6 @@ public object KotlinCompilerClient {
val verboseReporting = System.getProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY) != null
// TODO: remove jvmStatic after all use sites will switch to kotlin
@JvmStatic
public fun connectToCompileService(compilerId: CompilerId,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
@@ -51,6 +49,29 @@ public object KotlinCompilerClient {
autostart: Boolean = true,
checkId: Boolean = true
): CompileService? {
fun newFlagFile(): File {
val flagFile = File.createTempFile("kotlin-compiler-client-", "-is-running")
flagFile.deleteOnExit()
return flagFile
}
val flagFile = System.getProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY)
?.let { it.trimQuotes() }
?.ifOrNull { !isBlank() }
?.let { File(it) }
?.ifOrNull { exists() }
?: newFlagFile()
return connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, reportingTargets, autostart, checkId)
}
public fun connectToCompileService(compilerId: CompilerId,
clientAliveFlagFile: File,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
reportingTargets: DaemonReportingTargets,
autostart: Boolean = true,
checkId: Boolean = true
): CompileService? {
var attempts = 0
var fileLock: FileBasedLock? = null
@@ -60,6 +81,7 @@ public object KotlinCompilerClient {
val service = tryFindDaemon(File(daemonOptions.runFilesPath), compilerId, reportingTargets)
if (service != null) {
if (!checkId || service.checkCompilerId(compilerId)) {
service.registerClient(clientAliveFlagFile.absolutePath)
reportingTargets.report(DaemonReportCategory.DEBUG, "connected to the daemon")
return service
}
@@ -112,6 +134,7 @@ public object KotlinCompilerClient {
public fun compile(compilerService: CompileService,
sessionId: Int,
targetPlatform: CompileService.TargetPlatform,
args: Array<out String>,
out: OutputStream,
@@ -119,11 +142,12 @@ public object KotlinCompilerClient {
operationsTracer: RemoteOperationsTracer? = null
): Int {
val outStrm = RemoteOutputStreamServer(out, port = port)
return compilerService.remoteCompile(targetPlatform, args, CompilerCallbackServicesFacadeServer(port = port), outStrm, CompileService.OutputFormat.PLAIN, outStrm, operationsTracer)
return compilerService.remoteCompile(sessionId, targetPlatform, args, CompilerCallbackServicesFacadeServer(port = port), outStrm, CompileService.OutputFormat.PLAIN, outStrm, operationsTracer).get()
}
public fun incrementalCompile(compileService: CompileService,
sessionId: Int,
targetPlatform: CompileService.TargetPlatform,
args: Array<out String>,
callbackServices: CompilationServices,
@@ -134,6 +158,7 @@ public object KotlinCompilerClient {
operationsTracer: RemoteOperationsTracer? = null
): Int = profiler.withMeasure(this) {
compileService.remoteIncrementalCompile(
sessionId,
targetPlatform,
args,
CompilerCallbackServicesFacadeServer(incrementalCompilationComponents = callbackServices.incrementalCompilationComponents,
@@ -142,7 +167,7 @@ public object KotlinCompilerClient {
RemoteOutputStreamServer(compilerOut, port),
CompileService.OutputFormat.XML,
RemoteOutputStreamServer(daemonOut, port),
operationsTracer)
operationsTracer).get()
}
public val COMPILE_DAEMON_CLIENT_OPTIONS_PROPERTY: String = "kotlin.daemon.client.options"
@@ -211,14 +236,14 @@ public object KotlinCompilerClient {
val outStrm = RemoteOutputStreamServer(System.out)
val servicesFacade = CompilerCallbackServicesFacadeServer()
try {
val memBefore = daemon.getUsedMemory() / 1024
val memBefore = daemon.getUsedMemory().get() / 1024
val startTime = System.nanoTime()
val res = daemon.remoteCompile(CompileService.TargetPlatform.JVM, filteredArgs.toArrayList().toTypedArray(), servicesFacade, outStrm, CompileService.OutputFormat.PLAIN, outStrm, null)
val res = daemon.remoteCompile(CompileService.NO_SESSION, CompileService.TargetPlatform.JVM, filteredArgs.toArrayList().toTypedArray(), servicesFacade, outStrm, CompileService.OutputFormat.PLAIN, outStrm, null)
val endTime = System.nanoTime()
println("Compilation result code: $res")
val memAfter = daemon.getUsedMemory() / 1024
val memAfter = daemon.getUsedMemory().get() / 1024
println("Compilation time: " + TimeUnit.NANOSECONDS.toMillis(endTime - startTime) + " ms")
println("Used memory $memAfter (${"%+d".format(memAfter - memBefore)} kb)")
}
@@ -430,3 +455,4 @@ internal fun isProcessAlive(process: Process) =
true
}
internal fun<T> T.ifOrNull(pred: T.() -> Boolean): T? = if (this.pred()) this else null
@@ -16,32 +16,71 @@
package org.jetbrains.kotlin.rmi
import java.io.Serializable
import java.rmi.Remote
import java.rmi.RemoteException
public interface CompileService : Remote {
public enum class OutputFormat : java.io.Serializable {
public enum class OutputFormat : Serializable {
PLAIN,
XML
}
public enum class TargetPlatform : java.io.Serializable {
public enum class TargetPlatform : Serializable {
JVM,
JS
}
companion object {
public val NO_SESSION: Int = 0
}
public sealed class CallResult<R> : Serializable {
class Good<R>(val result: R) : CallResult<R>()
class Ok : CallResult<Nothing>()
class Dying : CallResult<Nothing>()
class Error(val message: String) : CallResult<Nothing>()
fun get(): R = when (this) {
is Good<R> -> this.result
is Dying -> throw IllegalStateException("Service is dying")
is Error -> throw IllegalStateException(this.message)
else -> throw IllegalStateException("Unknown state")
}
}
// TODO: remove!
@Throws(RemoteException::class)
public fun checkCompilerId(compilerId: CompilerId): Boolean
public fun checkCompilerId(expectedCompilerId: CompilerId): Boolean
@Throws(RemoteException::class)
public fun getUsedMemory(): Long
public fun getUsedMemory(): CallResult<Long>
@Throws(RemoteException::class)
public fun shutdown()
public fun getDaemonOptions(): CallResult<DaemonOptions>
@Throws(RemoteException::class)
public fun getDaemonJVMOptions(): CallResult<DaemonJVMOptions>
@Throws(RemoteException::class)
public fun registerClient(aliveFlagPath: String?): CallResult<Nothing>
// TODO: consider adding another client alive checking mechanism, e.g. socket/port
@Throws(RemoteException::class)
public fun leaseCompileSession(aliveFlagPath: String?): CallResult<Int>
@Throws(RemoteException::class)
public fun releaseCompileSession(sessionId: Int): Unit
@Throws(RemoteException::class)
public fun shutdown(): Unit
// TODO: consider adding async version of shutdown and release
@Throws(RemoteException::class)
public fun remoteCompile(
sessionId: Int,
targetPlatform: TargetPlatform,
args: Array<out String>,
servicesFacade: CompilerCallbackServicesFacade,
@@ -49,10 +88,11 @@ public interface CompileService : Remote {
outputFormat: OutputFormat,
serviceOutputStream: RemoteOutputStream,
operationsTracer: RemoteOperationsTracer?
): Int
): CallResult<Int>
@Throws(RemoteException::class)
public fun remoteIncrementalCompile(
sessionId: Int,
targetPlatform: TargetPlatform,
args: Array<out String>,
servicesFacade: CompilerCallbackServicesFacade,
@@ -60,5 +100,5 @@ public interface CompileService : Remote {
compilerOutputFormat: OutputFormat,
serviceOutputStream: RemoteOutputStream,
operationsTracer: RemoteOperationsTracer?
): Int
): CallResult<Int>
}
@@ -44,9 +44,11 @@ public val COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY: String = "kotlin.daemon.star
public val COMPILE_DAEMON_DEFAULT_FILES_PREFIX: String = "kotlin-daemon"
public val COMPILE_DAEMON_TIMEOUT_INFINITE_S: Int = 0
public val COMPILE_DAEMON_DEFAULT_IDLE_TIMEOUT_S: Int = 7200 // 2 hours
public val COMPILE_DAEMON_DEFAULT_UNUSED_TIMEOUT_S: Int = 60
public val COMPILE_DAEMON_DEFAULT_SHUTDOWN_DELAY_MS: Long = 1000L // 1 sec
public val COMPILE_DAEMON_MEMORY_THRESHOLD_INFINITE: Long = 0L
public val COMPILE_DAEMON_FORCE_SHUTDOWN_DEFAULT_TIMEOUT_MS: Long = 10000L // 10 secs
public val COMPILE_DAEMON_FORCE_SHUTDOWN_TIMEOUT_INFINITE: Long = 0L
public val COMPILE_DAEMON_TIMEOUT_INFINITE_MS: Long = 0L
public val COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH: String get() =
FileSystem.getRuntimeStateFilesPath("kotlin", "daemon")
@@ -204,8 +206,9 @@ public data class DaemonOptions(
public var runFilesPath: String = COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH,
public var autoshutdownMemoryThreshold: Long = COMPILE_DAEMON_MEMORY_THRESHOLD_INFINITE,
public var autoshutdownIdleSeconds: Int = COMPILE_DAEMON_DEFAULT_IDLE_TIMEOUT_S,
public var autoshutdownUnusedSeconds: Int = COMPILE_DAEMON_DEFAULT_UNUSED_TIMEOUT_S,
public var shutdownDelayMilliseconds: Long = COMPILE_DAEMON_DEFAULT_SHUTDOWN_DELAY_MS,
public var forceShutdownTimeoutMilliseconds: Long = COMPILE_DAEMON_FORCE_SHUTDOWN_DEFAULT_TIMEOUT_MS,
public var clientAliveFlagPath: String? = null,
public var verbose: Boolean = false,
public var reportPerf: Boolean = false
) : OptionsGroup {
@@ -213,9 +216,11 @@ public data class DaemonOptions(
override val mappers: List<PropMapper<*, *, *>>
get() = listOf(PropMapper(this, DaemonOptions::runFilesPath, fromString = { it.trimQuotes() }),
PropMapper(this, DaemonOptions::autoshutdownMemoryThreshold, fromString = { it.toLong() }, skipIf = { it == 0L }, mergeDelimiter = "="),
// TODO: implement "use default" value without specifying default, so if client and server uses different defaults, it should not lead to many params in the cmd line; use 0 for it and used different val for infinite
PropMapper(this, DaemonOptions::autoshutdownIdleSeconds, fromString = { it.toInt() }, skipIf = { it == 0 }, mergeDelimiter = "="),
PropMapper(this, DaemonOptions::autoshutdownUnusedSeconds, fromString = { it.toInt() }, skipIf = { it == COMPILE_DAEMON_DEFAULT_UNUSED_TIMEOUT_S }, mergeDelimiter = "="),
PropMapper(this, DaemonOptions::shutdownDelayMilliseconds, fromString = { it.toLong() }, skipIf = { it == COMPILE_DAEMON_DEFAULT_SHUTDOWN_DELAY_MS }, mergeDelimiter = "="),
PropMapper(this, DaemonOptions::forceShutdownTimeoutMilliseconds, fromString = { it.toLong() }, skipIf = { it == COMPILE_DAEMON_FORCE_SHUTDOWN_DEFAULT_TIMEOUT_MS }, mergeDelimiter = "="),
NullablePropMapper(this, DaemonOptions::clientAliveFlagPath, fromString = { it }, toString = { "${it?.trimQuotes()}" }, mergeDelimiter = "="),
BoolPropMapper(this, DaemonOptions::verbose),
BoolPropMapper(this, DaemonOptions::reportPerf))
}
@@ -295,12 +300,6 @@ public fun configureDaemonOptions(opts: DaemonOptions): DaemonOptions {
"Unrecognized daemon options passed via property $COMPILE_DAEMON_OPTIONS_PROPERTY: " + unrecognized.joinToString(" ") +
"\nSupported options: " + opts.mappers.joinToString(", ", transform = { it.names.first() }))
}
System.getProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY)?.let {
val trimmed = it.trimQuotes()
if (!trimmed.isBlank()) {
opts.clientAliveFlagPath = trimmed
}
}
System.getProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY)?.let { opts.verbose = true }
System.getProperty(COMPILE_DAEMON_REPORT_PERF_PROPERTY)?.let { opts.reportPerf = true }
return opts
@@ -33,7 +33,6 @@ import java.util.logging.Level
import java.util.logging.LogManager
import java.util.logging.Logger
import kotlin.concurrent.schedule
import kotlin.concurrent.timer
val DAEMON_PERIODIC_CHECK_INTERVAL_MS = 1000L
@@ -101,6 +100,8 @@ public object CompileDaemon {
val daemonOptions = DaemonOptions()
try {
val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = true, inheritAdditionalProperties = true)
val filteredArgs = args.asIterable().filterExtractProps(compilerId, daemonOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX)
if (filteredArgs.any()) {
@@ -120,18 +121,6 @@ public object CompileDaemon {
// setDaemonPermissions(daemonOptions.port)
val (registry, port) = findPortAndCreateRegistry(COMPILE_DAEMON_FIND_PORT_ATTEMPTS, COMPILE_DAEMON_PORTS_RANGE_START, COMPILE_DAEMON_PORTS_RANGE_END)
val runFileDir = File(if (daemonOptions.runFilesPath.isBlank()) COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH else daemonOptions.runFilesPath)
runFileDir.mkdirs()
val runFile = File(runFileDir,
makeRunFilenameString(timestamp = "%tFT%<tH-%<tM-%<tS.%<tLZ".format(Calendar.getInstance(TimeZone.getTimeZone("Z"))),
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest().toHexString(),
port = port.toString()))
try {
if (!runFile.createNewFile()) throw Exception("createNewFile returned false")
} catch (e: Exception) {
throw IllegalStateException("Unable to create run file '${runFile.absolutePath}'", e)
}
runFile.deleteOnExit()
val compilerSelector = object : CompilerSelector {
private val jvm by lazy { K2JVMCompiler() }
@@ -141,28 +130,32 @@ public object CompileDaemon {
CompileService.TargetPlatform.JS -> js
}
}
val compilerService = CompileServiceImpl(registry, compilerSelector, compilerId, daemonOptions, port,
// timer with a daemon thread, meaning it should not prevent JVM to exit normally
val timer = Timer(true)
val compilerService = CompileServiceImpl(registry = registry,
compiler = compilerSelector,
compilerId = compilerId,
daemonOptions = daemonOptions,
daemonJVMOptions = daemonJVMOptions,
port = port,
timer = timer,
onShutdown = {
if (daemonOptions.forceShutdownTimeoutMilliseconds != COMPILE_DAEMON_FORCE_SHUTDOWN_TIMEOUT_INFINITE) {
if (daemonOptions.forceShutdownTimeoutMilliseconds != COMPILE_DAEMON_TIMEOUT_INFINITE_MS) {
// running a watcher thread that ensures that if the daemon is not exited normally (may be due to RMI leftovers), it's forced to exit
// the watcher is a daemon thread, meaning it should not prevent JVM to exit normally
Timer(true).schedule(daemonOptions.forceShutdownTimeoutMilliseconds) {
timer.schedule(daemonOptions.forceShutdownTimeoutMilliseconds) {
cancel()
log.info("force JVM shutdown")
System.exit(0)
}
}
else {
timer.cancel()
}
})
if (daemonOptions.runFilesPath.isNotEmpty())
println(daemonOptions.runFilesPath)
daemonOptions.clientAliveFlagPath?.let {
if (!File(it).exists()) {
log.info("Client alive flag $it do not exist, disable watching it")
daemonOptions.clientAliveFlagPath = null
}
}
// this supposed to stop redirected streams reader(s) on the client side and prevent some situations with hanging threads, but doesn't work reliably
// TODO: implement more reliable scheme
System.out.close()
@@ -170,33 +163,6 @@ public object CompileDaemon {
System.setErr(PrintStream(LogStream("stderr")))
System.setOut(PrintStream(LogStream("stdout")))
fun shutdownCondition(check: () -> Boolean, message: String): Boolean {
val res = check()
if (res) {
log.info(message)
}
return res
}
// stopping daemon if any shutdown condition is met
timer(initialDelay = DAEMON_PERIODIC_CHECK_INTERVAL_MS, period = DAEMON_PERIODIC_CHECK_INTERVAL_MS, daemon = true) {
try {
val idleSeconds = nowSeconds() - compilerService.lastUsedSeconds
if (shutdownCondition({ !runFile.exists() }, "Run file removed, shutting down") ||
shutdownCondition({ daemonOptions.autoshutdownIdleSeconds != COMPILE_DAEMON_TIMEOUT_INFINITE_S && idleSeconds > daemonOptions.autoshutdownIdleSeconds },
"Idle timeout exceeded ${daemonOptions.autoshutdownIdleSeconds}s, shutting down") ||
shutdownCondition({ daemonOptions.clientAliveFlagPath?.let { !File(it).exists() } ?: false },
"Client alive flag ${daemonOptions.clientAliveFlagPath} removed, shutting down")) {
cancel()
compilerService.shutdown()
}
}
catch (e: Exception) {
System.err.println("Exception in timer thread: " + e.getMessage())
e.printStackTrace(System.err)
}
}
}
catch (e: Exception) {
System.err.println("Exception: " + e.getMessage())
@@ -23,14 +23,19 @@ import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompil
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
import org.jetbrains.kotlin.rmi.*
import java.io.BufferedOutputStream
import java.io.File
import java.io.PrintStream
import java.rmi.NoSuchObjectException
import java.rmi.registry.Registry
import java.rmi.server.UnicastRemoteObject
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantReadWriteLock
import java.util.logging.Level
import java.util.logging.Logger
import kotlin.concurrent.read
import kotlin.concurrent.schedule
import kotlin.concurrent.write
fun nowSeconds() = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime())
@@ -58,24 +63,104 @@ private class EventMangerImpl : EventManger {
class CompileServiceImpl(
val registry: Registry,
val compiler: CompilerSelector,
val selfCompilerId: CompilerId,
val compilerId: CompilerId,
val daemonOptions: DaemonOptions,
val daemonJVMOptions: DaemonJVMOptions,
port: Int,
val timer: Timer,
val onShutdown: () -> Unit
) : CompileService {
private val classpathWatcher = LazyClasspathWatcher(selfCompilerId.compilerClasspath)
// wrapped in a class to encapsulate alive check logic
private class ClientOrSessionProxy(val aliveFlagPath: String?) {
val registered = nowSeconds()
val secondsSinceRegistered: Long get() = nowSeconds() - registered
val isAlive: Boolean get() = aliveFlagPath?.let { File(it).exists() } ?: true // assuming that if no file was given, the client is alive
}
private val clientProxies: MutableSet<ClientOrSessionProxy> = hashSetOf()
private val sessions: MutableMap<Int, ClientOrSessionProxy> = hashMapOf()
private val sessionsIdCounter: AtomicInteger = AtomicInteger(0)
private val internalRng = Random()
private val classpathWatcher = LazyClasspathWatcher(compilerId.compilerClasspath)
private val compilationsCounter: AtomicInteger = AtomicInteger(0)
@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 rwlock = ReentrantReadWriteLock()
private var alive = false
private var runFile: File
init {
val runFileDir = File(if (daemonOptions.runFilesPath.isBlank()) COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH else daemonOptions.runFilesPath)
runFileDir.mkdirs()
runFile = File(runFileDir,
makeRunFilenameString(timestamp = "%tFT%<tH-%<tM-%<tS.%<tLZ".format(Calendar.getInstance(TimeZone.getTimeZone("Z"))),
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest().toHexString(),
port = port.toString()))
try {
if (!runFile.createNewFile()) throw Exception("createNewFile returned false")
} catch (e: Exception) {
throw IllegalStateException("Unable to create run file '${runFile.absolutePath}'", e)
}
runFile.deleteOnExit()
}
// RMI-exposed API
override fun checkCompilerId(compilerId: CompilerId): Boolean =
ifAlive {
(selfCompilerId.compilerVersion.isEmpty() || selfCompilerId.compilerVersion == compilerId.compilerVersion) &&
(selfCompilerId.compilerClasspath.all { compilerId.compilerClasspath.contains(it) }) &&
!classpathWatcher.isChanged
}
override fun getDaemonOptions(): CompileService.CallResult<DaemonOptions> = ifAlive { daemonOptions }
override fun getUsedMemory(): Long = ifAlive { usedMemory(withGC = true) }
override fun getDaemonJVMOptions(): CompileService.CallResult<DaemonJVMOptions> = ifAlive { daemonJVMOptions }
override fun registerClient(aliveFlagPath: String?): CompileService.CallResult<Nothing> = ifAlive_Nothing {
synchronized(clientProxies) {
clientProxies.add(ClientOrSessionProxy(aliveFlagPath))
}
}
// TODO: consider tying a session to a client and use this info to cleanup
override fun leaseCompileSession(aliveFlagPath: String?): CompileService.CallResult<Int> = ifAlive {
// fighting hypothetical integer wrapping
var newId = sessionsIdCounter.incrementAndGet()
val session = ClientOrSessionProxy(aliveFlagPath)
for (attempt in 1..100) {
if (newId != CompileService.NO_SESSION) {
synchronized(sessions) {
if (!sessions.containsKey(newId)) {
sessions.put(newId, session)
return@ifAlive newId
}
}
}
// assuming wrap, jumping to random number to reduce probability of further ckashes
newId = sessionsIdCounter.addAndGet(internalRng.nextInt())
}
throw Exception("Invalid state or algorithm error")
}
override fun releaseCompileSession(sessionId: Int) {
synchronized(sessions) {
sessions.remove(sessionId)
// TODO: some cleanup goes here
if (sessions.isEmpty()) {
// TODO: and some goes here
}
}
periodicAndAfterSessionCheck()
}
override fun checkCompilerId(expectedCompilerId: CompilerId): Boolean =
(compilerId.compilerVersion.isEmpty() || compilerId.compilerVersion == expectedCompilerId.compilerVersion) &&
(compilerId.compilerClasspath.all { expectedCompilerId.compilerClasspath.contains(it) }) &&
!classpathWatcher.isChanged
override fun getUsedMemory(): CompileService.CallResult<Long> = ifAlive { usedMemory(withGC = true) }
override fun shutdown() {
ifAliveExclusive {
@@ -87,30 +172,32 @@ class CompileServiceImpl(
}
}
override fun remoteCompile(targetPlatform: CompileService.TargetPlatform,
override fun remoteCompile(sessionId: Int,
targetPlatform: CompileService.TargetPlatform,
args: Array<out String>,
servicesFacade: CompilerCallbackServicesFacade,
compilerOutputStream: RemoteOutputStream,
outputFormat: CompileService.OutputFormat,
serviceOutputStream: RemoteOutputStream,
operationsTracer: RemoteOperationsTracer?
): Int =
doCompile(args, compilerOutputStream, serviceOutputStream, operationsTracer) { printStream, eventManager, profiler ->
): CompileService.CallResult<Int> =
doCompile(sessionId, args, compilerOutputStream, serviceOutputStream, operationsTracer) { printStream, eventManager, profiler ->
when (outputFormat) {
CompileService.OutputFormat.PLAIN -> compiler[targetPlatform].exec(printStream, *args)
CompileService.OutputFormat.XML -> compiler[targetPlatform].execAndOutputXml(printStream, createCompileServices(servicesFacade, eventManager, profiler), *args)
}
}
override fun remoteIncrementalCompile(targetPlatform: CompileService.TargetPlatform,
override fun remoteIncrementalCompile(sessionId: Int,
targetPlatform: CompileService.TargetPlatform,
args: Array<out String>,
servicesFacade: CompilerCallbackServicesFacade,
compilerOutputStream: RemoteOutputStream,
compilerOutputFormat: CompileService.OutputFormat,
serviceOutputStream: RemoteOutputStream,
operationsTracer: RemoteOperationsTracer?
): Int =
doCompile(args, compilerOutputStream, serviceOutputStream, operationsTracer) { printStream, eventManager, profiler ->
): CompileService.CallResult<Int> =
doCompile(sessionId, args, compilerOutputStream, serviceOutputStream, operationsTracer) { printStream, eventManager, profiler ->
when (compilerOutputFormat) {
CompileService.OutputFormat.PLAIN -> throw NotImplementedError("Only XML output is supported in remote incremental compilation")
CompileService.OutputFormat.XML -> compiler[targetPlatform].execAndOutputXml(printStream, createCompileServices(servicesFacade, eventManager, profiler), *args)
@@ -119,14 +206,6 @@ class CompileServiceImpl(
// internal implementation stuff
@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 rwlock = ReentrantReadWriteLock()
private var alive = false
// TODO: consider matching compilerId coming from outside with actual one
// private val selfCompilerId by lazy {
// CompilerId(
@@ -153,16 +232,79 @@ class CompileServiceImpl(
val stub = UnicastRemoteObject.exportObject(this, port, LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.serverLoopbackSocketFactory) as CompileService
registry.rebind (COMPILER_SERVICE_RMI_NAME, stub);
alive = true
timer.schedule(delay = DAEMON_PERIODIC_CHECK_INTERVAL_MS, period = DAEMON_PERIODIC_CHECK_INTERVAL_MS) {
try {
periodicAndAfterSessionCheck()
}
catch (e: Exception) {
System.err.println("Exception in timer thread: " + e.message)
e.printStackTrace(System.err)
log.log(Level.SEVERE, "Exception in timer thread", e)
}
}
}
private fun doCompile(args: Array<out String>,
private fun periodicAndAfterSessionCheck() {
// 1. check if unused for a timeout - shutdown
if (shutdownCondition({ daemonOptions.autoshutdownUnusedSeconds != COMPILE_DAEMON_TIMEOUT_INFINITE_S && compilationsCounter.get() == 0 && nowSeconds() - lastUsedSeconds > daemonOptions.autoshutdownUnusedSeconds },
"Unused timeout exceeded ${daemonOptions.autoshutdownUnusedSeconds}s, shutting down")) {
shutdown()
}
else {
synchronized(sessions) {
// 2. check if any session hanged - clean
// making copy of the list before calling release
sessions.filterValues { !it.isAlive }.keys.toArrayList()
}.forEach { releaseCompileSession(it) }
// 3. clean dead clients, then check if any left - conditional shutdown (with small delay)
synchronized(clientProxies) { clientProxies.removeAll( clientProxies.filter{ !it.isAlive }) }
if (clientProxies.none()) {
shutdownWithDelay()
}
// 4. check idle timeout - shutdown
if (shutdownCondition({ daemonOptions.autoshutdownIdleSeconds != COMPILE_DAEMON_TIMEOUT_INFINITE_S && nowSeconds() - lastUsedSeconds > daemonOptions.autoshutdownIdleSeconds },
"Idle timeout exceeded ${daemonOptions.autoshutdownIdleSeconds}s, shutting down") ||
// 5. discovery file removed - shutdown
shutdownCondition({ !runFile.exists() }, "Run file removed, shutting down") ||
// 6. compiler changed (seldom check) - shutdown
// TODO: could be too expensive anyway, consider removing this check
shutdownCondition({ classpathWatcher.isChanged }, "Compiler changed"))
{
shutdown()
}
}
}
private fun shutdownWithDelay() {
val currentCompilationsCount = compilationsCounter.get()
timer.schedule(daemonOptions.shutdownDelayMilliseconds) {
if (currentCompilationsCount == compilationsCounter.get()) {
shutdown()
}
}
}
private fun shutdownCondition(check: () -> Boolean, message: String): Boolean {
val res = check()
if (res) {
log.info(message)
}
return res
}
private fun doCompile(sessionId: Int,
args: Array<out String>,
compilerMessagesStreamProxy: RemoteOutputStream,
serviceOutputStreamProxy: RemoteOutputStream,
operationsTracer: RemoteOperationsTracer?,
body: (PrintStream, EventManger, Profiler) -> ExitCode): Int =
body: (PrintStream, EventManger, Profiler) -> ExitCode): CompileService.CallResult<Int> =
ifAlive {
operationsTracer?.before("compile")
compilationsCounter.incrementAndGet()
val rpcProfiler = if (daemonOptions.reportPerf) WallAndThreadTotalProfiler() else DummyProfiler()
val eventManger = EventMangerImpl()
val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerMessagesStreamProxy, rpcProfiler), 4096))
@@ -236,13 +378,20 @@ class CompileServiceImpl(
}
}
fun<R> ifAlive(body: () -> R): R = rwlock.read {
if (!alive) throw IllegalStateException("Kotlin Compiler Service is not in alive state")
body()
fun<R> ifAlive(body: () -> R): CompileService.CallResult<R> = rwlock.read {
if (!alive) CompileService.CallResult.Dying()
CompileService.CallResult.Good( body() )
}
fun<R> ifAliveExclusive(body: () -> R): R = rwlock.write {
if (!alive) throw IllegalStateException("Kotlin Compiler Service is not in alive state")
// TODO: find how to implement it without using unique name for this variant; making name deliberately ugly meanwhile
fun ifAlive_Nothing(body: () -> Unit): CompileService.CallResult<Nothing> = rwlock.read {
if (!alive) CompileService.CallResult.Dying()
body()
CompileService.CallResult.Ok()
}
fun<R> ifAliveExclusive(body: () -> R): CompileService.CallResult<R> = rwlock.write {
if (!alive) CompileService.CallResult.Dying()
CompileService.CallResult.Good( body() )
}
}
@@ -43,18 +43,19 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler.jar"))
val compilerId by lazy(LazyThreadSafetyMode.NONE) { CompilerId.makeCompilerId(compilerClassPath) }
private fun compileOnDaemon(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, vararg args: String): CompilerResults {
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true, checkId = true)
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, checkId = true)
TestCase.assertNotNull("failed to connect daemon", daemon)
daemon?.registerClient(clientAliveFile.absolutePath)
val strm = ByteArrayOutputStream()
val code = KotlinCompilerClient.compile(daemon!!, CompileService.TargetPlatform.JVM, args, strm)
val code = KotlinCompilerClient.compile(daemon!!, CompileService.NO_SESSION, CompileService.TargetPlatform.JVM, args, strm)
return CompilerResults(code, strm.toString())
}
private fun runDaemonCompilerTwice(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, vararg args: String): Unit {
val res1 = compileOnDaemon(compilerId, daemonJVMOptions, daemonOptions, *args)
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)
val res2 = compileOnDaemon(compilerId, daemonJVMOptions, daemonOptions, *args)
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))
}
@@ -67,50 +68,53 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
public fun testHelloApp() {
val flagFile = createTempFile(getTestName(true), ".alive")
flagFile.deleteOnExit()
val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath,
clientAliveFlagPath = flagFile.absolutePath,
verbose = true,
reportPerf = true)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
val logFile = createTempFile("kotlin-daemon-test.", ".log")
val daemonJVMOptions = configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.absolutePath}\"",
inheritMemoryLimits = false, inheritAdditionalProperties = false)
var daemonShotDown = false
try {
val jar = tmpdir.absolutePath + File.separator + "hello.jar"
runDaemonCompilerTwice(compilerId, daemonJVMOptions, daemonOptions,
"-include-runtime", File(getTestBaseDir(), "hello.kt").absolutePath, "-d", jar)
val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath,
verbose = true,
reportPerf = true)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
daemonShotDown = true
var compileTime1 = 0L
var compileTime2 = 0L
logFile.reader().useLines {
it.ifNotContainsSequence( LinePattern("Kotlin compiler daemon version"),
LinePattern("Starting compilation with args: "),
LinePattern("Compile on daemon: (\\d+) ms", { it.groups.get(1)?.value?.toLong()?.let { compileTime1 = it }; true } ),
LinePattern("Starting compilation with args: "),
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 +
"\nunmatched part of the log file (" + logFile.absolutePath +
") from line " + lineNo + ":\n\n" + logFile.reader().useLines { it.drop(lineNo).joinToString("\n") })
val logFile = createTempFile("kotlin-daemon-test.", ".log")
val daemonJVMOptions = configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile.absolutePath}\"",
inheritMemoryLimits = false, inheritAdditionalProperties = false)
var daemonShotDown = false
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)
daemonShotDown = true
var compileTime1 = 0L
var compileTime2 = 0L
logFile.reader().useLines {
it.ifNotContainsSequence(LinePattern("Kotlin compiler daemon version"),
LinePattern("Starting compilation with args: "),
LinePattern("Compile on daemon: (\\d+) ms", { it.groups.get(1)?.value?.toLong()?.let { compileTime1 = it }; true }),
LinePattern("Starting compilation with args: "),
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 +
"\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)",
compileTime1 > compileTime2 * 2)
logFile.delete()
run("hello.run", "-cp", jar, "Hello.HelloKt")
}
finally {
if (!daemonShotDown)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
}
TestCase.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)
flagFile.delete()
}
}
@@ -132,10 +136,9 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
public fun testDaemonOptionsParsing() {
val backupOptions = System.getProperty(COMPILE_DAEMON_OPTIONS_PROPERTY)
try {
System.setProperty(COMPILE_DAEMON_OPTIONS_PROPERTY, "runFilesPath=abcd,clientAliveFlagPath=efgh,autoshutdownIdleSeconds=1111")
System.setProperty(COMPILE_DAEMON_OPTIONS_PROPERTY, "runFilesPath=abcd,autoshutdownIdleSeconds=1111")
val opts = configureDaemonOptions()
TestCase.assertEquals("abcd", opts.runFilesPath)
TestCase.assertEquals("efgh", opts.clientAliveFlagPath)
TestCase.assertEquals(1111, opts.autoshutdownIdleSeconds)
}
finally {
@@ -146,47 +149,48 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
public fun testDaemonInstances() {
val jar = tmpdir.absolutePath + File.separator + "hello1.jar"
val flagFile = createTempFile(getTestName(true), ".alive")
flagFile.deleteOnExit()
val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath,
clientAliveFlagPath = flagFile.absolutePath)
val compilerId2 = CompilerId.makeCompilerId(compilerClassPath +
File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler-sources.jar"))
try {
val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath)
val compilerId2 = CompilerId.makeCompilerId(compilerClassPath +
File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler-sources.jar"))
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
KotlinCompilerClient.shutdownCompileService(compilerId2, daemonOptions)
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
KotlinCompilerClient.shutdownCompileService(compilerId2, daemonOptions)
val logFile1 = createTempFile("kotlin-daemon1-test", ".log")
val logFile2 = createTempFile("kotlin-daemon2-test", ".log")
val daemonJVMOptions1 =
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile1.absolutePath}\"",
inheritMemoryLimits = false, inheritAdditionalProperties = false)
val logFile1 = createTempFile("kotlin-daemon1-test", ".log")
val logFile2 = createTempFile("kotlin-daemon2-test", ".log")
val daemonJVMOptions1 =
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile1.absolutePath}\"",
inheritMemoryLimits = false, inheritAdditionalProperties = false)
val daemonJVMOptions2 =
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile2.absolutePath}\"",
inheritMemoryLimits = false, inheritAdditionalProperties = false)
val daemonJVMOptions2 =
configureDaemonJVMOptions("D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${logFile2.absolutePath}\"",
inheritMemoryLimits = false, inheritAdditionalProperties = false)
TestCase.assertTrue(logFile1.length() == 0L && logFile2.length() == 0L)
TestCase.assertTrue(logFile1.length() == 0L && logFile2.length() == 0L)
val res1 = compileOnDaemon(compilerId, daemonJVMOptions1, daemonOptions,
"-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar)
TestCase.assertEquals("first compilation failed:\n${res1.out}", 0, res1.resultCode)
val res1 = compileOnDaemon(flagFile, compilerId, daemonJVMOptions1, daemonOptions, "-include-runtime")
TestCase.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())
logFile1.assertLogContainsSequence("Starting compilation with args: ")
TestCase.assertEquals("expecting '${logFile2.absolutePath}' to be empty", 0L, logFile2.length())
val res2 = compileOnDaemon(compilerId2, daemonJVMOptions2, daemonOptions,
"-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar)
TestCase.assertEquals("second compilation failed:\n${res2.out}", 0, res1.resultCode)
val res2 = compileOnDaemon(flagFile, compilerId2, daemonJVMOptions2, daemonOptions, "-include-runtime")
TestCase.assertEquals("second compilation failed:\n${res2.out}", 0, res1.resultCode)
logFile2.assertLogContainsSequence("Starting compilation with args: ")
logFile2.assertLogContainsSequence("Starting compilation with args: ")
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
logFile1.assertLogContainsSequence("Shutdown complete")
logFile1.delete()
KotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
logFile1.assertLogContainsSequence("Shutdown complete")
logFile1.delete()
KotlinCompilerClient.shutdownCompileService(compilerId2, daemonOptions)
logFile2.assertLogContainsSequence("Shutdown complete")
logFile2.delete()
KotlinCompilerClient.shutdownCompileService(compilerId2, daemonOptions)
logFile2.assertLogContainsSequence("Shutdown complete")
logFile2.delete()
}
finally {
flagFile.delete()
}
}
@@ -203,7 +207,7 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
public fun testDaemonExecutionViaIntermediateProcess() {
val clientAliveFile = createTempFile("kotlin-daemon-transitive-run-test", ".run")
val runFilesPath = File(tmpdir, getTestName(true)).absolutePath
val daemonOptions = DaemonOptions(runFilesPath = runFilesPath, clientAliveFlagPath = clientAliveFile.absolutePath)
val daemonOptions = DaemonOptions(runFilesPath = runFilesPath)
val jar = tmpdir.absolutePath + File.separator + "hello.jar"
val args = listOf(
File(File(System.getProperty("java.home"), "bin"), "java").absolutePath,
@@ -257,43 +261,49 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
TestCase.assertTrue(PARALLEL_THREADS_TO_COMPILE <= LoopbackNetworkInterface.SERVER_SOCKET_BACKLOG_SIZE)
val flagFile = createTempFile(getTestName(true), ".alive")
flagFile.deleteOnExit()
val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath, clientAliveFlagPath = flagFile.absolutePath)
val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false)
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true, checkId = true)
TestCase.assertNotNull("failed to connect daemon", daemon)
try {
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, checkId = true)
TestCase.assertNotNull("failed to connect daemon", daemon)
val (registry, port) = findPortAndCreateRegistry(10, 16384, 65535)
val tracer = SynchronizationTracer(CountDownLatch(1), CountDownLatch(PARALLEL_THREADS_TO_COMPILE), port)
val (registry, port) = findPortAndCreateRegistry(10, 16384, 65535)
val tracer = SynchronizationTracer(CountDownLatch(1), CountDownLatch(PARALLEL_THREADS_TO_COMPILE), port)
val resultCodes = arrayOfNulls<Int>(PARALLEL_THREADS_TO_COMPILE)
val localEndSignal = CountDownLatch(PARALLEL_THREADS_TO_COMPILE)
val outStreams = Array(PARALLEL_THREADS_TO_COMPILE, { ByteArrayOutputStream() })
val resultCodes = arrayOfNulls<Int>(PARALLEL_THREADS_TO_COMPILE)
val localEndSignal = CountDownLatch(PARALLEL_THREADS_TO_COMPILE)
val outStreams = Array(PARALLEL_THREADS_TO_COMPILE, { ByteArrayOutputStream() })
fun runCompile(threadNo: Int) =
thread {
val jar = tmpdir.absolutePath + File.separator + "hello.$threadNo.jar"
val res = KotlinCompilerClient.compile(daemon!!,
CompileService.TargetPlatform.JVM,
arrayOf("-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar),
outStreams[threadNo],
port = port,
operationsTracer = tracer as RemoteOperationsTracer)
synchronized(resultCodes) {
resultCodes[threadNo] = res
}
localEndSignal.countDown()
fun runCompile(threadNo: Int) =
thread {
val jar = tmpdir.absolutePath + File.separator + "hello.$threadNo.jar"
val res = KotlinCompilerClient.compile(
daemon!!,
CompileService.NO_SESSION,
CompileService.TargetPlatform.JVM,
arrayOf("-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar),
outStreams[threadNo],
port = port,
operationsTracer = tracer as RemoteOperationsTracer)
synchronized(resultCodes) {
resultCodes[threadNo] = res
}
localEndSignal.countDown()
}
(1..PARALLEL_THREADS_TO_COMPILE).forEach { runCompile(it - 1) }
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)
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])
}
(1..PARALLEL_THREADS_TO_COMPILE).forEach { runCompile(it-1) }
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)
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])
}
finally {
flagFile.delete()
}
}
}
@@ -132,7 +132,7 @@ public object KotlinCompilerRunner {
}
internal class DaemonConnection(public val daemon: CompileService?)
internal class DaemonConnection(public val daemon: CompileService?, public val sessionId: Int = CompileService.NO_SESSION)
internal object getDaemonConnection {
private @Volatile var connection: DaemonConnection? = null
@@ -153,9 +153,13 @@ public object KotlinCompilerRunner {
val profiler = if (daemonOptions.reportPerf) WallAndThreadAndMemoryTotalProfiler(withGC = false) else DummyProfiler()
profiler.withMeasure(null) {
connection = DaemonConnection(
KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, DaemonReportingTargets(null, daemonReportMessages), true, true)
)
fun newFlagFile(): File {
val flagFile = File.createTempFile("kotlin-compiler-jps-session-", "-is-running")
flagFile.deleteOnExit()
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)
}
for (msg in daemonReportMessages) {
@@ -196,7 +200,7 @@ public object KotlinCompilerRunner {
K2JS_COMPILER -> CompileService.TargetPlatform.JS
else -> throw IllegalArgumentException("Unknown compiler type $compilerClassName")
}
val res = KotlinCompilerClient.incrementalCompile(connection!!.daemon!!, targetPlatform, argsArray, services, compilerOut, daemonOut)
val res = KotlinCompilerClient.incrementalCompile(connection!!.daemon!!, connection.sessionId, targetPlatform, argsArray, services, compilerOut, daemonOut)
processCompilerOutput(messageCollector, collector, compilerOut, res.toString())
BufferedReader(StringReader(daemonOut.toString())).forEachLine {