Implementation of multiple daemon instances management
This commit is contained in:
+114
-34
@@ -19,11 +19,8 @@ package org.jetbrains.kotlin.rmi.kotlinr
|
|||||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
|
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
|
||||||
import org.jetbrains.kotlin.modules.TargetId
|
import org.jetbrains.kotlin.modules.TargetId
|
||||||
import org.jetbrains.kotlin.rmi.*
|
import org.jetbrains.kotlin.rmi.*
|
||||||
import java.io.File
|
import java.io.*
|
||||||
import java.io.OutputStream
|
|
||||||
import java.io.PrintStream
|
|
||||||
import java.rmi.ConnectException
|
import java.rmi.ConnectException
|
||||||
import java.rmi.Remote
|
|
||||||
import java.rmi.registry.LocateRegistry
|
import java.rmi.registry.LocateRegistry
|
||||||
import java.util.concurrent.Semaphore
|
import java.util.concurrent.Semaphore
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -43,20 +40,39 @@ public class KotlinCompilerClient {
|
|||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val DAEMON_DEFAULT_STARTUP_TIMEOUT_MS = 10000L
|
val DAEMON_DEFAULT_STARTUP_TIMEOUT_MS = 10000L
|
||||||
|
val DAEMON_CONNECT_CYCLE_ATTEMPTS = 3
|
||||||
|
|
||||||
private fun connectToService(compilerId: CompilerId, daemonOptions: DaemonOptions, errStream: PrintStream): CompileService? {
|
private fun tryFindDaemon(registryDir: File, compilerId: CompilerId, errStream: PrintStream): CompileService? {
|
||||||
|
val classPathDigest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest()
|
||||||
val compilerObj = connectToDaemon(compilerId, daemonOptions, errStream) ?: return null // no registry - no daemon running
|
var port = 0
|
||||||
return compilerObj as? CompileService ?:
|
val daemons = registryDir.walk()
|
||||||
throw ClassCastException("Unable to cast compiler service, actual class received: ${compilerObj.javaClass}")
|
.map { Pair(it, makeRunFilenameRegex(digest = classPathDigest, port = "(\\d+)").match(it.name)?.groups?.get(1)?.value?.toInt() ?: 0) }
|
||||||
|
.filter { it.second != 0 }
|
||||||
|
.map {
|
||||||
|
val daemon = tryConnectToDaemon(it.second, errStream)
|
||||||
|
// cleaning orphaned file; note: daemon should shut itself down if it detects that the run file is deleted
|
||||||
|
if (daemon == null && !it.first.delete()) {
|
||||||
|
errStream.println("WARNING: Unable to delete seemingly orphaned file '', cleanup recommended")
|
||||||
|
}
|
||||||
|
daemon
|
||||||
|
}
|
||||||
|
.filterNotNull()
|
||||||
|
.toList()
|
||||||
|
return when (daemons.size()) {
|
||||||
|
0 -> null
|
||||||
|
1 -> daemons.first()
|
||||||
|
else -> throw IllegalStateException("Multiple daemons serving the same compiler, reset with the cleanup required")
|
||||||
|
// TODO: consider implementing automatic recovery instead, e.g. getting the youngest or least used daemon and shut down others
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun connectToDaemon(compilerId: CompilerId, daemonOptions: DaemonOptions, errStream: PrintStream): Remote? {
|
private fun tryConnectToDaemon(port: Int, errStream: PrintStream): CompileService? {
|
||||||
try {
|
try {
|
||||||
val daemon = LocateRegistry.getRegistry("localhost", daemonOptions.port)
|
val daemon = LocateRegistry.getRegistry("localhost", port)
|
||||||
?.lookup(COMPILER_SERVICE_RMI_NAME)
|
?.lookup(COMPILER_SERVICE_RMI_NAME)
|
||||||
if (daemon != null)
|
if (daemon != null)
|
||||||
return daemon
|
return daemon as? CompileService ?:
|
||||||
|
throw ClassCastException("Unable to cast compiler service, actual class received: ${daemon.javaClass}")
|
||||||
errStream.println("[daemon client] daemon not found")
|
errStream.println("[daemon client] daemon not found")
|
||||||
}
|
}
|
||||||
catch (e: ConnectException) {
|
catch (e: ConnectException) {
|
||||||
@@ -93,7 +109,7 @@ public class KotlinCompilerClient {
|
|||||||
daemon.getInputStream()
|
daemon.getInputStream()
|
||||||
.reader()
|
.reader()
|
||||||
.forEachLine {
|
.forEachLine {
|
||||||
if (daemonOptions.startEcho.isNotEmpty() && it.contains(daemonOptions.startEcho)) {
|
if (daemonOptions.runFilesPath.isNotEmpty() && it.contains(daemonOptions.runFilesPath)) {
|
||||||
isEchoRead.release()
|
isEchoRead.release()
|
||||||
return@forEachLine
|
return@forEachLine
|
||||||
}
|
}
|
||||||
@@ -102,7 +118,7 @@ public class KotlinCompilerClient {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// trying to wait for process
|
// trying to wait for process
|
||||||
if (daemonOptions.startEcho.isNotEmpty()) {
|
if (daemonOptions.runFilesPath.isNotEmpty()) {
|
||||||
val daemonStartupTimeout = System.getProperty(COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY)?.let {
|
val daemonStartupTimeout = System.getProperty(COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY)?.let {
|
||||||
try {
|
try {
|
||||||
it.toLong()
|
it.toLong()
|
||||||
@@ -140,29 +156,93 @@ public class KotlinCompilerClient {
|
|||||||
(localId.compilerDigest.isEmpty() || remoteId.compilerDigest.isEmpty() || localId.compilerDigest == remoteId.compilerDigest)
|
(localId.compilerDigest.isEmpty() || remoteId.compilerDigest.isEmpty() || localId.compilerDigest == remoteId.compilerDigest)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun connectToCompileService(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, errStream: PrintStream, autostart: Boolean = true, checkId: Boolean = true): CompileService? {
|
|
||||||
val service = connectToService(compilerId, daemonOptions, errStream)
|
class FileBasedLock(compilerId: CompilerId, daemonOptions: DaemonOptions) {
|
||||||
if (service != null) {
|
private val lockFile: File =
|
||||||
if (!checkId || checkCompilerId(service, compilerId, errStream)) {
|
File(daemonOptions.runFilesPath,
|
||||||
errStream.println("[daemon client] found the suitable daemon")
|
makeRunFilenameString(ts = "lock",
|
||||||
return service
|
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest(),
|
||||||
|
port = "0"))
|
||||||
|
|
||||||
|
private var locked: Boolean = acquireLockFile(lockFile)
|
||||||
|
|
||||||
|
public fun isLocked(): Boolean = locked
|
||||||
|
|
||||||
|
synchronized public fun release(): Unit {
|
||||||
|
if (locked) {
|
||||||
|
lock?.release()
|
||||||
|
channel?.close()
|
||||||
|
lockFile.delete()
|
||||||
|
locked = false
|
||||||
}
|
}
|
||||||
errStream.println("[daemon client] compiler identity don't match: " + compilerId.mappers.flatMap { it.toArgs("") }.joinToString(" "))
|
|
||||||
if (!autostart) return null;
|
|
||||||
errStream.println("[daemon client] shutdown the daemon")
|
|
||||||
service.shutdown()
|
|
||||||
// TODO: find more reliable way
|
|
||||||
Thread.sleep(1000)
|
|
||||||
errStream.println("[daemon client] daemon shut down correctly, restarting")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!autostart) return null;
|
|
||||||
else errStream.println("[daemon client] cannot connect to Compile Daemon, trying to start")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startDaemon(compilerId, daemonJVMOptions, daemonOptions, errStream)
|
private val channel = if (locked) RandomAccessFile(lockFile, "rw").channel else null
|
||||||
errStream.println("[daemon client] daemon started, trying to connect")
|
private val lock = channel?.lock()
|
||||||
return connectToService(compilerId, daemonOptions, errStream)
|
|
||||||
|
synchronized private fun acquireLockFile(lockFile: File): Boolean {
|
||||||
|
if (lockFile.createNewFile()) return true
|
||||||
|
try {
|
||||||
|
// attempt to delete if file is orphaned
|
||||||
|
if (lockFile.delete() && lockFile.createNewFile())
|
||||||
|
return true // if orphaned file deleted assuming that the probability of
|
||||||
|
}
|
||||||
|
catch (e: IOException) {
|
||||||
|
// Ignoring it - assuming it is another client process owning it
|
||||||
|
}
|
||||||
|
var attempts = 0L
|
||||||
|
while (lockFile.exists() && attempts++ * COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_CHECK_MS < COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_MS) {
|
||||||
|
Thread.sleep(COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_CHECK_MS)
|
||||||
|
}
|
||||||
|
if (lockFile.exists())
|
||||||
|
throw IOException("Timeout waiting the release of the lock file '${lockFile.absolutePath}")
|
||||||
|
|
||||||
|
return lockFile.createNewFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun connectToCompileService(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, errStream: PrintStream, autostart: Boolean = true, checkId: Boolean = true): CompileService? {
|
||||||
|
var attempts = 0
|
||||||
|
var fileLock: FileBasedLock? = null
|
||||||
|
try {
|
||||||
|
while (attempts++ < DAEMON_CONNECT_CYCLE_ATTEMPTS) {
|
||||||
|
val service = tryFindDaemon(File(daemonOptions.runFilesPath), compilerId, errStream)
|
||||||
|
if (service != null) {
|
||||||
|
if (!checkId || checkCompilerId(service, compilerId, errStream)) {
|
||||||
|
errStream.println("[daemon client] found the suitable daemon")
|
||||||
|
return service
|
||||||
|
}
|
||||||
|
errStream.println("[daemon client] compiler identity don't match: " + compilerId.mappers.flatMap { it.toArgs("") }.joinToString(" "))
|
||||||
|
if (!autostart) return null
|
||||||
|
errStream.println("[daemon client] shutdown the daemon")
|
||||||
|
service.shutdown()
|
||||||
|
// TODO: find more reliable way
|
||||||
|
Thread.sleep(1000)
|
||||||
|
errStream.println("[daemon client] daemon shut down correctly, restarting")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!autostart) return null
|
||||||
|
errStream.println("[daemon client] cannot connect to Compile Daemon, trying to start")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileLock == null || !fileLock.isLocked()) {
|
||||||
|
File(daemonOptions.runFilesPath).mkdirs()
|
||||||
|
fileLock = FileBasedLock(compilerId, daemonOptions)
|
||||||
|
// need to check the daemons again here, because of possible racing conditions
|
||||||
|
// note: the algorithm could be simpler if we'll acquire lock right from the beginning, but it may be costly
|
||||||
|
attempts--
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
startDaemon(compilerId, daemonJVMOptions, daemonOptions, errStream)
|
||||||
|
errStream.println("[daemon client] daemon started, trying to connect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
fileLock?.release()
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun shutdownCompileService(daemonOptions: DaemonOptions): Unit {
|
public fun shutdownCompileService(daemonOptions: DaemonOptions): Unit {
|
||||||
|
|||||||
@@ -22,22 +22,30 @@ import java.lang.management.ManagementFactory
|
|||||||
import java.security.DigestInputStream
|
import java.security.DigestInputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import kotlin.reflect.KMutableProperty1
|
import kotlin.reflect.KMutableProperty1
|
||||||
|
import kotlin.text.Regex
|
||||||
|
|
||||||
|
|
||||||
public val COMPILER_JAR_NAME: String = "kotlin-compiler.jar"
|
public val COMPILER_JAR_NAME: String = "kotlin-compiler.jar"
|
||||||
public val COMPILER_SERVICE_RMI_NAME: String = "KotlinJvmCompilerService"
|
public val COMPILER_SERVICE_RMI_NAME: String = "KotlinJvmCompilerService"
|
||||||
public val COMPILER_DAEMON_CLASS_FQN: String = "org.jetbrains.kotlin.rmi.service.CompileDaemon"
|
public val COMPILER_DAEMON_CLASS_FQN: String = "org.jetbrains.kotlin.rmi.service.CompileDaemon"
|
||||||
public val COMPILE_DAEMON_DEFAULT_PORT: Int = 17031
|
public val COMPILE_DAEMON_FIND_PORT_ATTEMPTS: Int = 10
|
||||||
public val COMPILE_DAEMON_ENABLED_PROPERTY: String ="kotlin.daemon.enabled"
|
public val COMPILE_DAEMON_PORTS_RANGE_START: Int = 17001
|
||||||
public val COMPILE_DAEMON_JVM_OPTIONS_PROPERTY: String ="kotlin.daemon.jvm.options"
|
public val COMPILE_DAEMON_PORTS_RANGE_END: Int = 18000
|
||||||
public val COMPILE_DAEMON_OPTIONS_PROPERTY: String ="kotlin.daemon.options"
|
public val COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_MS: Long = 10000L
|
||||||
|
public val COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_CHECK_MS: Long = 100L
|
||||||
|
public val COMPILE_DAEMON_ENABLED_PROPERTY: String = "kotlin.daemon.enabled"
|
||||||
|
public val COMPILE_DAEMON_JVM_OPTIONS_PROPERTY: String = "kotlin.daemon.jvm.options"
|
||||||
|
public val COMPILE_DAEMON_OPTIONS_PROPERTY: String = "kotlin.daemon.options"
|
||||||
|
public val COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX: String = "--daemon-"
|
||||||
public val COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY: String ="kotlin.daemon.startup.timeout"
|
public val COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY: String ="kotlin.daemon.startup.timeout"
|
||||||
public val COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX: String ="--daemon-"
|
|
||||||
public val COMPILE_DAEMON_TIMEOUT_INFINITE_S: Int = 0
|
public val COMPILE_DAEMON_TIMEOUT_INFINITE_S: Int = 0
|
||||||
public val COMPILE_DAEMON_MEMORY_THRESHOLD_INFINITE: Long = 0L
|
public val COMPILE_DAEMON_MEMORY_THRESHOLD_INFINITE: Long = 0L
|
||||||
|
|
||||||
val COMPILER_ID_DIGEST = "MD5"
|
val COMPILER_ID_DIGEST = "MD5"
|
||||||
|
|
||||||
|
public fun makeRunFilenameString(ts: String, digest: String, port: String, esc: String = ""): String = "kotlin-daemon$esc.$ts$esc.$digest$esc.$port$esc.run"
|
||||||
|
public fun makeRunFilenameRegex(ts: String = "[0-9-]+", digest: String = "[0-9a-f]+", port: String = "\\d+"): Regex = makeRunFilenameString(ts, digest, port, esc = "\\").toRegex()
|
||||||
|
|
||||||
|
|
||||||
open class PropMapper<C, V, P: KMutableProperty1<C, V>>(val dest: C,
|
open class PropMapper<C, V, P: KMutableProperty1<C, V>>(val dest: C,
|
||||||
val prop: P,
|
val prop: P,
|
||||||
@@ -167,17 +175,15 @@ public data class DaemonJVMOptions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public data class DaemonOptions(
|
public data class DaemonOptions(
|
||||||
public var port: Int = COMPILE_DAEMON_DEFAULT_PORT,
|
public var runFilesPath: String = File(System.getProperty("java.io.tmpdir"), "kotlin_daemon").absolutePath,
|
||||||
public var autoshutdownMemoryThreshold: Long = COMPILE_DAEMON_MEMORY_THRESHOLD_INFINITE,
|
public var autoshutdownMemoryThreshold: Long = COMPILE_DAEMON_MEMORY_THRESHOLD_INFINITE,
|
||||||
public var autoshutdownIdleSeconds: Int = COMPILE_DAEMON_TIMEOUT_INFINITE_S,
|
public var autoshutdownIdleSeconds: Int = COMPILE_DAEMON_TIMEOUT_INFINITE_S
|
||||||
public var startEcho: String = COMPILER_SERVICE_RMI_NAME
|
|
||||||
) : OptionsGroup {
|
) : OptionsGroup {
|
||||||
|
|
||||||
override val mappers: List<PropMapper<*, *, *>>
|
override val mappers: List<PropMapper<*, *, *>>
|
||||||
get() = listOf( PropMapper(this, ::port, fromString = { it.toInt() }),
|
get() = listOf( PropMapper(this, ::runFilesPath, fromString = { it.trim('"') }),
|
||||||
PropMapper(this, ::autoshutdownMemoryThreshold, fromString = { it.toLong() }, skipIf = { it == 0L }),
|
PropMapper(this, ::autoshutdownMemoryThreshold, fromString = { it.toLong() }, skipIf = { it == 0L }),
|
||||||
PropMapper(this, ::autoshutdownIdleSeconds, fromString = { it.toInt() }, skipIf = { it == 0 }),
|
PropMapper(this, ::autoshutdownIdleSeconds, fromString = { it.toInt() }, skipIf = { it == 0 }))
|
||||||
PropMapper(this, ::startEcho, fromString = { it.trim('"') }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -205,13 +211,20 @@ fun updateEntryDigest(entry: File, md: MessageDigest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jvmName("getFilesClasspathDigest_Files")
|
||||||
fun Iterable<File>.getFilesClasspathDigest(): String {
|
fun Iterable<File>.getFilesClasspathDigest(): String {
|
||||||
val md = MessageDigest.getInstance(COMPILER_ID_DIGEST)
|
val md = MessageDigest.getInstance(COMPILER_ID_DIGEST)
|
||||||
this.forEach { updateEntryDigest(it, md) }
|
this.forEach { updateEntryDigest(it, md) }
|
||||||
return md.digest().joinToString("", transform = { "%02x".format(it) })
|
return md.digest().joinToString("", transform = { "%02x".format(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Iterable<String>.getClasspathDigest(): String = map { File(it) }.getFilesClasspathDigest()
|
jvmName("getFilesClasspathDigest_Strings")
|
||||||
|
fun Iterable<String>.getFilesClasspathDigest(): String = map { File(it) }.getFilesClasspathDigest()
|
||||||
|
|
||||||
|
fun Iterable<String>.distinctStringsDigest(): String =
|
||||||
|
MessageDigest.getInstance(COMPILER_ID_DIGEST)
|
||||||
|
.digest(this.distinct().sort().joinToString("").toByteArray())
|
||||||
|
.joinToString("", transform = { "%02x".format(it) })
|
||||||
|
|
||||||
|
|
||||||
public data class CompilerId(
|
public data class CompilerId(
|
||||||
@@ -227,7 +240,7 @@ public data class CompilerId(
|
|||||||
StringPropMapper(this, ::compilerVersion))
|
StringPropMapper(this, ::compilerVersion))
|
||||||
|
|
||||||
public fun updateDigest() {
|
public fun updateDigest() {
|
||||||
compilerDigest = compilerClasspath.getClasspathDigest()
|
compilerDigest = compilerClasspath.getFilesClasspathDigest()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -17,23 +17,22 @@
|
|||||||
package org.jetbrains.kotlin.rmi.service
|
package org.jetbrains.kotlin.rmi.service
|
||||||
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
||||||
import org.jetbrains.kotlin.rmi.COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX
|
import org.jetbrains.kotlin.rmi.*
|
||||||
import org.jetbrains.kotlin.rmi.CompilerId
|
|
||||||
import org.jetbrains.kotlin.rmi.DaemonOptions
|
|
||||||
import org.jetbrains.kotlin.rmi.filterExtractProps
|
|
||||||
import org.jetbrains.kotlin.service.CompileServiceImpl
|
import org.jetbrains.kotlin.service.CompileServiceImpl
|
||||||
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
import java.lang.management.ManagementFactory
|
import java.lang.management.ManagementFactory
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
|
import java.rmi.RemoteException
|
||||||
import java.rmi.registry.LocateRegistry
|
import java.rmi.registry.LocateRegistry
|
||||||
|
import java.rmi.registry.Registry
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.jar.Manifest
|
import java.util.jar.Manifest
|
||||||
import java.util.logging.LogManager
|
import java.util.logging.LogManager
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
import kotlin.platform.platformStatic
|
|
||||||
|
|
||||||
class LogStream(name: String) : OutputStream() {
|
class LogStream(name: String) : OutputStream() {
|
||||||
|
|
||||||
@@ -107,7 +106,7 @@ public class CompileDaemon {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
platformStatic public fun main(args: Array<String>) {
|
jvmStatic public fun main(args: Array<String>) {
|
||||||
|
|
||||||
log.info("Kotlin compiler daemon version " + (loadVersionFromResource() ?: "<unknown>"))
|
log.info("Kotlin compiler daemon version " + (loadVersionFromResource() ?: "<unknown>"))
|
||||||
log.info("daemon JVM args: " + ManagementFactory.getRuntimeMXBean().inputArguments.joinToString(" "))
|
log.info("daemon JVM args: " + ManagementFactory.getRuntimeMXBean().inputArguments.joinToString(" "))
|
||||||
@@ -132,13 +131,23 @@ public class CompileDaemon {
|
|||||||
//
|
//
|
||||||
// setDaemonPpermissions(daemonOptions.port)
|
// setDaemonPpermissions(daemonOptions.port)
|
||||||
|
|
||||||
val registry = LocateRegistry.createRegistry(daemonOptions.port);
|
val (registry, port) = createRegistry(COMPILE_DAEMON_FIND_PORT_ATTEMPTS)
|
||||||
val compiler = K2JVMCompiler()
|
val runFileDir = File(daemonOptions.runFilesPath)
|
||||||
|
runFileDir.mkdirs()
|
||||||
|
val runFile = File(runFileDir,
|
||||||
|
makeRunFilenameString(ts = "%tFT%<tRZ".format(Calendar.getInstance(TimeZone.getTimeZone("Z"))),
|
||||||
|
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest(),
|
||||||
|
port = port.toString()))
|
||||||
|
if (!runFile.createNewFile()) {
|
||||||
|
throw IllegalStateException("Unable to create run file '${runFile.absolutePath}'")
|
||||||
|
}
|
||||||
|
runFile.deleteOnExit()
|
||||||
|
|
||||||
|
val compiler = K2JVMCompiler()
|
||||||
CompileServiceImpl(registry, compiler, compilerId, daemonOptions)
|
CompileServiceImpl(registry, compiler, compilerId, daemonOptions)
|
||||||
|
|
||||||
if (daemonOptions.startEcho.isNotEmpty())
|
if (daemonOptions.runFilesPath.isNotEmpty())
|
||||||
println(daemonOptions.startEcho)
|
println(runFile.name)
|
||||||
|
|
||||||
// this stops redirected streams reader(s) on the client side and prevent some situations with hanging threads
|
// this stops redirected streams reader(s) on the client side and prevent some situations with hanging threads
|
||||||
System.out.close()
|
System.out.close()
|
||||||
@@ -147,5 +156,23 @@ public class CompileDaemon {
|
|||||||
System.setErr(PrintStream(LogStream("stderr")))
|
System.setErr(PrintStream(LogStream("stderr")))
|
||||||
System.setOut(PrintStream(LogStream("stdout")))
|
System.setOut(PrintStream(LogStream("stdout")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val random = Random()
|
||||||
|
|
||||||
|
private fun createRegistry(attempts: Int) : Pair<Registry, Int> {
|
||||||
|
var i = 0
|
||||||
|
var lastException: RemoteException? = null
|
||||||
|
while (i++ < attempts) {
|
||||||
|
val port = random.nextInt(COMPILE_DAEMON_PORTS_RANGE_END - COMPILE_DAEMON_PORTS_RANGE_START) + COMPILE_DAEMON_PORTS_RANGE_START
|
||||||
|
try {
|
||||||
|
return Pair(LocateRegistry.createRegistry(port), port)
|
||||||
|
}
|
||||||
|
catch (e: RemoteException) {
|
||||||
|
// assuming that the port is already taken
|
||||||
|
lastException = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw IllegalStateException("Cannot find free port in $attempts attempts", lastException)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,6 +115,7 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
|
|||||||
log.info("Used memory: $endMem kb (${"%+d".format(endMem - startMem)} kb)")
|
log.info("Used memory: $endMem kb (${"%+d".format(endMem - startMem)} kb)")
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
// TODO: consider possibilities to handle OutOfMemory
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
log.info("Error: $e")
|
log.info("Error: $e")
|
||||||
throw e
|
throw e
|
||||||
|
|||||||
@@ -26,21 +26,21 @@ import org.jetbrains.kotlin.rmi.kotlinr.KotlinCompilerClient
|
|||||||
import org.jetbrains.kotlin.test.JetTestUtils
|
import org.jetbrains.kotlin.test.JetTestUtils
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
val KOTLIN_DAEMON_TEST_PORT = 19753
|
import java.util.*
|
||||||
|
|
||||||
public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||||
|
|
||||||
data class CompilerResults(val resultCode: Int, val out: String)
|
data class CompilerResults(val resultCode: Int, val out: String)
|
||||||
|
|
||||||
val daemonOptions = DaemonOptions(port = KOTLIN_DAEMON_TEST_PORT)
|
val daemonOptions = DaemonOptions(runFilesPath = tmpdir.absolutePath)
|
||||||
val daemonLaunchingOptions = DaemonJVMOptions()
|
val daemonJVMOptions = DaemonJVMOptions()
|
||||||
val compilerId by lazy { CompilerId.makeCompilerId( File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler.jar"),
|
val compilerId by lazy { CompilerId.makeCompilerId( File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-compiler.jar"),
|
||||||
File("dependencies/bootstrap-compiler/Kotlin/kotlinc/lib/kotlin-runtime.jar"),
|
File("dependencies/bootstrap-compiler/Kotlin/kotlinc/lib/kotlin-runtime.jar"),
|
||||||
File("dependencies/bootstrap-compiler/Kotlin/kotlinc/lib/kotlin-reflect.jar")) }
|
File("dependencies/bootstrap-compiler/Kotlin/kotlinc/lib/kotlin-reflect.jar")) }
|
||||||
|
|
||||||
private fun compileOnDaemon(args: Array<out String>): CompilerResults {
|
private fun compileOnDaemon(args: Array<out String>): CompilerResults {
|
||||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, daemonLaunchingOptions, daemonOptions, System.err, autostart = true, checkId = true)
|
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, System.err, autostart = true, checkId = true)
|
||||||
TestCase.assertNotNull("failed to connect daemon", daemon)
|
TestCase.assertNotNull("failed to connect daemon", daemon)
|
||||||
val strm = ByteArrayOutputStream()
|
val strm = ByteArrayOutputStream()
|
||||||
val code = KotlinCompilerClient.compile(daemon!!, args, strm)
|
val code = KotlinCompilerClient.compile(daemon!!, args, strm)
|
||||||
|
|||||||
Reference in New Issue
Block a user