Fixes after review, adding basic options parsing tests
This commit is contained in:
@@ -32,6 +32,8 @@ public object KotlinCompilerClient {
|
||||
val DAEMON_DEFAULT_STARTUP_TIMEOUT_MS = 10000L
|
||||
val DAEMON_CONNECT_CYCLE_ATTEMPTS = 3
|
||||
|
||||
val verboseReporting = System.getProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY) != null
|
||||
|
||||
|
||||
// TODO: remove jvmStatic after all use sites will switch to kotlin
|
||||
jvmStatic
|
||||
@@ -45,6 +47,7 @@ public object KotlinCompilerClient {
|
||||
|
||||
var attempts = 0
|
||||
var fileLock: FileBasedLock? = null
|
||||
var shutdonwnPerformed = false
|
||||
try {
|
||||
while (attempts++ < DAEMON_CONNECT_CYCLE_ATTEMPTS) {
|
||||
val service = tryFindDaemon(File(daemonOptions.runFilesPath), compilerId, reportingTargets)
|
||||
@@ -59,11 +62,12 @@ public object KotlinCompilerClient {
|
||||
service.shutdown()
|
||||
// TODO: find more reliable way
|
||||
Thread.sleep(1000)
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "daemon shut down correctly, restarting search")
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "daemon shut down correctly")
|
||||
shutdonwnPerformed = true
|
||||
}
|
||||
else {
|
||||
if (!autostart) return null
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "no suitable daemon found, starting a new one")
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, if (shutdonwnPerformed) "starting a new daemon" else "no suitable daemon found, starting a new one")
|
||||
}
|
||||
|
||||
if (fileLock == null || !fileLock.isLocked()) {
|
||||
@@ -75,7 +79,7 @@ public object KotlinCompilerClient {
|
||||
}
|
||||
else {
|
||||
startDaemon(compilerId, daemonJVMOptions, daemonOptions, reportingTargets)
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "daemon started, trying to resolve")
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "daemon started, trying to find it")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,20 +212,28 @@ public object KotlinCompilerClient {
|
||||
|
||||
// --- 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 String.extractPortFromRunFilename(digest: String): Int =
|
||||
makeRunFilenameString(timestamp = "[0-9TZ:\\.\\+-]+", digest = digest, port = "(\\d+)", escapeSequence = "\\").toRegex()
|
||||
.match(this)
|
||||
?.groups?.get(1)
|
||||
?.value?.toInt()
|
||||
?: 0
|
||||
|
||||
|
||||
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) }
|
||||
.map { Pair(it, it.name.extractPortFromRunFilename(classPathDigest)) }
|
||||
.filter { it.second != 0 }
|
||||
.map {
|
||||
assert(it.second > 0 && it.second < 0xffff)
|
||||
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
|
||||
@@ -240,9 +252,10 @@ public object KotlinCompilerClient {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun tryConnectToDaemon(port: Int, reportingTargets: DaemonReportingTargets): CompileService? {
|
||||
try {
|
||||
val daemon = LocateRegistry.getRegistry(loopbackAddrName, port)
|
||||
val daemon = LocateRegistry.getRegistry(LoopbackNetworkInterface.loopbackInetAddressName, port)
|
||||
?.lookup(COMPILER_SERVICE_RMI_NAME)
|
||||
if (daemon != null)
|
||||
return daemon as? CompileService ?:
|
||||
@@ -330,13 +343,15 @@ public object KotlinCompilerClient {
|
||||
|
||||
|
||||
class FileBasedLock(compilerId: CompilerId, daemonOptions: DaemonOptions) {
|
||||
|
||||
private val lockFile: File =
|
||||
File(daemonOptions.runFilesPath,
|
||||
makeRunFilenameString(ts = "lock",
|
||||
makeRunFilenameString(timestamp = "lock",
|
||||
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest(),
|
||||
port = "0"))
|
||||
|
||||
private var locked: Boolean = acquireLockFile(lockFile)
|
||||
private volatile var locked = acquireLockFile(lockFile)
|
||||
private val channel = if (locked) RandomAccessFile(lockFile, "rw").channel else null
|
||||
private val lock = channel?.lock()
|
||||
|
||||
public fun isLocked(): Boolean = locked
|
||||
|
||||
@@ -349,42 +364,26 @@ public object KotlinCompilerClient {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
val maxAttempts = COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_MS / COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_CHECK_MS + 1
|
||||
var attempt = 0L
|
||||
while (true) {
|
||||
try {
|
||||
if (lockFile.createNewFile()) break
|
||||
// attempt to delete if file is orphaned
|
||||
if (lockFile.delete() && lockFile.createNewFile()) break
|
||||
}
|
||||
catch (e: IOException) {} // Ignoring it - assuming it is another client process owning it
|
||||
if (lockFile.exists() && ++attempt >= maxAttempts)
|
||||
throw IOException("Timeout waiting the release of the lock file '${lockFile.absolutePath}")
|
||||
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()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Process.isAlive() =
|
||||
try {
|
||||
this.exitValue()
|
||||
false
|
||||
}
|
||||
catch (e: IllegalThreadStateException) {
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
public enum class DaemonReportCategory {
|
||||
DEBUG, INFO, EXCEPTION;
|
||||
}
|
||||
@@ -392,3 +391,13 @@ 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<DaemonReportMessage>? = null)
|
||||
|
||||
|
||||
internal fun Process.isAlive() =
|
||||
try {
|
||||
this.exitValue()
|
||||
false
|
||||
}
|
||||
catch (e: IllegalThreadStateException) {
|
||||
true
|
||||
}
|
||||
|
||||
+4
-4
@@ -18,15 +18,15 @@ package org.jetbrains.kotlin.rmi.kotlinr
|
||||
|
||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
|
||||
import org.jetbrains.kotlin.rmi.CompileService
|
||||
import org.jetbrains.kotlin.rmi.clientLoopbackSocketFactory
|
||||
import org.jetbrains.kotlin.rmi.serverLoopbackSocketFactory
|
||||
import org.jetbrains.kotlin.rmi.LoopbackNetworkInterface
|
||||
import org.jetbrains.kotlin.rmi.SOCKET_ANY_FREE_PORT
|
||||
import java.rmi.server.UnicastRemoteObject
|
||||
|
||||
|
||||
public class RemoteIncrementalCacheServer(val cache: IncrementalCache, port: Int = 0) : CompileService.RemoteIncrementalCache {
|
||||
public class RemoteIncrementalCacheServer(val cache: IncrementalCache, port: Int = SOCKET_ANY_FREE_PORT) : CompileService.RemoteIncrementalCache {
|
||||
|
||||
init {
|
||||
UnicastRemoteObject.exportObject(this, port, clientLoopbackSocketFactory, serverLoopbackSocketFactory)
|
||||
UnicastRemoteObject.exportObject(this, port, LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.serverLoopbackSocketFactory)
|
||||
}
|
||||
|
||||
override fun getObsoletePackageParts(): Collection<String> = cache.getObsoletePackageParts()
|
||||
|
||||
+4
-4
@@ -16,17 +16,17 @@
|
||||
|
||||
package org.jetbrains.kotlin.rmi.kotlinr
|
||||
|
||||
import org.jetbrains.kotlin.rmi.LoopbackNetworkInterface
|
||||
import org.jetbrains.kotlin.rmi.RemoteOutputStream
|
||||
import org.jetbrains.kotlin.rmi.clientLoopbackSocketFactory
|
||||
import org.jetbrains.kotlin.rmi.serverLoopbackSocketFactory
|
||||
import org.jetbrains.kotlin.rmi.SOCKET_ANY_FREE_PORT
|
||||
import java.io.OutputStream
|
||||
import java.rmi.server.UnicastRemoteObject
|
||||
|
||||
|
||||
class RemoteOutputStreamServer(val out: OutputStream, port: Int = 0) : RemoteOutputStream {
|
||||
class RemoteOutputStreamServer(val out: OutputStream, port: Int = SOCKET_ANY_FREE_PORT) : RemoteOutputStream {
|
||||
|
||||
init {
|
||||
UnicastRemoteObject.exportObject(this, port, clientLoopbackSocketFactory, serverLoopbackSocketFactory)
|
||||
UnicastRemoteObject.exportObject(this, port, LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.serverLoopbackSocketFactory)
|
||||
}
|
||||
|
||||
public fun disconnect() {
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.lang.management.ManagementFactory
|
||||
import java.security.DigestInputStream
|
||||
import java.security.MessageDigest
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.text.Regex
|
||||
|
||||
|
||||
public val COMPILER_JAR_NAME: String = "kotlin-compiler.jar"
|
||||
@@ -54,9 +53,7 @@ public val COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH: String get() =
|
||||
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()
|
||||
public fun makeRunFilenameString(timestamp: String, digest: String, port: String, escapeSequence: String = ""): String = "$COMPILE_DAEMON_DEFAULT_FILES_PREFIX$escapeSequence.$timestamp$escapeSequence.$digest$escapeSequence.$port$escapeSequence.run"
|
||||
|
||||
|
||||
open class PropMapper<C, V, P : KMutableProperty1<C, V>>(val dest: C,
|
||||
@@ -114,6 +111,8 @@ class RestPropMapper<C, P : KMutableProperty1<C, MutableCollection<String>>>(des
|
||||
}
|
||||
|
||||
|
||||
// helper function combining find with map, useful for the cases then there is a calculation performed in find, which is nice to return along with
|
||||
// found value; mappingPredicate should return the pair of boolean compare predicate result and transformation value, we want to get along with found value
|
||||
inline fun <T, R : Any> Iterable<T>.findWithTransform(mappingPredicate: (T) -> Pair<Boolean, R?>): R? {
|
||||
for (element in this) {
|
||||
val (found, mapped) = mappingPredicate(element)
|
||||
@@ -123,6 +122,9 @@ inline fun <T, R : Any> Iterable<T>.findWithTransform(mappingPredicate: (T) -> P
|
||||
}
|
||||
|
||||
|
||||
// filter-like function, takes list of propmappers, bound to properties of concrete objects, iterates over receiver, extract matching values via appropriate
|
||||
// mappers into bound properties; if restParser is given, adds all non-matching elements to it, otherwise return them as an iterable
|
||||
// note bound properties mutation!
|
||||
fun Iterable<String>.filterExtractProps(propMappers: List<PropMapper<*, *, *>>, prefix: String, restParser: RestPropMapper<*, *>? = null): Iterable<String> {
|
||||
|
||||
val iter = iterator()
|
||||
@@ -169,6 +171,9 @@ fun Iterable<String>.filterExtractProps(propMappers: List<PropMapper<*, *, *>>,
|
||||
}
|
||||
|
||||
|
||||
public fun String.trimQuotes() = trim('"','\'')
|
||||
|
||||
|
||||
public interface OptionsGroup : Serializable {
|
||||
public val mappers: List<PropMapper<*, *, *>>
|
||||
}
|
||||
@@ -203,10 +208,10 @@ public data class DaemonOptions(
|
||||
) : OptionsGroup {
|
||||
|
||||
override val mappers: List<PropMapper<*, *, *>>
|
||||
get() = listOf(PropMapper(this, ::runFilesPath, fromString = { it.trim('"') }),
|
||||
get() = listOf(PropMapper(this, ::runFilesPath, fromString = { it.trimQuotes() }),
|
||||
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 = "="))
|
||||
NullablePropMapper(this, ::clientAliveFlagPath, fromString = { it }, toString = { "${it?.trimQuotes()}" }, mergeDelimiter = "="))
|
||||
}
|
||||
|
||||
|
||||
@@ -257,7 +262,7 @@ public data class CompilerId(
|
||||
) : OptionsGroup {
|
||||
|
||||
override val mappers: List<PropMapper<*, *, *>>
|
||||
get() = listOf(PropMapper(this, ::compilerClasspath, toString = { it.joinToString(File.pathSeparator) }, fromString = { it.trim('"').split(File.pathSeparator) }),
|
||||
get() = listOf(PropMapper(this, ::compilerClasspath, toString = { it.joinToString(File.pathSeparator) }, fromString = { it.trimQuotes().split(File.pathSeparator) }),
|
||||
StringPropMapper(this, ::compilerDigest),
|
||||
StringPropMapper(this, ::compilerVersion))
|
||||
|
||||
@@ -283,10 +288,11 @@ public fun configureDaemonJVMOptions(opts: DaemonJVMOptions, inheritMemoryLimits
|
||||
ManagementFactory.getRuntimeMXBean().inputArguments.filterExtractProps(opts.mappers, "-")
|
||||
}
|
||||
System.getProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY)?.let {
|
||||
opts.jvmParams.addAll(it.trim('"', '\'')
|
||||
.split("(?<!\\\\),".toRegex())
|
||||
.map { it.replace("[\\\\](.)".toRegex(), "$1") }
|
||||
.filterExtractProps(opts.mappers, "-", opts.restMapper))
|
||||
opts.jvmParams.addAll(
|
||||
it.trimQuotes()
|
||||
.split("(?<!\\\\),".toRegex()) // using independent non-capturing group with negative lookahead zero length assertion to split only on non-escaped commas
|
||||
.map { it.replace("\\\\(.)".toRegex(), "$1") } // de-escaping characters escaped by backslash, straightforward, without exceptions
|
||||
.filterExtractProps(opts.mappers, "-", opts.restMapper))
|
||||
}
|
||||
|
||||
System.getProperty(COMPILE_DAEMON_REPORT_PERF_PROPERTY)?.let { opts.jvmParams.add("D" + COMPILE_DAEMON_REPORT_PERF_PROPERTY) }
|
||||
@@ -294,19 +300,21 @@ public fun configureDaemonJVMOptions(opts: DaemonJVMOptions, inheritMemoryLimits
|
||||
return opts
|
||||
}
|
||||
|
||||
|
||||
public fun configureDaemonJVMOptions(inheritMemoryLimits: Boolean): DaemonJVMOptions =
|
||||
configureDaemonJVMOptions(DaemonJVMOptions(), inheritMemoryLimits = inheritMemoryLimits)
|
||||
|
||||
|
||||
public fun configureDaemonOptions(opts: DaemonOptions): DaemonOptions {
|
||||
System.getProperty(COMPILE_DAEMON_OPTIONS_PROPERTY)?.let {
|
||||
val unrecognized = it.trim('"', '\'').split(",").filterExtractProps(opts.mappers, "")
|
||||
val unrecognized = it.trimQuotes().split(",").filterExtractProps(opts.mappers, "")
|
||||
if (unrecognized.any())
|
||||
throw IllegalArgumentException(
|
||||
"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.trim('"', '\'')
|
||||
val trimmed = it.trimQuotes()
|
||||
if (!trimmed.isBlank()) {
|
||||
opts.clientAliveFlagPath = trimmed
|
||||
}
|
||||
@@ -314,4 +322,5 @@ public fun configureDaemonOptions(opts: DaemonOptions): DaemonOptions {
|
||||
return opts
|
||||
}
|
||||
|
||||
|
||||
public fun configureDaemonOptions(): DaemonOptions = configureDaemonOptions(DaemonOptions())
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.rmi
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.Serializable
|
||||
import java.net.InetAddress
|
||||
import java.net.ServerSocket
|
||||
import java.net.Socket
|
||||
import java.rmi.server.RMIClientSocketFactory
|
||||
import java.rmi.server.RMIServerSocketFactory
|
||||
|
||||
|
||||
// TODO switch to InetAddress.getLoopbackAddress on java 7+
|
||||
val loopbackAddrName by lazy { if (java.net.InetAddress.getLocalHost() is java.net.Inet6Address) "::1" else "127.0.0.1" }
|
||||
val loopbackAddr by lazy { InetAddress.getByName(loopbackAddrName) }
|
||||
val serverLoopbackSocketFactory by lazy { ServerLoopbackSocketFactory() }
|
||||
val clientLoopbackSocketFactory by lazy { ClientLoopbackSocketFactory() }
|
||||
|
||||
|
||||
data class ServerLoopbackSocketFactory : RMIServerSocketFactory, Serializable {
|
||||
|
||||
throws(IOException::class)
|
||||
override fun createServerSocket(port: Int): ServerSocket = ServerSocket(port, 5, loopbackAddr)
|
||||
}
|
||||
|
||||
|
||||
data class ClientLoopbackSocketFactory : RMIClientSocketFactory, Serializable {
|
||||
|
||||
throws(IOException::class)
|
||||
override fun createSocket(host: String, port: Int): Socket = Socket(loopbackAddr, port)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.rmi
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.Serializable
|
||||
import java.net.InetAddress
|
||||
import java.net.ServerSocket
|
||||
import java.net.Socket
|
||||
import java.rmi.server.RMIClientSocketFactory
|
||||
import java.rmi.server.RMIServerSocketFactory
|
||||
|
||||
|
||||
public val SOCKET_ANY_FREE_PORT = 0
|
||||
|
||||
public object LoopbackNetworkInterface {
|
||||
|
||||
val IPV4_LOOPBACK_INET_ADDRESS = "127.0.0.1"
|
||||
val IPV6_LOOPBACK_INET_ADDRESS = "::1"
|
||||
|
||||
val SERVER_SOCKET_BACKLOG_SIZE = 5 // size of the requests queue for daemon services, so far seems that we don't need any big numbers here
|
||||
// but if we'll start getting "connection refused" errors, that could be the first place to try to fix it
|
||||
|
||||
public val serverLoopbackSocketFactory by lazy { ServerLoopbackSocketFactory() }
|
||||
public val clientLoopbackSocketFactory by lazy { ClientLoopbackSocketFactory() }
|
||||
|
||||
// TODO switch to InetAddress.getLoopbackAddress on java 7+
|
||||
public val loopbackInetAddressName by lazy {
|
||||
try {
|
||||
if (java.net.InetAddress.getLocalHost() is java.net.Inet6Address) IPV6_LOOPBACK_INET_ADDRESS else IPV4_LOOPBACK_INET_ADDRESS
|
||||
}
|
||||
catch (e: IOException) {
|
||||
// getLocalHost may fail for unknown reasons in some situations, the fallback is to assume IPv4 for now
|
||||
// TODO consider some other ways to detect default to IPv6 addresses in this case
|
||||
IPV4_LOOPBACK_INET_ADDRESS
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class ServerLoopbackSocketFactory : RMIServerSocketFactory, Serializable {
|
||||
|
||||
throws(IOException::class)
|
||||
override fun createServerSocket(port: Int): ServerSocket = ServerSocket(port, SERVER_SOCKET_BACKLOG_SIZE, InetAddress.getByName(loopbackInetAddressName))
|
||||
}
|
||||
|
||||
|
||||
data class ClientLoopbackSocketFactory : RMIClientSocketFactory, Serializable {
|
||||
|
||||
throws(IOException::class)
|
||||
override fun createSocket(host: String, port: Int): Socket = Socket(InetAddress.getByName(loopbackInetAddressName), port)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ public object CompileDaemon {
|
||||
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%<tT.%<tLZ".format(Calendar.getInstance(TimeZone.getTimeZone("Z"))),
|
||||
makeRunFilenameString(timestamp = "%tFT%<tT.%<tLZ".format(Calendar.getInstance(TimeZone.getTimeZone("Z"))),
|
||||
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest(),
|
||||
port = port.toString()))
|
||||
if (!runFile.createNewFile()) {
|
||||
@@ -190,7 +190,7 @@ public object CompileDaemon {
|
||||
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)
|
||||
return Pair(LocateRegistry.createRegistry(port, LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.serverLoopbackSocketFactory), port)
|
||||
}
|
||||
catch (e: RemoteException) {
|
||||
// assuming that the port is already taken
|
||||
|
||||
@@ -122,7 +122,7 @@ class CompileServiceImpl<Compiler: CLICompiler<*>>(
|
||||
// ignoring if object already exported
|
||||
}
|
||||
|
||||
val stub = UnicastRemoteObject.exportObject(this, port, clientLoopbackSocketFactory, serverLoopbackSocketFactory) as CompileService
|
||||
val stub = UnicastRemoteObject.exportObject(this, port, LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.serverLoopbackSocketFactory) as CompileService
|
||||
registry.rebind (COMPILER_SERVICE_RMI_NAME, stub);
|
||||
alive = true
|
||||
}
|
||||
|
||||
@@ -19,10 +19,7 @@ package org.jetbrains.kotlin.daemon
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.cli.CliBaseTest
|
||||
import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase
|
||||
import org.jetbrains.kotlin.rmi.CompileService
|
||||
import org.jetbrains.kotlin.rmi.CompilerId
|
||||
import org.jetbrains.kotlin.rmi.DaemonJVMOptions
|
||||
import org.jetbrains.kotlin.rmi.DaemonOptions
|
||||
import org.jetbrains.kotlin.rmi.*
|
||||
import org.jetbrains.kotlin.rmi.kotlinr.DaemonReportingTargets
|
||||
import org.jetbrains.kotlin.rmi.kotlinr.KotlinCompilerClient
|
||||
import org.jetbrains.kotlin.test.JetTestUtils
|
||||
@@ -75,4 +72,33 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
runDaemonCompilerTwice("hello.compile", "-include-runtime", File(getTestBaseDir(), "hello.kt").absolutePath, "-d", jar)
|
||||
run("hello.run", "-cp", jar, "Hello.HelloPackage")
|
||||
}
|
||||
|
||||
public fun testDaemonJvmOptionsParsing() {
|
||||
val backupJvmOptions = System.getProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY)
|
||||
try {
|
||||
System.setProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY, "-aaa,-bbb\\,ccc,-ddd,-Xmx200m,-XX:MaxPermSize=10k,-XX:ReservedCodeCacheSize=100,-xxx\\,yyy")
|
||||
val opts = configureDaemonJVMOptions(inheritMemoryLimits = false)
|
||||
TestCase.assertEquals("200m", opts.maxMemory)
|
||||
TestCase.assertEquals("10k", opts.maxPermSize)
|
||||
TestCase.assertEquals("100", opts.reservedCodeCacheSize)
|
||||
TestCase.assertEquals(arrayListOf("aaa", "bbb,ccc", "ddd", "xxx,yyy"), opts.jvmParams)
|
||||
}
|
||||
finally {
|
||||
backupJvmOptions?.let { System.setProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY, it) }
|
||||
}
|
||||
}
|
||||
|
||||
public fun testDaemonOptionsParsing() {
|
||||
val backupJvmOptions = System.getProperty(COMPILE_DAEMON_OPTIONS_PROPERTY)
|
||||
try {
|
||||
System.setProperty(COMPILE_DAEMON_OPTIONS_PROPERTY, "runFilesPath=abcd,clientAliveFlagPath=efgh,autoshutdownIdleSeconds=1111")
|
||||
val opts = configureDaemonOptions()
|
||||
TestCase.assertEquals("abcd", opts.runFilesPath)
|
||||
TestCase.assertEquals("efgh", opts.clientAliveFlagPath)
|
||||
TestCase.assertEquals(1111, opts.autoshutdownIdleSeconds)
|
||||
}
|
||||
finally {
|
||||
backupJvmOptions?.let { System.setProperty(COMPILE_DAEMON_OPTIONS_PROPERTY, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user