Moving daemon files, renaming namespaces, modules and jar

This commit is contained in:
Ilya Chernikov
2015-11-27 18:54:09 +01:00
parent 312e931582
commit c76bec51a0
35 changed files with 166 additions and 158 deletions
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-runtime" level="project" />
<orderEntry type="module" module-name="cli-common" />
<orderEntry type="module" module-name="frontend.java" />
<orderEntry type="module" module-name="util" />
</component>
</module>
@@ -0,0 +1,83 @@
/*
* 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.daemon.common
import java.io.File
import java.rmi.ConnectException
import java.rmi.registry.LocateRegistry
internal val MAX_PORT_NUMBER = 0xffff
enum class DaemonReportCategory {
DEBUG, INFO, EXCEPTION;
}
fun makeRunFilenameString(timestamp: String, digest: String, port: String, escapeSequence: String = ""): String =
"${COMPILE_DAEMON_DEFAULT_FILES_PREFIX}$escapeSequence.$timestamp$escapeSequence.$digest$escapeSequence.$port$escapeSequence.run"
fun makePortFromRunFilenameExtractor(digest: String): (String) -> Int? {
val regex = makeRunFilenameString(timestamp = "[0-9TZ:\\.\\+-]+", digest = digest, port = "(\\d+)", escapeSequence = "\\").toRegex()
return { regex.find(it)
?.groups?.get(1)
?.value?.toInt()
}
}
fun walkDaemons(registryDir: File,
compilerId: CompilerId,
filter: (File, Int) -> Boolean = { f, p -> true },
report: (DaemonReportCategory, String) -> Unit = { cat, msg -> }
): Sequence<CompileService> {
val classPathDigest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest().toHexString()
val portExtractor = makePortFromRunFilenameExtractor(classPathDigest)
return registryDir.walk()
.map { Pair(it, portExtractor(it.name)) }
.filter { it.second != null && filter(it.first, it.second!!) }
.map {
assert(it.second!! > 0 && it.second!! < MAX_PORT_NUMBER)
report(DaemonReportCategory.DEBUG, "found daemon on port ${it.second}, trying to connect")
val daemon = tryConnectToDaemon(it.second!!, report)
// cleaning orphaned file; note: daemon should shut itself down if it detects that the run file is deleted
if (daemon == null && !it.first.delete()) {
report(DaemonReportCategory.INFO, "WARNING: unable to delete seemingly orphaned file '${it.first.absolutePath}', cleanup recommended")
}
daemon
}
.filterNotNull()
}
private inline fun tryConnectToDaemon(port: Int, report: (DaemonReportCategory, String) -> Unit): CompileService? {
try {
val daemon = LocateRegistry.getRegistry(LoopbackNetworkInterface.loopbackInetAddressName, 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}")
report(DaemonReportCategory.EXCEPTION, "daemon not found")
}
catch (e: ConnectException) {
report(DaemonReportCategory.EXCEPTION, "cannot connect to registry: " + (e.cause?.message ?: e.message ?: "unknown exception"))
// ignoring it - processing below
}
return null
}
@@ -0,0 +1,125 @@
/*
* 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.daemon.common
import java.io.Serializable
import java.rmi.Remote
import java.rmi.RemoteException
interface CompileService : Remote {
enum class OutputFormat : Serializable {
PLAIN,
XML
}
enum class TargetPlatform : Serializable {
JVM,
JS
}
companion object {
val NO_SESSION: Int = 0
}
sealed class CallResult<out R> : Serializable {
class Good<R>(val result: R) : CallResult<R>() {
override fun get(): R = result
override fun equals(other: Any?): Boolean = other is Good<*> && this.result == other.result
override fun hashCode(): Int = this.javaClass.hashCode() + (result?.hashCode() ?: 1)
}
class Ok : CallResult<Nothing>() {
override fun get(): Nothing = throw IllegalStateException("Gey is inapplicable to Ok call result")
override fun equals(other: Any?): Boolean = other is Ok
override fun hashCode(): Int = this.javaClass.hashCode() + 1 // avoiding clash with the hash of class itself
}
class Dying : CallResult<Nothing>() {
override fun get(): Nothing = throw IllegalStateException("Service is dying")
override fun equals(other: Any?): Boolean = other is Dying
override fun hashCode(): Int = this.javaClass.hashCode() + 1 // see comment to Ok.hashCode
}
class Error(val message: String) : CallResult<Nothing>() {
override fun get(): Nothing = throw Exception(message)
override fun equals(other: Any?): Boolean = other is Error && this.message == other.message
override fun hashCode(): Int = this.javaClass.hashCode() + message.hashCode()
}
val isGood: Boolean get() = this is Good<*>
abstract fun get(): R
}
// TODO: remove!
@Throws(RemoteException::class)
fun checkCompilerId(expectedCompilerId: CompilerId): Boolean
@Throws(RemoteException::class)
fun getUsedMemory(): CallResult<Long>
@Throws(RemoteException::class)
fun getDaemonOptions(): CallResult<DaemonOptions>
@Throws(RemoteException::class)
fun getDaemonJVMOptions(): CallResult<DaemonJVMOptions>
@Throws(RemoteException::class)
fun registerClient(aliveFlagPath: String?): CallResult<Nothing>
// TODO: consider adding another client alive checking mechanism, e.g. socket/port
@Throws(RemoteException::class)
fun getClients(): CallResult<List<String>>
@Throws(RemoteException::class)
fun leaseCompileSession(aliveFlagPath: String?): CallResult<Int>
@Throws(RemoteException::class)
fun releaseCompileSession(sessionId: Int): CallResult<Nothing>
@Throws(RemoteException::class)
fun shutdown(): CallResult<Nothing>
@Throws(RemoteException::class)
fun scheduleShutdown(graceful: Boolean): CallResult<Boolean>
// TODO: consider adding async version of shutdown and release
@Throws(RemoteException::class)
fun remoteCompile(
sessionId: Int,
targetPlatform: TargetPlatform,
args: Array<out String>,
servicesFacade: CompilerCallbackServicesFacade,
compilerOutputStream: RemoteOutputStream,
outputFormat: OutputFormat,
serviceOutputStream: RemoteOutputStream,
operationsTracer: RemoteOperationsTracer?
): CallResult<Int>
@Throws(RemoteException::class)
fun remoteIncrementalCompile(
sessionId: Int,
targetPlatform: TargetPlatform,
args: Array<out String>,
servicesFacade: CompilerCallbackServicesFacade,
compilerOutputStream: RemoteOutputStream,
compilerOutputFormat: OutputFormat,
serviceOutputStream: RemoteOutputStream,
operationsTracer: RemoteOperationsTracer?
): CallResult<Int>
}
@@ -0,0 +1,87 @@
/*
* 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.daemon.common
import org.jetbrains.kotlin.incremental.components.LookupInfo
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
import org.jetbrains.kotlin.modules.TargetId
import java.rmi.Remote
import java.rmi.RemoteException
/**
* common facade for compiler services exposed from client process (e.g. JPS) to the compiler running on daemon
* the reason for having common facade is attempt to reduce number of connections between client and daemon
* Note: non-standard naming convention used to denote combining several entities in one facade - prefix <entityName>_ is used for every function belonging to the entity
*/
interface CompilerCallbackServicesFacade : Remote {
@Throws(RemoteException::class)
fun hasIncrementalCaches(): Boolean
@Throws(RemoteException::class)
fun hasLookupTracker(): Boolean
@Throws(RemoteException::class)
fun hasCompilationCanceledStatus(): Boolean
// ----------------------------------------------------
// IncrementalCache
@Throws(RemoteException::class)
fun incrementalCache_getObsoletePackageParts(target: TargetId): Collection<String>
@Throws(RemoteException::class)
fun incrementalCache_getObsoleteMultifileClassFacades(target: TargetId): Collection<String>
@Throws(RemoteException::class)
fun incrementalCache_getMultifileFacade(target: TargetId, partInternalName: String): String?
@Throws(RemoteException::class)
fun incrementalCache_getPackagePartData(target: TargetId, fqName: String): JvmPackagePartProto?
@Throws(RemoteException::class)
fun incrementalCache_getModuleMappingData(target: TargetId): ByteArray?
@Throws(RemoteException::class)
fun incrementalCache_registerInline(target: TargetId, fromPath: String, jvmSignature: String, toPath: String)
@Throws(RemoteException::class)
fun incrementalCache_getClassFilePath(target: TargetId, internalClassName: String): String
@Throws(RemoteException::class)
fun incrementalCache_close(target: TargetId)
@Throws(RemoteException::class)
fun incrementalCache_getMultifileFacadeParts(target: TargetId, internalName: String): Collection<String>?
// ----------------------------------------------------
// LookupTracker
@Throws(RemoteException::class)
fun lookupTracker_requiresPosition(): Boolean
@Throws(RemoteException::class)
fun lookupTracker_record(lookups: Collection<LookupInfo>)
@Throws(RemoteException::class)
fun lookupTracker_isDoNothing(): Boolean
// ----------------------------------------------------
// CompilationCanceledStatus
@Throws(RemoteException::class)
fun compilationCanceledStatus_checkCanceled(): Unit
}
@@ -0,0 +1,357 @@
/*
* 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.daemon.common
import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY
import java.io.File
import java.io.Serializable
import java.lang.management.ManagementFactory
import java.security.MessageDigest
import java.util.*
import kotlin.reflect.KMutableProperty1
val COMPILER_JAR_NAME: String = "kotlin-compiler.jar"
val COMPILER_SERVICE_RMI_NAME: String = "KotlinJvmCompilerService"
val COMPILER_DAEMON_CLASS_FQN: String = "org.jetbrains.kotlin.daemon.CompileDaemon"
val COMPILE_DAEMON_FIND_PORT_ATTEMPTS: Int = 10
val COMPILE_DAEMON_PORTS_RANGE_START: Int = 17001
val COMPILE_DAEMON_PORTS_RANGE_END: Int = 18000
val COMPILE_DAEMON_ENABLED_PROPERTY: String = "kotlin.daemon.enabled"
val COMPILE_DAEMON_JVM_OPTIONS_PROPERTY: String = "kotlin.daemon.jvm.options"
val COMPILE_DAEMON_OPTIONS_PROPERTY: String = "kotlin.daemon.options"
val COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY: String = "kotlin.daemon.client.alive.path"
val COMPILE_DAEMON_LOG_PATH_PROPERTY: String = "kotlin.daemon.log.path"
val COMPILE_DAEMON_REPORT_PERF_PROPERTY: String = "kotlin.daemon.perf"
val COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY: String = "kotlin.daemon.verbose"
val COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX: String = "--daemon-"
val COMPILE_DAEMON_STARTUP_TIMEOUT_PROPERTY: String = "kotlin.daemon.startup.timeout"
val COMPILE_DAEMON_DEFAULT_FILES_PREFIX: String = "kotlin-daemon"
val COMPILE_DAEMON_TIMEOUT_INFINITE_S: Int = 0
val COMPILE_DAEMON_DEFAULT_IDLE_TIMEOUT_S: Int = 7200 // 2 hours
val COMPILE_DAEMON_DEFAULT_UNUSED_TIMEOUT_S: Int = 60
val COMPILE_DAEMON_DEFAULT_SHUTDOWN_DELAY_MS: Long = 1000L // 1 sec
val COMPILE_DAEMON_MEMORY_THRESHOLD_INFINITE: Long = 0L
val COMPILE_DAEMON_FORCE_SHUTDOWN_DEFAULT_TIMEOUT_MS: Long = 10000L // 10 secs
val COMPILE_DAEMON_TIMEOUT_INFINITE_MS: Long = 0L
val COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH: String get() =
FileSystem.getRuntimeStateFilesPath("kotlin", "daemon")
val CLASSPATH_ID_DIGEST = "MD5"
open class PropMapper<C, V, P : KMutableProperty1<C, V>>(val dest: C,
val prop: P,
val names: List<String> = 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<String> =
when {
skipIf(prop.get(dest)) -> listOf<String>()
mergeDelimiter != null -> listOf(listOfNotNull(prefix + names.first(), toString(prop.get(dest))).joinToString(mergeDelimiter))
else -> listOfNotNull(prefix + names.first(), toString(prop.get(dest)))
}
open fun apply(s: String) = prop.set(dest, fromString(s))
}
class NullablePropMapper<C, V : Any?, P : KMutableProperty1<C, V>>(dest: C,
prop: P,
names: List<String> = listOf(),
fromString: ((String) -> V),
toString: ((V) -> String?) = { it.toString() },
skipIf: ((V) -> Boolean) = { it == null },
mergeDelimiter: String? = null)
: PropMapper<C, V, P>(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name),
fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter)
class StringPropMapper<C, P : KMutableProperty1<C, String>>(dest: C,
prop: P,
names: List<String> = listOf(),
fromString: ((String) -> String) = { it },
toString: ((String) -> String?) = { it.toString() },
skipIf: ((String) -> Boolean) = { it.isEmpty() },
mergeDelimiter: String? = null)
: PropMapper<C, String, P>(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name),
fromString = fromString, toString = toString, skipIf = skipIf, mergeDelimiter = mergeDelimiter)
class BoolPropMapper<C, P : KMutableProperty1<C, Boolean>>(dest: C, prop: P, names: List<String> = listOf())
: PropMapper<C, Boolean, P>(dest = dest, prop = prop, names = if (names.any()) names else listOf(prop.name),
fromString = { true }, toString = { null }, skipIf = { !prop.get(dest) })
class RestPropMapper<C, P : KMutableProperty1<C, MutableCollection<String>>>(dest: C, prop: P)
: PropMapper<C, MutableCollection<String>, P>(dest = dest, prop = prop, toString = { null }, fromString = { arrayListOf() }) {
override fun toArgs(prefix: String): List<String> = prop.get(dest).map { prefix + it }
override fun apply(s: String) = add(s)
fun add(s: String) {
prop.get(dest).add(s)
}
}
// 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)
if (found) return mapped
}
return null
}
// 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()
val rest = arrayListOf<String>()
while (iter.hasNext()) {
val param = iter.next()
val (propMapper, matchingOption) = propMappers.findWithTransform { mapper ->
mapper.names
.firstOrNull { param.startsWith(prefix + it) }
.let { Pair(it != null, Pair(mapper, it)) }
} ?: Pair(null, null)
when {
propMapper != null -> {
val optionLength = prefix.length + matchingOption!!.length
when {
propMapper is BoolPropMapper<*, *> -> {
if (param.length > optionLength)
throw IllegalArgumentException("Invalid switch option '$param', expecting $prefix$matchingOption without arguments")
propMapper.apply("")
}
param.length > optionLength ->
if (param[optionLength] != '=') {
if (propMapper.mergeDelimiter == null)
throw IllegalArgumentException("Invalid option syntax '$param', expecting $prefix$matchingOption[= ]<arg>")
propMapper.apply(param.substring(optionLength))
}
else {
propMapper.apply(param.substring(optionLength + 1))
}
else -> {
if (!iter.hasNext()) throw IllegalArgumentException("Expecting argument for the option $prefix$matchingOption")
propMapper.apply(iter.next())
}
}
}
restParser != null && param.startsWith(prefix) ->
restParser.add(param.removePrefix(prefix))
else -> rest.add(param)
}
}
return rest
}
fun String.trimQuotes() = trim('"','\'')
interface OptionsGroup : Serializable {
val mappers: List<PropMapper<*, *, *>>
}
fun Iterable<String>.filterExtractProps(vararg groups: OptionsGroup, prefix: String): Iterable<String> =
filterExtractProps(groups.flatMap { it.mappers }, prefix)
data class DaemonJVMOptions(
var maxMemory: String = "",
var maxPermSize: String = "",
var reservedCodeCacheSize: String = "",
var jvmParams: MutableCollection<String> = arrayListOf()
) : OptionsGroup {
override val mappers: List<PropMapper<*, *, *>>
get() = listOf(StringPropMapper(this, DaemonJVMOptions::maxMemory, listOf("Xmx"), mergeDelimiter = ""),
StringPropMapper(this, DaemonJVMOptions::maxPermSize, listOf("XX:MaxPermSize"), mergeDelimiter = "="),
StringPropMapper(this, DaemonJVMOptions::reservedCodeCacheSize, listOf("XX:ReservedCodeCacheSize"), mergeDelimiter = "="),
restMapper)
val restMapper: RestPropMapper<*, *>
get() = RestPropMapper(this, DaemonJVMOptions::jvmParams)
}
data class DaemonOptions(
var runFilesPath: String = COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH,
var autoshutdownMemoryThreshold: Long = COMPILE_DAEMON_MEMORY_THRESHOLD_INFINITE,
var autoshutdownIdleSeconds: Int = COMPILE_DAEMON_DEFAULT_IDLE_TIMEOUT_S,
var autoshutdownUnusedSeconds: Int = COMPILE_DAEMON_DEFAULT_UNUSED_TIMEOUT_S,
var shutdownDelayMilliseconds: Long = COMPILE_DAEMON_DEFAULT_SHUTDOWN_DELAY_MS,
var forceShutdownTimeoutMilliseconds: Long = COMPILE_DAEMON_FORCE_SHUTDOWN_DEFAULT_TIMEOUT_MS,
var verbose: Boolean = false,
var reportPerf: Boolean = false
) : OptionsGroup {
override val mappers: List<PropMapper<*, *, *>>
get() = listOf(PropMapper(this, DaemonOptions::runFilesPath, fromString = { it.trimQuotes() }),
PropMapper(this, DaemonOptions::autoshutdownMemoryThreshold, fromString = { it.toLong() }, skipIf = { it == 0L }, mergeDelimiter = "="),
// TODO: implement "use default" value without specifying default, so if client and server uses different defaults, it should not lead to many params in the cmd line; use 0 for it and used different val for infinite
PropMapper(this, DaemonOptions::autoshutdownIdleSeconds, fromString = { it.toInt() }, skipIf = { it == 0 }, mergeDelimiter = "="),
PropMapper(this, DaemonOptions::autoshutdownUnusedSeconds, fromString = { it.toInt() }, skipIf = { it == COMPILE_DAEMON_DEFAULT_UNUSED_TIMEOUT_S }, mergeDelimiter = "="),
PropMapper(this, DaemonOptions::shutdownDelayMilliseconds, fromString = { it.toLong() }, skipIf = { it == COMPILE_DAEMON_DEFAULT_SHUTDOWN_DELAY_MS }, mergeDelimiter = "="),
PropMapper(this, DaemonOptions::forceShutdownTimeoutMilliseconds, fromString = { it.toLong() }, skipIf = { it == COMPILE_DAEMON_FORCE_SHUTDOWN_DEFAULT_TIMEOUT_MS }, mergeDelimiter = "="),
BoolPropMapper(this, DaemonOptions::verbose),
BoolPropMapper(this, DaemonOptions::reportPerf))
}
// TODO: consider implementing generic approach to it or may be replace getters with ones returning default if necessary
val DaemonOptions.runFilesPathOrDefault: String
get() = if (runFilesPath.isBlank()) COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH else runFilesPath
fun Iterable<String>.distinctStringsDigest(): ByteArray =
MessageDigest.getInstance(CLASSPATH_ID_DIGEST)
.digest(this.distinct().sorted().joinToString("").toByteArray())
fun ByteArray.toHexString(): String = joinToString("", transform = { "%02x".format(it) })
data class CompilerId(
var compilerClasspath: List<String> = listOf(),
var compilerVersion: String = ""
) : OptionsGroup {
override val mappers: List<PropMapper<*, *, *>>
get() = listOf(PropMapper(this, CompilerId::compilerClasspath, toString = { it.joinToString(File.pathSeparator) }, fromString = { it.trimQuotes().split(File.pathSeparator) }),
StringPropMapper(this, CompilerId::compilerVersion))
companion object {
@JvmStatic
fun makeCompilerId(vararg paths: File): CompilerId = makeCompilerId(paths.asIterable())
@JvmStatic
fun makeCompilerId(paths: Iterable<File>): CompilerId =
CompilerId(compilerClasspath = paths.map { it.absolutePath })
}
}
fun isDaemonEnabled(): Boolean = System.getProperty(COMPILE_DAEMON_ENABLED_PROPERTY) != null
fun configureDaemonJVMOptions(opts: DaemonJVMOptions,
vararg additionalParams: String,
inheritMemoryLimits: Boolean,
inheritAdditionalProperties: Boolean
): DaemonJVMOptions {
// note: sequence matters, explicit override in COMPILE_DAEMON_JVM_OPTIONS_PROPERTY should be done after inputArguments processing
if (inheritMemoryLimits) {
ManagementFactory.getRuntimeMXBean().inputArguments.filterExtractProps(opts.mappers, "-")
}
System.getProperty(COMPILE_DAEMON_JVM_OPTIONS_PROPERTY)?.let {
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))
}
opts.jvmParams.addAll(additionalParams)
if (inheritAdditionalProperties) {
System.getProperty(COMPILE_DAEMON_LOG_PATH_PROPERTY)?.let { opts.jvmParams.add("D${COMPILE_DAEMON_LOG_PATH_PROPERTY}=\"$it\"") }
System.getProperty(KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY)?.let { opts.jvmParams.add("D${KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY}") }
}
return opts
}
fun configureDaemonJVMOptions(vararg additionalParams: String,
inheritMemoryLimits: Boolean,
inheritAdditionalProperties: Boolean
): DaemonJVMOptions =
configureDaemonJVMOptions(DaemonJVMOptions(),
additionalParams = *additionalParams,
inheritMemoryLimits = inheritMemoryLimits,
inheritAdditionalProperties = inheritAdditionalProperties)
fun configureDaemonOptions(opts: DaemonOptions): DaemonOptions {
System.getProperty(COMPILE_DAEMON_OPTIONS_PROPERTY)?.let {
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_VERBOSE_REPORT_PROPERTY)?.let { opts.verbose = true }
System.getProperty(COMPILE_DAEMON_REPORT_PERF_PROPERTY)?.let { opts.reportPerf = true }
return opts
}
fun configureDaemonOptions(): DaemonOptions = configureDaemonOptions(DaemonOptions())
private val humanizedMemorySizeRegex = "(\\d+)([kmg]?)".toRegex()
private fun String.memToBytes(): Long? =
humanizedMemorySizeRegex
.matchEntire(this.trim().toLowerCase())
?.groups?.let { match ->
match[1]?.value?.let {
it.toLong() *
when (match[2]?.value) {
"k" -> 1 shl 10
"m" -> 1 shl 20
"g" -> 1 shl 30
else -> 1
}
}
}
private val daemonJVMOptionsMemoryProps =
listOf(DaemonJVMOptions::maxMemory, DaemonJVMOptions::maxPermSize, DaemonJVMOptions::reservedCodeCacheSize)
infix fun DaemonJVMOptions.memorywiseFitsInto(other: DaemonJVMOptions): Boolean =
daemonJVMOptionsMemoryProps
.all { (it.get(this).memToBytes() ?: 0) <= (it.get(other).memToBytes() ?: 0) }
fun compareDaemonJVMOptionsMemory(left: DaemonJVMOptions, right: DaemonJVMOptions): Int {
val props = daemonJVMOptionsMemoryProps.map { Pair(it.get(left).memToBytes() ?: 0, it.get(right).memToBytes() ?: 0) }
return when {
props.all { it.first == it.second } -> 0
props.all { it.first <= it.second } -> -1
props.all { it.first >= it.second } -> 1
else -> 0
}
}
class DaemonJVMOptionsMemoryComparator : Comparator<DaemonJVMOptions> {
override fun compare(left: DaemonJVMOptions, right: DaemonJVMOptions): Int = compareDaemonJVMOptionsMemory(left, right)
}
fun DaemonJVMOptions.updateMemoryUpperBounds(other: DaemonJVMOptions): DaemonJVMOptions {
daemonJVMOptionsMemoryProps
.forEach { if ((it.get(this).memToBytes() ?: 0) < (it.get(other).memToBytes() ?: 0)) it.set(this, it.get(other)) }
return this
}
@@ -0,0 +1,84 @@
/*
* 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.daemon.common
import java.io.File
enum class OSKind {
Windows,
OSX,
Unix,
Unknown;
companion object {
val current: OSKind = System.getProperty("os.name").toLowerCase().let {
when {
// partly taken from http://www.code4copy.com/java/post/detecting-os-type-in-java
it.startsWith("windows") -> Windows
it.startsWith("mac os") -> OSX
it.contains("unix") -> Unix
it.startsWith("linux") -> Unix
it.contains("bsd") -> Unix
it.startsWith("irix") -> Unix
it.startsWith("mpe/ix") -> Unix
it.startsWith("aix") -> Unix
it.startsWith("hp-ux") -> Unix
it.startsWith("sunos") -> Unix
it.startsWith("sun os") -> Unix
it.startsWith("solaris") -> Unix
else -> Unknown
}
}
}
}
private fun String?.orDefault(v: String): String =
if (this == null || this.isBlank()) v else this
// Note links to OS recommendations for storing various kinds of files
// Windows: http://www.microsoft.com/security/portal/mmpc/shared/variables.aspx
// unix (freedesktop): http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
// OS X: https://developer.apple.com/library/mac/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html
object FileSystem {
val userHomePath: String get() = System.getProperty("user.home")
val tempPath: String get() = System.getProperty("java.io.tmpdir")
val logFilesPath: String get() = tempPath
val runtimeStateFilesBasePath: String get() = when (OSKind.current) {
OSKind.Windows -> System.getenv("LOCALAPPDATA").orDefault(tempPath)
OSKind.OSX -> userHomePath + "/Library/Application Support"
OSKind.Unix -> System.getenv("XDG_DATA_HOME").orDefault(userHomePath + "/.local/share")
OSKind.Unknown -> tempPath
}
fun getRuntimeStateFilesPath(vararg names: String): String {
assert(names.any())
val base = File(runtimeStateFilesBasePath)
// if base is not suitable, take home dir as a base and ensure the first name is prefixed with "." -
// this will work ok as a fallback solution on most systems
val dir = if (base.exists() && base.isDirectory) names.fold(base, { r, v -> File(r, v) })
else names.drop(1)
.fold(File(userHomePath, names.first().let { if (it.startsWith(".")) it else ".$it" }),
{ r, v -> File(r, v) })
return if ((dir.exists() && dir.isDirectory) || dir.mkdirs()) dir.absolutePath
else tempPath
}
}
@@ -0,0 +1,98 @@
/*
* 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.daemon.common
import java.io.IOException
import java.io.Serializable
import java.net.Inet6Address
import java.net.InetAddress
import java.net.ServerSocket
import java.net.Socket
import java.rmi.RemoteException
import java.rmi.registry.LocateRegistry
import java.rmi.registry.Registry
import java.rmi.server.RMIClientSocketFactory
import java.rmi.server.RMIServerSocketFactory
import java.util.*
val SOCKET_ANY_FREE_PORT = 0
object LoopbackNetworkInterface {
val IPV4_LOOPBACK_INET_ADDRESS = "127.0.0.1"
val IPV6_LOOPBACK_INET_ADDRESS = "::1"
val SERVER_SOCKET_BACKLOG_SIZE = 10 // 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
val serverLoopbackSocketFactory by lazy { ServerLoopbackSocketFactory() }
val clientLoopbackSocketFactory by lazy { ClientLoopbackSocketFactory() }
// TODO switch to InetAddress.getLoopbackAddress on java 7+
val loopbackInetAddressName by lazy {
try {
if (InetAddress.getLocalHost() is 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
}
}
// base socket factories by default don't implement equals properly (see e.g. http://stackoverflow.com/questions/21555710/rmi-and-jmx-socket-factories)
// so implementing it in derived classes using the fact that they are singletons
class ServerLoopbackSocketFactory : RMIServerSocketFactory, Serializable {
override fun equals(other: Any?): Boolean = other === this || super.equals(other)
override fun hashCode(): Int = super.hashCode()
@Throws(IOException::class)
override fun createServerSocket(port: Int): ServerSocket = ServerSocket(port, SERVER_SOCKET_BACKLOG_SIZE, InetAddress.getByName(loopbackInetAddressName))
}
class ClientLoopbackSocketFactory : RMIClientSocketFactory, Serializable {
override fun equals(other: Any?): Boolean = other === this || super.equals(other)
override fun hashCode(): Int = super.hashCode()
@Throws(IOException::class)
override fun createSocket(host: String, port: Int): Socket = Socket(InetAddress.getByName(loopbackInetAddressName), port)
}
}
private val portSelectionRng = Random()
fun findPortAndCreateRegistry(attempts: Int, portRangeStart: Int, portRangeEnd: Int) : Pair<Registry, Int> {
var i = 0
var lastException: RemoteException? = null
while (i++ < attempts) {
val port = portSelectionRng.nextInt(portRangeEnd - portRangeStart) + portRangeStart
try {
return Pair(LocateRegistry.createRegistry(port, LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.serverLoopbackSocketFactory), port)
}
catch (e: RemoteException) {
// assuming that the port is already taken
lastException = e
}
}
throw IllegalStateException("Cannot find free port in $attempts attempts", lastException)
}
@@ -0,0 +1,174 @@
/*
* 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.daemon.common
import java.lang.management.ManagementFactory
import java.lang.management.ThreadMXBean
import java.util.concurrent.atomic.AtomicLong
interface PerfCounters {
val count: Long
val time: Long
val threadTime: Long
val threadUserTime: Long
val memory: Long
fun addMeasurement(time: Long = 0, thread: Long = 0, threadUser: Long = 0, memory: Long = 0)
}
interface Profiler {
fun getCounters(): Map<Any?, PerfCounters>
fun getTotalCounters(): PerfCounters
fun<R> withMeasure(obj: Any?, body: () -> R): R
}
open class SimplePerfCounters : PerfCounters {
private val _count: AtomicLong = AtomicLong(0L)
private val _time: AtomicLong = AtomicLong(0L)
private val _threadTime: AtomicLong = AtomicLong(0L)
private val _threadUserTime: AtomicLong = AtomicLong(0L)
private val _memory: AtomicLong = AtomicLong(0L)
override val count: Long get() = _count.get()
override val time: Long get() = _time.get()
override val threadTime: Long get() = _threadTime.get()
override val threadUserTime: Long get() = _threadUserTime.get()
override val memory: Long get() = _memory.get()
override fun addMeasurement(time: Long, thread: Long, threadUser: Long, memory: Long) {
_count.incrementAndGet()
_time.addAndGet(time)
_threadTime.addAndGet(thread)
_threadUserTime.addAndGet(threadUser)
_memory.addAndGet(memory)
}
}
class SimplePerfCountersWithTotal(val totalRef: PerfCounters) : SimplePerfCounters() {
override fun addMeasurement(time: Long, thread: Long, threadUser: Long, memory: Long) {
super.addMeasurement(time, thread, threadUser, memory)
totalRef.addMeasurement(time, thread, threadUser, memory)
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun ThreadMXBean.threadCpuTime() = if (isCurrentThreadCpuTimeSupported) currentThreadCpuTime else 0L
@Suppress("NOTHING_TO_INLINE")
inline fun ThreadMXBean.threadUserTime() = if (isCurrentThreadCpuTimeSupported) currentThreadUserTime else 0L
@Suppress("NOTHING_TO_INLINE")
inline fun usedMemory(withGC: Boolean): Long {
if (withGC) {
System.gc()
}
val rt = Runtime.getRuntime()
return (rt.totalMemory() - rt.freeMemory())
}
inline fun<R> withMeasureWallTime(perfCounters: PerfCounters, body: () -> R): R {
val startTime = System.nanoTime()
val res = body()
perfCounters.addMeasurement(time = System.nanoTime() - startTime) // TODO: add support for time wrapping
return res
}
inline fun<R> withMeasureWallAndThreadTimes(perfCounters: PerfCounters, threadMXBean: ThreadMXBean, body: () -> R): R {
val startTime = System.nanoTime()
val startThreadTime = threadMXBean.threadCpuTime()
val startThreadUserTime = threadMXBean.threadUserTime()
val res = body()
// TODO: add support for time wrapping
perfCounters.addMeasurement(time = System.nanoTime() - startTime,
thread = threadMXBean.threadCpuTime() - startThreadTime,
threadUser = threadMXBean.threadUserTime() - startThreadUserTime)
return res
}
inline fun<R> withMeasureWallAndThreadTimes(perfCounters: PerfCounters, body: () -> R): R = withMeasureWallAndThreadTimes(perfCounters, ManagementFactory.getThreadMXBean(), body)
inline fun<R> withMeasureWallAndThreadTimesAndMemory(perfCounters: PerfCounters, withGC: Boolean = false, threadMXBean: ThreadMXBean, body: () -> R): R {
val startMem = usedMemory(withGC)
val startTime = System.nanoTime()
val startThreadTime = threadMXBean.threadCpuTime()
val startThreadUserTime = threadMXBean.threadUserTime()
val res = body()
// TODO: add support for time wrapping
perfCounters.addMeasurement(time = System.nanoTime() - startTime,
thread = threadMXBean.threadCpuTime() - startThreadTime,
threadUser = threadMXBean.threadUserTime() - startThreadUserTime,
memory = usedMemory(withGC) - startMem)
return res
}
inline fun<R> withMeasureWallAndThreadTimesAndMemory(perfCounters: PerfCounters, withGC: Boolean, body: () -> R): R =
withMeasureWallAndThreadTimesAndMemory(perfCounters, withGC, ManagementFactory.getThreadMXBean(), body)
class DummyProfiler : Profiler {
override fun getCounters(): Map<Any?, PerfCounters> = mapOf(null to SimplePerfCounters())
override fun getTotalCounters(): PerfCounters = SimplePerfCounters()
override final inline fun <R> withMeasure(obj: Any?, body: () -> R): R = body()
}
abstract class TotalProfiler : Profiler {
val total = SimplePerfCounters()
val threadMXBean = ManagementFactory.getThreadMXBean()
override fun getCounters(): Map<Any?, PerfCounters> = mapOf()
override fun getTotalCounters(): PerfCounters = total
}
class WallTotalProfiler : TotalProfiler() {
override final inline fun <R> withMeasure(obj: Any?, body: () -> R): R = withMeasureWallTime(total, body)
}
class WallAndThreadTotalProfiler : TotalProfiler() {
override final inline fun <R> withMeasure(obj: Any?, body: () -> R): R = withMeasureWallAndThreadTimes(total, threadMXBean, body)
}
class WallAndThreadAndMemoryTotalProfiler(val withGC: Boolean) : TotalProfiler() {
override final inline fun <R> withMeasure(obj: Any?, body: () -> R): R = withMeasureWallAndThreadTimesAndMemory(total, withGC, threadMXBean, body)
}
class WallAndThreadByClassProfiler() : TotalProfiler() {
val counters = hashMapOf<Any?, SimplePerfCountersWithTotal>()
override fun getCounters(): Map<Any?, PerfCounters> = counters
override final inline fun <R> withMeasure(obj: Any?, body: () -> R): R =
withMeasureWallAndThreadTimes(counters.getOrPut(obj?.javaClass?.name, { SimplePerfCountersWithTotal(total) }), threadMXBean, body)
}
@@ -0,0 +1,29 @@
/*
* 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.daemon.common
import java.rmi.Remote
import java.rmi.RemoteException
interface RemoteOperationsTracer : Remote {
@Throws(RemoteException::class)
fun before(id: String)
@Throws(RemoteException::class)
fun after(id: String)
}
@@ -0,0 +1,32 @@
/*
* 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.daemon.common
import java.rmi.Remote
import java.rmi.RemoteException
interface RemoteOutputStream : Remote {
@Throws(RemoteException::class)
fun close()
@Throws(RemoteException::class)
fun write(data: ByteArray, offset: Int, length: Int)
@Throws(RemoteException::class)
fun write(dataByte: Int)
}