From 96558c52ffd2f0bc7548b00bdc9b922ce9c9ab8b Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Tue, 8 Sep 2015 17:28:31 +0200 Subject: [PATCH] Refactorings, reformatting code, applying code style and other cleanup --- .../rmi/kotlinr/KotlinCompilerClient.kt | 685 +++++++++--------- .../org/jetbrains/kotlin/rmi/DaemonParams.kt | 147 ++-- .../kotlin/rmi/LoopbackSocketFactory.kt | 13 +- .../kotlin/rmi/service/CompileDaemon.kt | 263 ++++--- .../kotlin/rmi/service/CompileServiceImpl.kt | 30 +- .../kotlin/daemon/CompilerDaemonTest.kt | 4 +- .../compilerRunner/KotlinCompilerRunner.java | 4 +- 7 files changed, 570 insertions(+), 576 deletions(-) diff --git a/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/KotlinCompilerClient.kt b/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/KotlinCompilerClient.kt index 0fc7f4f5282..1e370e2c273 100644 --- a/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/KotlinCompilerClient.kt +++ b/compiler/rmi/kotlinr/src/org/jetbrains/kotlin/rmi/kotlinr/KotlinCompilerClient.kt @@ -26,6 +26,355 @@ import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit import kotlin.concurrent.thread + +public object KotlinCompilerClient { + + val DAEMON_DEFAULT_STARTUP_TIMEOUT_MS = 10000L + val DAEMON_CONNECT_CYCLE_ATTEMPTS = 3 + + + // TODO: remove jvmStatic after all use sites will switch to kotlin + jvmStatic + public fun connectToCompileService(compilerId: CompilerId, + daemonJVMOptions: DaemonJVMOptions, + daemonOptions: DaemonOptions, + reportingTargets: DaemonReportingTargets, + 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, reportingTargets) + if (service != null) { + if (!checkId || checkCompilerId(service, compilerId)) { + reportingTargets.report(DaemonReportCategory.DEBUG, "connected to the daemon") + return service + } + reportingTargets.report(DaemonReportCategory.DEBUG, "compiler identity don't match: " + compilerId.mappers.flatMap { it.toArgs("") }.joinToString(" ")) + if (!autostart) return null + reportingTargets.report(DaemonReportCategory.DEBUG, "shutdown the daemon") + service.shutdown() + // TODO: find more reliable way + Thread.sleep(1000) + reportingTargets.report(DaemonReportCategory.DEBUG, "daemon shut down correctly, restarting search") + } + else { + if (!autostart) return null + reportingTargets.report(DaemonReportCategory.DEBUG, "no suitable daemon found, starting a new one") + } + + 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, reportingTargets) + reportingTargets.report(DaemonReportCategory.DEBUG, "daemon started, trying to resolve") + } + } + } + catch (e: Exception) { + reportingTargets.report(DaemonReportCategory.EXCEPTION, e.toString()) + } + finally { + fileLock?.release() + } + return null + } + + + public fun shutdownCompileService(daemonOptions: DaemonOptions): Unit { + KotlinCompilerClient.connectToCompileService(CompilerId(), DaemonJVMOptions(), daemonOptions, DaemonReportingTargets(out = System.out), autostart = false, checkId = false) + ?.shutdown() + } + + + public fun shutdownCompileService(): Unit { + shutdownCompileService(DaemonOptions()) + } + + + public fun compile(compiler: CompileService, args: Array, out: OutputStream): Int { + + val outStrm = RemoteOutputStreamServer(out) + try { + return compiler.remoteCompile(args, outStrm, CompileService.OutputFormat.PLAIN, outStrm) + } + finally { + outStrm.disconnect() + } + } + + + // TODO: remove jvmStatic after all use sites will switch to kotlin + jvmStatic + public fun incrementalCompile(compiler: CompileService, args: Array, caches: Map, compilerOut: OutputStream, daemonOut: OutputStream): Int { + + val compilerOutStreamServer = RemoteOutputStreamServer(compilerOut) + val daemonOutStreamServer = RemoteOutputStreamServer(daemonOut) + val cacheServers = hashMapOf() + try { + caches.mapValuesTo(cacheServers, { RemoteIncrementalCacheServer(it.getValue()) }) + return compiler.remoteIncrementalCompile(args, cacheServers, compilerOutStreamServer, CompileService.OutputFormat.XML, daemonOutStreamServer) + } + finally { + cacheServers.forEach { it.getValue().disconnect() } + compilerOutStreamServer.disconnect() + daemonOutStreamServer.disconnect() + } + } + + + data class ClientOptions( + public var stop: Boolean = false + ) : OptionsGroup { + override val mappers: List> + get() = listOf(BoolPropMapper(this, ::stop)) + } + + + jvmStatic public fun main(vararg args: String) { + val compilerId = CompilerId() + val daemonOptions = DaemonOptions() + val daemonLaunchingOptions = DaemonJVMOptions() + val clientOptions = ClientOptions() + val filteredArgs = args.asIterable().filterExtractProps(compilerId, daemonOptions, daemonLaunchingOptions, clientOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) + + if (!clientOptions.stop) { + if (compilerId.compilerClasspath.none()) { + // attempt to find compiler to use + System.err.println("compiler wasn't explicitly specified, attempt to find appropriate jar") + System.getProperty("java.class.path") + ?.split(File.pathSeparator) + ?.map { File(it).parentFile } + ?.distinct() + ?.map { + it?.walk() + ?.firstOrNull { it.name.equals(COMPILER_JAR_NAME, ignoreCase = true) } + } + ?.filterNotNull() + ?.firstOrNull() + ?.let { compilerId.compilerClasspath = listOf(it.absolutePath) } + } + if (compilerId.compilerClasspath.none()) + throw IllegalArgumentException("Cannot find compiler jar") + else + println("desired compiler classpath: " + compilerId.compilerClasspath.joinToString(File.pathSeparator)) + + compilerId.updateDigest() + } + + val daemon = connectToCompileService(compilerId, daemonLaunchingOptions, daemonOptions, DaemonReportingTargets(out = System.out), autostart = !clientOptions.stop, checkId = !clientOptions.stop) + + if (daemon == null) { + if (clientOptions.stop) { + System.err.println("No daemon found to shut down") + } + else throw Exception("Unable to connect to daemon") + } + else when { + clientOptions.stop -> { + println("Shutdown the daemon") + daemon.shutdown() + println("Daemon shut down successfully") + } + else -> { + println("Executing daemon compilation with args: " + filteredArgs.joinToString(" ")) + val outStrm = RemoteOutputStreamServer(System.out) + try { + val memBefore = daemon.getUsedMemory() / 1024 + val startTime = System.nanoTime() + + val res = daemon.remoteCompile(filteredArgs.toArrayList().toTypedArray(), outStrm, CompileService.OutputFormat.PLAIN, outStrm) + + val endTime = System.nanoTime() + println("Compilation result code: $res") + val memAfter = daemon.getUsedMemory() / 1024 + println("Compilation time: " + TimeUnit.NANOSECONDS.toMillis(endTime - startTime) + " ms") + println("Used memory $memAfter (${"%+d".format(memAfter - memBefore)} kb)") + } + finally { + outStrm.disconnect() + } + } + } + } + + // --- Implementation --------------------------------------- + + val verboseReporting = System.getProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY) != null + + fun DaemonReportingTargets.report(category: DaemonReportCategory, message: String, source: String = "daemon client") { + if (category == DaemonReportCategory.DEBUG && !verboseReporting) return + out?.println("[$source] ${category.name()}: $message") + messages?.add(DaemonReportMessage(category, "[$source] $message")) + } + + private fun tryFindDaemon(registryDir: File, compilerId: CompilerId, reportingTargets: DaemonReportingTargets): CompileService? { + val classPathDigest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest() + val daemons = registryDir.walk() + .map { Pair(it, makeRunFilenameRegex(digest = classPathDigest, port = "(\\d+)").match(it.name)?.groups?.get(1)?.value?.toInt() ?: 0) } + .filter { it.second != 0 } + .map { + reportingTargets.report(DaemonReportCategory.DEBUG, "found suitable daemon on port ${it.second}, trying to connect") + val daemon = tryConnectToDaemon(it.second, reportingTargets) + // cleaning orphaned file; note: daemon should shut itself down if it detects that the run file is deleted + if (daemon == null && !it.first.delete()) { + reportingTargets.report(DaemonReportCategory.INFO, "WARNING: unable to delete seemingly orphaned file '${it.first.absolutePath}', 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 tryConnectToDaemon(port: Int, reportingTargets: DaemonReportingTargets): CompileService? { + try { + val daemon = LocateRegistry.getRegistry(loopbackAddrName, port) + ?.lookup(COMPILER_SERVICE_RMI_NAME) + if (daemon != null) + return daemon as? CompileService ?: + throw ClassCastException("Unable to cast compiler service, actual class received: ${daemon.javaClass}") + reportingTargets.report(DaemonReportCategory.EXCEPTION, "daemon not found") + } + catch (e: ConnectException) { + reportingTargets.report(DaemonReportCategory.EXCEPTION, "cannot connect to registry: " + (e.getCause()?.getMessage() ?: e.getMessage() ?: "unknown exception")) + // ignoring it - processing below + } + return null + } + + + private fun startDaemon(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, reportingTargets: DaemonReportingTargets) { + val javaExecutable = File(System.getProperty("java.home"), "bin").let { + val javaw = File(it, "javaw.exe") + // TODO: doesn't seem reliable enough, consider more checks if OS is of windows flavor, etc. + if (javaw.exists() && javaw.isFile && javaw.canExecute()) javaw else File(it, "java") + } + val args = listOf(javaExecutable.absolutePath, + "-cp", compilerId.compilerClasspath.joinToString(File.pathSeparator)) + + daemonJVMOptions.mappers.flatMap { it.toArgs("-") } + + COMPILER_DAEMON_CLASS_FQN + + daemonOptions.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) } + + compilerId.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) } + reportingTargets.report(DaemonReportCategory.DEBUG, "starting the daemon as: " + args.joinToString(" ")) + val processBuilder = ProcessBuilder(args).redirectErrorStream(true) + // assuming daemon process is deaf and (mostly) silent, so do not handle streams + val daemon = processBuilder.start() + + var isEchoRead = Semaphore(1) + isEchoRead.acquire() + + val stdoutThread = + thread { + daemon.inputStream + .reader() + .forEachLine { + if (daemonOptions.runFilesPath.isNotEmpty() && it.contains(daemonOptions.runFilesPath)) { + isEchoRead.release() + return@forEachLine + } + reportingTargets.report(DaemonReportCategory.DEBUG, it, "daemon") + } + } + try { + // trying to wait for process + val daemonStartupTimeout = System.getProperty(COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY)?.let { + try { + it.toLong() + } + catch (e: Exception) { + reportingTargets.report(DaemonReportCategory.INFO, "unable to interpret $COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY property ('$it'); using default timeout $DAEMON_DEFAULT_STARTUP_TIMEOUT_MS ms") + null + } + } ?: DAEMON_DEFAULT_STARTUP_TIMEOUT_MS + if (daemonOptions.runFilesPath.isNotEmpty()) { + val succeeded = isEchoRead.tryAcquire(daemonStartupTimeout, TimeUnit.MILLISECONDS) + if (!daemon.isAlive()) + throw Exception("Daemon terminated unexpectedly") + if (!succeeded) + throw Exception("Unable to get response from daemon in $daemonStartupTimeout ms") + } + else + // without startEcho defined waiting for max timeout + Thread.sleep(daemonStartupTimeout) + } + finally { + // assuming that all important output is already done, the rest should be routed to the log by the daemon itself + if (stdoutThread.isAlive) { + // TODO: find better method to stop the thread, but seems it will require asynchronous consuming of the stream + stdoutThread.stop() + } + } + } + + + private fun checkCompilerId(compiler: CompileService, localId: CompilerId): Boolean { + val remoteId = compiler.getCompilerId() + return (localId.compilerVersion.isEmpty() || localId.compilerVersion == remoteId.compilerVersion) && + (localId.compilerClasspath.all { remoteId.compilerClasspath.contains(it) }) && + (localId.compilerDigest.isEmpty() || remoteId.compilerDigest.isEmpty() || localId.compilerDigest == remoteId.compilerDigest) + } + + + class FileBasedLock(compilerId: CompilerId, daemonOptions: DaemonOptions) { + private val lockFile: File = + File(daemonOptions.runFilesPath, + makeRunFilenameString(ts = "lock", + 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 + } + } + + private val channel = if (locked) RandomAccessFile(lockFile, "rw").channel else null + private val lock = channel?.lock() + + 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() + } + } +} + + fun Process.isAlive() = try { this.exitValue() @@ -43,339 +392,3 @@ public enum class DaemonReportCategory { public data class DaemonReportMessage(public val category: DaemonReportCategory, public val message: String) public class DaemonReportingTargets(public val out: PrintStream? = null, public val messages: MutableCollection? = null) - - -public class KotlinCompilerClient { - - companion object { - - val DAEMON_DEFAULT_STARTUP_TIMEOUT_MS = 10000L - val DAEMON_CONNECT_CYCLE_ATTEMPTS = 3 - - val verboseReporting = System.getProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY) != null - - fun DaemonReportingTargets.report(category: DaemonReportCategory, message: String, source: String = "daemon client") { - if (category == DaemonReportCategory.DEBUG && !verboseReporting) return - out?.println("[$source] ${category.name()}: $message") - messages?.add(DaemonReportMessage(category, "[$source] $message")) - } - - private fun tryFindDaemon(registryDir: File, compilerId: CompilerId, reportingTargets: DaemonReportingTargets): CompileService? { - val classPathDigest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest() - val daemons = registryDir.walk() - .map { Pair(it, makeRunFilenameRegex(digest = classPathDigest, port = "(\\d+)").match(it.name)?.groups?.get(1)?.value?.toInt() ?: 0) } - .filter { it.second != 0 } - .map { - reportingTargets.report(DaemonReportCategory.DEBUG, "found suitable daemon on port ${it.second}, trying to connect") - val daemon = tryConnectToDaemon(it.second, reportingTargets) - // cleaning orphaned file; note: daemon should shut itself down if it detects that the run file is deleted - if (daemon == null && !it.first.delete()) { - reportingTargets.report(DaemonReportCategory.INFO, "WARNING: unable to delete seemingly orphaned file '${it.first.absolutePath}', 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 tryConnectToDaemon(port: Int, reportingTargets: DaemonReportingTargets): CompileService? { - try { - val daemon = LocateRegistry.getRegistry(loopbackAddrName, port) - ?.lookup(COMPILER_SERVICE_RMI_NAME) - if (daemon != null) - return daemon as? CompileService ?: - throw ClassCastException("Unable to cast compiler service, actual class received: ${daemon.javaClass}") - reportingTargets.report(DaemonReportCategory.EXCEPTION, "daemon not found") - } - catch (e: ConnectException) { - reportingTargets.report(DaemonReportCategory.EXCEPTION, "cannot connect to registry: " + (e.getCause()?.getMessage() ?: e.getMessage() ?: "unknown exception")) - // ignoring it - processing below - } - return null - } - - - private fun startDaemon(compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, reportingTargets: DaemonReportingTargets) { - val javaExecutable = File(System.getProperty("java.home"), "bin").let { - val javaw = File(it, "javaw.exe") - if (javaw.exists()) javaw - else File(it, "java") - } - // TODO: add some specific environment variables to the cp and may be command line, to allow some specific startup configs - val args = listOf(javaExecutable.absolutePath, - "-cp", compilerId.compilerClasspath.joinToString(File.pathSeparator)) + - daemonJVMOptions.mappers.flatMap { it.toArgs("-") } + - COMPILER_DAEMON_CLASS_FQN + - daemonOptions.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) } + - compilerId.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) } - reportingTargets.report(DaemonReportCategory.DEBUG, "starting the daemon as: " + args.joinToString(" ")) - val processBuilder = ProcessBuilder(args).redirectErrorStream(true) - // assuming daemon process is deaf and (mostly) silent, so do not handle streams - val daemon = processBuilder.start() - - var isEchoRead = Semaphore(1) - isEchoRead.acquire() - - val stdoutThread = - thread { - daemon.getInputStream() - .reader() - .forEachLine { - if (daemonOptions.runFilesPath.isNotEmpty() && it.contains(daemonOptions.runFilesPath)) { - isEchoRead.release() - return@forEachLine - } - reportingTargets.report(DaemonReportCategory.DEBUG, it, "daemon") - } - } - try { - // trying to wait for process - val daemonStartupTimeout = System.getProperty(COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY)?.let { - try { - it.toLong() - } - catch (e: Exception) { - reportingTargets.report(DaemonReportCategory.INFO, "unable to interpret $COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY property ('$it'); using default timeout $DAEMON_DEFAULT_STARTUP_TIMEOUT_MS ms") - null - } - } ?: DAEMON_DEFAULT_STARTUP_TIMEOUT_MS - if (daemonOptions.runFilesPath.isNotEmpty()) { - val succeeded = isEchoRead.tryAcquire(daemonStartupTimeout, TimeUnit.MILLISECONDS) - if (!daemon.isAlive()) - throw Exception("Daemon terminated unexpectedly") - if (!succeeded) - throw Exception("Unable to get response from daemon in $daemonStartupTimeout ms") - } - else - // without startEcho defined waiting for max timeout - Thread.sleep(daemonStartupTimeout) - } - finally { - // assuming that all important output is already done, the rest should be routed to the log by the daemon itself - if (stdoutThread.isAlive) - // TODO: find better method to stop the thread, but seems it will require asynchronous consuming of the stream - stdoutThread.stop() - } - } - - public fun checkCompilerId(compiler: CompileService, localId: CompilerId): Boolean { - val remoteId = compiler.getCompilerId() - return (localId.compilerVersion.isEmpty() || localId.compilerVersion == remoteId.compilerVersion) && - (localId.compilerClasspath.all { remoteId.compilerClasspath.contains(it) }) && - (localId.compilerDigest.isEmpty() || remoteId.compilerDigest.isEmpty() || localId.compilerDigest == remoteId.compilerDigest) - } - - - class FileBasedLock(compilerId: CompilerId, daemonOptions: DaemonOptions) { - private val lockFile: File = - File(daemonOptions.runFilesPath, - makeRunFilenameString(ts = "lock", - 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 - } - } - - private val channel = if (locked) RandomAccessFile(lockFile, "rw").channel else null - private val lock = channel?.lock() - - 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, - reportingTargets: DaemonReportingTargets, - 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, reportingTargets) - if (service != null) { - if (!checkId || checkCompilerId(service, compilerId)) { - reportingTargets.report(DaemonReportCategory.DEBUG, "connected to the daemon") - return service - } - reportingTargets.report(DaemonReportCategory.DEBUG, "compiler identity don't match: " + compilerId.mappers.flatMap { it.toArgs("") }.joinToString(" ")) - if (!autostart) return null - reportingTargets.report(DaemonReportCategory.DEBUG, "shutdown the daemon") - service.shutdown() - // TODO: find more reliable way - Thread.sleep(1000) - reportingTargets.report(DaemonReportCategory.DEBUG, "daemon shut down correctly, restarting search") - } - else { - if (!autostart) return null - reportingTargets.report(DaemonReportCategory.DEBUG, "no suitable daemon found, starting a new one") - } - - 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, reportingTargets) - reportingTargets.report(DaemonReportCategory.DEBUG, "daemon started, trying to resolve") - } - } - } - catch (e: Exception) { - reportingTargets.report(DaemonReportCategory.EXCEPTION, e.toString()) - } - finally { - fileLock?.release() - } - return null - } - - public fun shutdownCompileService(daemonOptions: DaemonOptions): Unit { - KotlinCompilerClient.connectToCompileService(CompilerId(), DaemonJVMOptions(), daemonOptions, DaemonReportingTargets(out = System.out), autostart = false, checkId = false) - ?.shutdown() - } - - public fun shutdownCompileService(): Unit { - shutdownCompileService(DaemonOptions()) - } - - public fun compile(compiler: CompileService, args: Array, out: OutputStream): Int { - - val outStrm = RemoteOutputStreamServer(out) - try { - return compiler.remoteCompile(args, outStrm, CompileService.OutputFormat.PLAIN, outStrm) - } - finally { - outStrm.disconnect() - } - } - - public fun incrementalCompile(compiler: CompileService, args: Array, caches: Map, compilerOut: OutputStream, daemonOut: OutputStream): Int { - - val compilerOutStreamServer = RemoteOutputStreamServer(compilerOut) - val daemonOutStreamServer = RemoteOutputStreamServer(daemonOut) - val cacheServers = hashMapOf() - try { - caches.mapValuesTo(cacheServers, { RemoteIncrementalCacheServer( it.getValue()) }) - return compiler.remoteIncrementalCompile(args, cacheServers, compilerOutStreamServer, CompileService.OutputFormat.XML, daemonOutStreamServer) - } - finally { - cacheServers.forEach { it.getValue().disconnect() } - compilerOutStreamServer.disconnect() - daemonOutStreamServer.disconnect() - } - } - - data class ClientOptions( - public var stop: Boolean = false - ) : OptionsGroup { - override val mappers: List> - get() = listOf( BoolPropMapper(this, ::stop)) - } - - jvmStatic public fun main(vararg args: String) { - val compilerId = CompilerId() - val daemonOptions = DaemonOptions() - val daemonLaunchingOptions = DaemonJVMOptions() - val clientOptions = ClientOptions() - val filteredArgs = args.asIterable().filterExtractProps(compilerId, daemonOptions, daemonLaunchingOptions, clientOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) - - if (!clientOptions.stop) { - if (compilerId.compilerClasspath.none()) { - // attempt to find compiler to use - System.err.println("compiler wasn't explicitly specified, attempt to find appropriate jar") - System.getProperty("java.class.path") - ?.split(File.pathSeparator) - ?.map { File(it).parentFile } - ?.distinct() - ?.map { - it?.walk() - ?.firstOrNull { it.getName().equals(COMPILER_JAR_NAME, ignoreCase = true) } - } - ?.filterNotNull() - ?.firstOrNull() - ?.let { compilerId.compilerClasspath = listOf(it.absolutePath) } - } - if (compilerId.compilerClasspath.none()) - throw IllegalArgumentException("Cannot find compiler jar") - else - println("desired compiler classpath: " + compilerId.compilerClasspath.joinToString(File.pathSeparator)) - - compilerId.updateDigest() - } - - val daemon = connectToCompileService(compilerId, daemonLaunchingOptions, daemonOptions, DaemonReportingTargets(out = System.out), autostart = !clientOptions.stop, checkId = !clientOptions.stop) - - if (daemon == null) { - if (clientOptions.stop) System.err.println("No daemon found to shut down") - else throw Exception("Unable to connect to daemon") - } - else when { - clientOptions.stop -> { - println("Shutdown the daemon") - daemon.shutdown() - println("Daemon shut down successfully") - } - else -> { - println("Executing daemon compilation with args: " + filteredArgs.joinToString(" ")) - val outStrm = RemoteOutputStreamServer(System.out) - try { - val memBefore = daemon.getUsedMemory() / 1024 - val startTime = System.nanoTime() - val res = daemon.remoteCompile(filteredArgs.toArrayList().toTypedArray(), outStrm, CompileService.OutputFormat.PLAIN, outStrm) - val endTime = System.nanoTime() - println("Compilation result code: $res") - val memAfter = daemon.getUsedMemory() / 1024 - println("Compilation time: " + TimeUnit.NANOSECONDS.toMillis(endTime - startTime) + " ms") - println("Used memory $memAfter (${"%+d".format(memAfter - memBefore)} kb)") - } - finally { - outStrm.disconnect() - } - } - } - } - } -} - diff --git a/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/DaemonParams.kt b/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/DaemonParams.kt index 84555bb547e..0f04bd33b10 100644 --- a/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/DaemonParams.kt +++ b/compiler/rmi/rmi-interface/src/org/jetbrains/kotlin/rmi/DaemonParams.kt @@ -40,7 +40,7 @@ public val COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY: String = "kotlin.daemon.cl public val COMPILE_DAEMON_REPORT_PERF_PROPERTY: String = "kotlin.daemon.perf" public val COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY: String = "kotlin.daemon.verbose" 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_DEFAULT_FILES_PREFIX: String = "kotlin-daemon" public val COMPILE_DAEMON_DATA_DIRECTORY_NAME: String = "." + COMPILE_DAEMON_DEFAULT_FILES_PREFIX public val COMPILE_DAEMON_TIMEOUT_INFINITE_S: Int = 0 @@ -51,67 +51,70 @@ public val COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH: String get() = // TODO consider special case for windows - local appdata File(System.getProperty("user.home"), COMPILE_DAEMON_DATA_DIRECTORY_NAME).absolutePath - val COMPILER_ID_DIGEST = "MD5" + public fun makeRunFilenameString(ts: String, digest: String, port: String, esc: String = ""): String = "$COMPILE_DAEMON_DEFAULT_FILES_PREFIX$esc.$ts$esc.$digest$esc.$port$esc.run" + public fun makeRunFilenameRegex(ts: String = "[0-9TZ:\\.\\+-]+", digest: String = "[0-9a-f]+", port: String = "\\d+"): Regex = makeRunFilenameString(ts, digest, port, esc = "\\").toRegex() -open class PropMapper>(val dest: C, - val prop: P, - val names: List = listOf(prop.name), - val fromString: (String) -> V, - val toString: ((V) -> String?) = { it.toString() }, - val skipIf: ((V) -> Boolean) = { false }, - val mergeDelimiter: String? = null) -{ +open class PropMapper>(val dest: C, + val prop: P, + val names: List = listOf(prop.name), + val fromString: (String) -> V, + val toString: ((V) -> String?) = { it.toString() }, + val skipIf: ((V) -> Boolean) = { false }, + val mergeDelimiter: String? = null) { open fun toArgs(prefix: String = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX): List = when { skipIf(prop.get(dest)) -> listOf() - mergeDelimiter != null -> listOf( listOf(prefix + names.first(), toString(prop.get(dest))).filterNotNull().joinToString(mergeDelimiter)) + mergeDelimiter != null -> listOf(listOf(prefix + names.first(), toString(prop.get(dest))).filterNotNull().joinToString(mergeDelimiter)) else -> listOf(prefix + names.first(), toString(prop.get(dest))).filterNotNull() } + open fun apply(s: String) = prop.set(dest, fromString(s)) } -class NullablePropMapper>(dest: C, - prop: P, - names: List = listOf(), - fromString: ((String) -> V), - toString: ((V) -> String?) = { it.toString() }, - skipIf: ((V) -> Boolean) = { it == null }, - mergeDelimiter: String? = null) +class NullablePropMapper>(dest: C, + prop: P, + names: List = listOf(), + fromString: ((String) -> V), + toString: ((V) -> String?) = { it.toString() }, + skipIf: ((V) -> Boolean) = { it == null }, + mergeDelimiter: String? = null) : PropMapper(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name), - fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter) + fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter) -class StringPropMapper>(dest: C, - prop: P, - names: List = listOf(), - fromString: ((String) -> String) = { it }, - toString: ((String) -> String?) = { it.toString() }, - skipIf: ((String) -> Boolean) = { it.isEmpty() }, - mergeDelimiter: String? = null) + +class StringPropMapper>(dest: C, + prop: P, + names: List = listOf(), + fromString: ((String) -> String) = { it }, + toString: ((String) -> String?) = { it.toString() }, + skipIf: ((String) -> Boolean) = { it.isEmpty() }, + mergeDelimiter: String? = null) : PropMapper(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name), - fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter) + fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter) -class BoolPropMapper>(dest: C, prop: P, names: List = listOf()) - : PropMapper(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name), - fromString = { true }, toString = { null }, skipIf = { !prop.get(dest) }) +class BoolPropMapper>(dest: C, prop: P, names: List = listOf()) +: PropMapper(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name), + fromString = { true }, toString = { null }, skipIf = { !prop.get(dest) }) -class RestPropMapper>>(dest: C, prop: P) - : PropMapper, P>(dest = dest, prop = prop, toString = { null }, fromString = { arrayListOf() }) -{ +class RestPropMapper>>(dest: C, prop: P) +: PropMapper, P>(dest = dest, prop = prop, toString = { null }, fromString = { arrayListOf() }) { override fun toArgs(prefix: String): List = prop.get(dest).map { prefix + it } override fun apply(s: String) = add(s) - fun add(s: String) { prop.get(dest).add(s) } + fun add(s: String) { + prop.get(dest).add(s) + } } -inline fun Iterable.findWithTransform(mappingPredicate: (T) -> Pair): R? { +inline fun Iterable.findWithTransform(mappingPredicate: (T) -> Pair): R? { for (element in this) { val (found, mapped) = mappingPredicate(element) if (found) return mapped @@ -120,7 +123,7 @@ inline fun Iterable.findWithTransform(mappingPredicate: (T) -> Pa } -fun Iterable.filterExtractProps(propMappers: List>, prefix: String, restParser: RestPropMapper<*,*>? = null) : Iterable { +fun Iterable.filterExtractProps(propMappers: List>, prefix: String, restParser: RestPropMapper<*, *>? = null): Iterable { val iter = iterator() val rest = arrayListOf() @@ -137,7 +140,7 @@ fun Iterable.filterExtractProps(propMappers: List>, pr propMapper != null -> { val optionLength = prefix.length() + matchingOption!!.length() when { - propMapper is BoolPropMapper<*,*> -> { + propMapper is BoolPropMapper<*, *> -> { if (param.length() > optionLength) throw IllegalArgumentException("Invalid switch option '$param', expecting $prefix$matchingOption without arguments") propMapper.apply("") @@ -166,19 +169,11 @@ fun Iterable.filterExtractProps(propMappers: List>, pr } -// TODO: find out how to create more generic variant using first constructor -//fun C.propsToParams() { -// val kc = C::class -// kc.constructors.first(). -//} - - - public interface OptionsGroup : Serializable { - public val mappers: List> + public val mappers: List> } -public fun Iterable.filterExtractProps(vararg groups: OptionsGroup, prefix: String) : Iterable = +public fun Iterable.filterExtractProps(vararg groups: OptionsGroup, prefix: String): Iterable = filterExtractProps(groups.flatMap { it.mappers }, prefix) @@ -189,13 +184,13 @@ public data class DaemonJVMOptions( public var jvmParams: MutableCollection = arrayListOf() ) : OptionsGroup { - override val mappers: List> - get() = listOf( StringPropMapper(this, ::maxMemory, listOf("Xmx"), mergeDelimiter = ""), - StringPropMapper(this, ::maxPermSize, listOf("XX:MaxPermSize"), mergeDelimiter = "="), - StringPropMapper(this, ::reservedCodeCacheSize, listOf("XX:ReservedCodeCacheSize"), mergeDelimiter = "="), - restMapper) + override val mappers: List> + get() = listOf(StringPropMapper(this, ::maxMemory, listOf("Xmx"), mergeDelimiter = ""), + StringPropMapper(this, ::maxPermSize, listOf("XX:MaxPermSize"), mergeDelimiter = "="), + StringPropMapper(this, ::reservedCodeCacheSize, listOf("XX:ReservedCodeCacheSize"), mergeDelimiter = "="), + restMapper) - val restMapper: RestPropMapper<*,*> + val restMapper: RestPropMapper<*, *> get() = RestPropMapper(this, ::jvmParams) } @@ -208,17 +203,17 @@ public data class DaemonOptions( ) : OptionsGroup { override val mappers: List> - get() = listOf( PropMapper(this, ::runFilesPath, fromString = { it.trim('"') }), - PropMapper(this, ::autoshutdownMemoryThreshold, fromString = { it.toLong() }, skipIf = { it == 0L }, mergeDelimiter = "="), - PropMapper(this, ::autoshutdownIdleSeconds, fromString = { it.toInt() }, skipIf = { it == 0 }, mergeDelimiter = "="), - NullablePropMapper(this, ::clientAliveFlagPath, fromString = { it }, toString = { "${it?.trim('\"','\'')}" }, mergeDelimiter = "=")) + get() = listOf(PropMapper(this, ::runFilesPath, fromString = { it.trim('"') }), + PropMapper(this, ::autoshutdownMemoryThreshold, fromString = { it.toLong() }, skipIf = { it == 0L }, mergeDelimiter = "="), + PropMapper(this, ::autoshutdownIdleSeconds, fromString = { it.toInt() }, skipIf = { it == 0 }, mergeDelimiter = "="), + NullablePropMapper(this, ::clientAliveFlagPath, fromString = { it }, toString = { "${it?.trim('\"', '\'')}" }, mergeDelimiter = "=")) } fun updateSingleFileDigest(file: File, md: MessageDigest) { DigestInputStream(file.inputStream(), md).use { val buf = ByteArray(1024) - while (it.read(buf) != -1) { } + while (it.read(buf) != -1) {} it.close() } } @@ -232,10 +227,10 @@ fun updateEntryDigest(entry: File, md: MessageDigest) { entry.isDirectory -> updateForAllClasses(entry, md) entry.isFile && - (entry.getName().endsWith(".class", ignoreCase = true) || - entry.getName().endsWith(".jar", ignoreCase = true)) + (entry.extension.equals("class", ignoreCase = true) || + entry.extension.equals("jar", ignoreCase = true)) -> updateSingleFileDigest(entry, md) - // else skip + // else skip } } @@ -250,22 +245,21 @@ jvmName("getFilesClasspathDigest_Strings") fun Iterable.getFilesClasspathDigest(): String = map { File(it) }.getFilesClasspathDigest() fun Iterable.distinctStringsDigest(): String = - MessageDigest.getInstance(COMPILER_ID_DIGEST) - .digest(this.distinct().sort().joinToString("").toByteArray()) - .joinToString("", transform = { "%02x".format(it) }) + MessageDigest.getInstance(COMPILER_ID_DIGEST) + .digest(this.distinct().sorted().joinToString("").toByteArray()) + .joinToString("", transform = { "%02x".format(it) }) public data class CompilerId( public var compilerClasspath: List = listOf(), public var compilerDigest: String = "", public var compilerVersion: String = "" - // TODO: checksum ) : OptionsGroup { override val mappers: List> - get() = listOf( PropMapper(this, ::compilerClasspath, toString = { it.joinToString(File.pathSeparator) }, fromString = { it.trim('"').split(File.pathSeparator)}), - StringPropMapper(this, ::compilerDigest), - StringPropMapper(this, ::compilerVersion)) + get() = listOf(PropMapper(this, ::compilerClasspath, toString = { it.joinToString(File.pathSeparator) }, fromString = { it.trim('"').split(File.pathSeparator) }), + StringPropMapper(this, ::compilerDigest), + StringPropMapper(this, ::compilerVersion)) public fun updateDigest() { compilerDigest = compilerClasspath.getFilesClasspathDigest() @@ -275,7 +269,6 @@ public data class CompilerId( public jvmStatic fun makeCompilerId(vararg paths: File): CompilerId = makeCompilerId(paths.asIterable()) public jvmStatic fun makeCompilerId(paths: Iterable): CompilerId = - // TODO consider reading version here CompilerId(compilerClasspath = paths.map { it.absolutePath }, compilerDigest = paths.getFilesClasspathDigest()) } } @@ -286,14 +279,14 @@ public fun isDaemonEnabled(): Boolean = System.getProperty(COMPILE_DAEMON_ENABLE public fun configureDaemonJVMOptions(opts: DaemonJVMOptions, inheritMemoryLimits: Boolean): DaemonJVMOptions { // note: sequence matters, explicit override in COMPILE_DAEMON_JVM_OPTIONS_PROPERTY should be done after inputArguments processing - if (inheritMemoryLimits) + if (inheritMemoryLimits) { ManagementFactory.getRuntimeMXBean().inputArguments.filterExtractProps(opts.mappers, "-") - + } System.getProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY)?.let { - opts.jvmParams.addAll( it.trim('"', '\'') - .split("(?) { + + log.info("Kotlin compiler daemon version " + (loadVersionFromResource() ?: "")) + log.info("daemon JVM args: " + ManagementFactory.getRuntimeMXBean().inputArguments.joinToString(" ")) + log.info("daemon args: " + args.joinToString(" ")) + + val compilerId = CompilerId() + val daemonOptions = DaemonOptions() + + try { + val filteredArgs = args.asIterable().filterExtractProps(compilerId, daemonOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) + + if (filteredArgs.any()) { + val helpLine = "usage: " + log.info(helpLine) + println(helpLine) + throw IllegalArgumentException("Unknown arguments: " + filteredArgs.joinToString(" ")) + } + + log.info("starting daemon") + + // TODO: find minimal set of permissions and restore security management + // note: may be not needed anymore since (hopefully) server is now loopback-only + // if (System.getSecurityManager() == null) + // System.setSecurityManager (RMISecurityManager()) + // + // setDaemonPpermissions(daemonOptions.port) + + val (registry, port) = createRegistry(COMPILE_DAEMON_FIND_PORT_ATTEMPTS) + val runFileDir = File(if (daemonOptions.runFilesPath.isBlank()) COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH else daemonOptions.runFilesPath) + runFileDir.mkdirs() + val runFile = File(runFileDir, + makeRunFilenameString(ts = "%tFT% 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) { + 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: " + e.getMessage()) + log.info("Exception" + e.getMessage()) + log.info(e.toString()) + // TODO consider exiting without throwing + throw e } - jvmStatic public fun main(args: Array) { + } - log.info("Kotlin compiler daemon version " + (loadVersionFromResource() ?: "")) - log.info("daemon JVM args: " + ManagementFactory.getRuntimeMXBean().inputArguments.joinToString(" ")) - log.info("daemon args: " + args.joinToString(" ")) + val random = Random() - val compilerId = CompilerId() - val daemonOptions = DaemonOptions() + private fun createRegistry(attempts: Int) : Pair { + 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 { - val filteredArgs = args.asIterable().filterExtractProps(compilerId, daemonOptions, prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) - - if (filteredArgs.any()) { - val helpLine = "usage: " - log.info(helpLine) - println(helpLine) - throw IllegalArgumentException("Unknown arguments: " + filteredArgs.joinToString(" ")) - } - - log.info("starting daemon") - - // TODO: find minimal set of permissions and restore security management - // note: may be not needed anymore since (hopefully) server is now loopback-only - // if (System.getSecurityManager() == null) - // System.setSecurityManager (RMISecurityManager()) - // - // setDaemonPpermissions(daemonOptions.port) - - val (registry, port) = createRegistry(COMPILE_DAEMON_FIND_PORT_ATTEMPTS) - val runFileDir = File(if (daemonOptions.runFilesPath.isBlank()) COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH else daemonOptions.runFilesPath) - runFileDir.mkdirs() - val runFile = File(runFileDir, - makeRunFilenameString(ts = "%tFT% 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) { - 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() - } - } + return Pair(LocateRegistry.createRegistry(port, clientLoopbackSocketFactory, serverLoopbackSocketFactory), port) } - catch (e: Exception) { - System.err.println("Exception: " + e.getMessage()) - log.info("Exception" + e.getMessage()) - log.info(e.toString()) - // TODO consider exiting without throwing - throw e + catch (e: RemoteException) { + // assuming that the port is already taken + lastException = e } - - } - - val random = Random() - - private fun createRegistry(attempts: Int) : Pair { - 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, clientLoopbackSocketFactory, serverLoopbackSocketFactory), port) - } - catch (e: RemoteException) { - // assuming that the port is already taken - lastException = e - } - } - throw IllegalStateException("Cannot find free port in $attempts attempts", lastException) } + throw IllegalStateException("Cannot find free port in $attempts attempts", lastException) } } \ No newline at end of file diff --git a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileServiceImpl.kt b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileServiceImpl.kt index 71854ea71f3..90e20a989c5 100644 --- a/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileServiceImpl.kt +++ b/compiler/rmi/rmi-server/src/org/jetbrains/kotlin/rmi/service/CompileServiceImpl.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jetbrains.kotlin.service +package org.jetbrains.kotlin.rmi.service import org.jetbrains.kotlin.cli.common.CLICompiler import org.jetbrains.kotlin.cli.common.ExitCode @@ -24,13 +24,12 @@ import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents import org.jetbrains.kotlin.modules.TargetId import org.jetbrains.kotlin.rmi.* -import org.jetbrains.kotlin.rmi.service.RemoteIncrementalCacheClient -import org.jetbrains.kotlin.rmi.service.RemoteOutputStreamClient import java.io.IOException import java.io.PrintStream import java.lang.management.ManagementFactory import java.lang.management.ThreadMXBean import java.net.URLClassLoader +import java.rmi.NoSuchObjectException import java.rmi.registry.Registry import java.rmi.server.UnicastRemoteObject import java.util.concurrent.TimeUnit @@ -47,7 +46,6 @@ class CompileServiceImpl>( val registry: Registry, val compiler: Compiler, val selfCompilerId: CompilerId, - val daemonOptions: DaemonOptions, port: Int ) : CompileService, UnicastRemoteObject() { @@ -120,7 +118,7 @@ class CompileServiceImpl>( // cleanup for the case of incorrect restart and many other situations UnicastRemoteObject.unexportObject(this, false) } - catch (e: java.rmi.NoSuchObjectException) { + catch (e: NoSuchObjectException) { // ignoring if object already exported } @@ -138,8 +136,8 @@ class CompileServiceImpl>( private fun doCompile(args: Array, compilerMessagesStreamProxy: RemoteOutputStream, serviceOutputStreamProxy: RemoteOutputStream, body: (PrintStream) -> ExitCode): Int = ifAlive { - val compilerMessagesStream = PrintStream( RemoteOutputStreamClient(compilerMessagesStreamProxy)) - val serviceOutputStream = PrintStream( RemoteOutputStreamClient(serviceOutputStreamProxy)) + val compilerMessagesStream = PrintStream(RemoteOutputStreamClient(compilerMessagesStreamProxy)) + val serviceOutputStream = PrintStream(RemoteOutputStreamClient(serviceOutputStreamProxy)) checkedCompile(args, serviceOutputStream) { val res = body( compilerMessagesStream).code _lastUsedSeconds = nowSeconds() @@ -176,8 +174,8 @@ class CompileServiceImpl>( return "" } - fun ThreadMXBean.threadCputime() = if (isCurrentThreadCpuTimeSupported()) currentThreadCpuTime else 0L - fun ThreadMXBean.threadUsertime() = if (isCurrentThreadCpuTimeSupported()) currentThreadUserTime else 0L + fun ThreadMXBean.threadCpuTime() = if (isCurrentThreadCpuTimeSupported) currentThreadCpuTime else 0L + fun ThreadMXBean.threadUserTime() = if (isCurrentThreadCpuTimeSupported) currentThreadUserTime else 0L fun checkedCompile(args: Array, serviceOut: PrintStream, body: () -> R): R { try { @@ -187,12 +185,12 @@ class CompileServiceImpl>( val threadMXBean: ThreadMXBean = ManagementFactory.getThreadMXBean() val startMem = usedMemory() / 1024 val startTime = System.nanoTime() - val startThreadTime = threadMXBean.threadCputime() - val startThreadUserTime = threadMXBean.threadUsertime() + val startThreadTime = threadMXBean.threadCpuTime() + val startThreadUserTime = threadMXBean.threadUserTime() val res = body() val endTime = System.nanoTime() - val endThreadTime = threadMXBean.threadCputime() - val endThreadUserTime = threadMXBean.threadUsertime() + val endThreadTime = threadMXBean.threadCpuTime() + val endThreadUserTime = threadMXBean.threadUserTime() val endMem = usedMemory() / 1024 log.info("Done with result " + res.toString()) val elapsed = TimeUnit.NANOSECONDS.toMillis(endTime - startTime) @@ -201,7 +199,7 @@ class CompileServiceImpl>( log.info("Elapsed time: $elapsed ms (thread user: $elapsedThreadUser ms sys: ${elapsedThread - elapsedThreadUser} ms)") log.info("Used memory: $endMem kb (${"%+d".format(endMem - startMem)} kb)") System.getProperty(COMPILE_DAEMON_REPORT_PERF_PROPERTY)?.let { - serviceOut.println("PERF: TOTAL: $elapsed ms (thread user: $elapsedThreadUser ms sys: ${elapsedThread - elapsedThreadUser} ms); memory: $endMem kb (${"%+d".format(endMem - startMem)} kb)") + serviceOut.println("PERF: Compile on daemon: $elapsed ms (thread user: $elapsedThreadUser ms sys: ${elapsedThread - elapsedThreadUser} ms); memory: $endMem kb (${"%+d".format(endMem - startMem)} kb)") } return res } @@ -214,12 +212,12 @@ class CompileServiceImpl>( fun ifAlive(body: () -> R): R = rwlock.read { if (!alive) throw IllegalStateException("Kotlin Compiler Service is not in alive state") - else body() + body() } fun ifAliveExclusive(body: () -> R): R = rwlock.write { if (!alive) throw IllegalStateException("Kotlin Compiler Service is not in alive state") - else body() + body() } // sometimes used for debugging diff --git a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt index 1868999d056..2a8c94f793a 100644 --- a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt @@ -35,8 +35,8 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() { data class CompilerResults(val resultCode: Int, val out: String) - val daemonOptions = DaemonOptions(runFilesPath = tmpdir.absolutePath) - val daemonJVMOptions = DaemonJVMOptions() + val daemonOptions by lazy { DaemonOptions(runFilesPath = tmpdir.absolutePath) } + val daemonJVMOptions by lazy { DaemonJVMOptions() } 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-reflect.jar")) } diff --git a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.java b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.java index e22c84941b6..3710979bd86 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.java +++ b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/KotlinCompilerRunner.java @@ -165,7 +165,7 @@ public class KotlinCompilerRunner { ArrayList daemonReportMessages = new ArrayList(); - CompileService daemon = KotlinCompilerClient.Companion.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, new DaemonReportingTargets(null, daemonReportMessages), true, true); + CompileService daemon = KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, new DaemonReportingTargets(null, daemonReportMessages), true, true); for (DaemonReportMessage msg: daemonReportMessages) { if (msg.getCategory() == DaemonReportCategory.EXCEPTION && daemon == null) { @@ -182,7 +182,7 @@ public class KotlinCompilerRunner { ByteArrayOutputStream compilerOut = new ByteArrayOutputStream(); ByteArrayOutputStream daemonOut = new ByteArrayOutputStream(); - Integer res = KotlinCompilerClient.Companion.incrementalCompile(daemon, argsArray, incrementalCaches, compilerOut, daemonOut); + Integer res = KotlinCompilerClient.incrementalCompile(daemon, argsArray, incrementalCaches, compilerOut, daemonOut); ProcessCompilerOutput(messageCollector, collector, compilerOut, res.toString()); BufferedReader reader = new BufferedReader(new StringReader(daemonOut.toString()));