Implementation of the new daemon management without inter-daemon elections and with few other simplifications
This commit is contained in:
@@ -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())
|
||||
|
||||
+179
-30
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user