Introduce new Kotlin Daemon without RMI abstraction

This commit is contained in:
Vadim Brilyantov
2019-02-28 17:30:35 +03:00
parent a3f718733d
commit ced973b707
107 changed files with 8866 additions and 523 deletions
@@ -34443,7 +34443,7 @@ public final class DebugProtoBuf {
"tbrains.kotlin.metadata.VersionRequireme" +
"nt.Level:\005ERROR\022\022\n\nerror_code\030\004 \001(\005\022\025\n\007m" +
"essage\030\005 \001(\005B\004\230\265\030\001\022e\n\014version_kind\030\006 \001(\016" +
"2=.org.jetbrains.kotlin.metadata.Version" +
"2=.org.jetbrains.kotlin.metadata.version" +
"Requirement.VersionKind:\020LANGUAGE_VERSIO" +
"N\"+\n\005Level\022\013\n\007WARNING\020\000\022\t\n\005ERROR\020\001\022\n\n\006HI" +
"DDEN\020\002\"J\n\013VersionKind\022\024\n\020LANGUAGE_VERSIO" +
+15
View File
@@ -15,6 +15,7 @@ buildscript {
repositories.withRedirector(project) {
bootstrapKotlinRepo?.let(::maven)
maven("https://plugins.gradle.org/m2")
maven("https://dl.bintray.com/kotlin/ktor")
}
// a workaround for kotlin compiler classpath in kotlin project: sometimes gradle substitutes
@@ -95,6 +96,17 @@ val artifactsDir = "$distDir/artifacts"
val ideaPluginDir = "$artifactsDir/ideaPlugin/Kotlin"
val ideaUltimatePluginDir = "$artifactsDir/ideaUltimatePlugin/Kotlin"
extra["ktorExcludesForDaemon"] = listOf(
"org.jetbrains.kotlin" to "kotlin-reflect",
"org.jetbrains.kotlin" to "kotlin-stdlib",
"org.jetbrains.kotlin" to "kotlin-stdlib-common",
"org.jetbrains.kotlin" to "kotlin-stdlib-jdk8",
"org.jetbrains.kotlin" to "kotlin-stdlib-jdk7",
"org.jetbrains.kotlinx" to "kotlinx-coroutines-jdk8",
"org.jetbrains.kotlinx" to "kotlinx-coroutines-core",
"org.jetbrains.kotlinx" to "kotlinx-coroutines-core-common"
)
// TODO: use "by extra()" syntax where possible
extra["distLibDir"] = project.file(distLibDir)
extra["libsDir"] = project.file(distLibDir)
@@ -160,6 +172,7 @@ extra["versions.jflex"] = "1.7.0"
extra["versions.markdown"] = "0.1.25"
extra["versions.trove4j"] = "1.0.20181211"
extra["versions.kotlin-native-shared"] = "1.0-dev-57"
extra["versions.ktor-network"] = "1.0.1"
if (!project.hasProperty("versions.kotlin-native")) {
extra["versions.kotlin-native"] = "1.3-dev-9780"
@@ -198,6 +211,7 @@ extra["compilerModules"] = arrayOf(
":compiler:frontend.java",
":compiler:cli-common",
":compiler:daemon-common",
":compiler:daemon-common-new",
":compiler:daemon",
":compiler:ir.tree",
":compiler:ir.psi2ir",
@@ -319,6 +333,7 @@ allprojects {
maven(intellijRepo)
maven(bootstrapKotlinRepo!!.replace("artifacts/content/maven/", "artifacts/content/internal/repo"))
maven(kotlinNativeRepo)
maven("https://dl.bintray.com/kotlin/ktor")
}
configureJvmProject(javaHome!!, jvmTarget!!)
+21 -2
View File
@@ -30,6 +30,8 @@ fun configureFreeCompilerArg(isEnabled: Boolean, compilerArgument: String) {
val antLauncherJar by configurations.creating
val ktorExcludesForDaemon : List<Pair<String, String>> by rootProject.extra
dependencies {
testRuntime(intellijDep()) // Should come before compiler, because of "progarded" stuff needed for tests
@@ -50,8 +52,14 @@ dependencies {
testCompile(project(":compiler:ir.tree")) // used for deepCopyWithSymbols call that is removed by proguard from the compiler TODO: make it more straightforward
testCompile(project(":kotlin-scripting-compiler-impl"))
testCompile(project(":kotlin-script-util"))
testCompileOnly(projectRuntimeJar(":kotlin-daemon-client"))
testCompileOnly(projectRuntimeJar(":kotlin-daemon-client-new"))
testCompileOnly(project(":kotlin-reflect-api"))
testCompile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) { isTransitive = false }
testCompile(commonDep("io.ktor", "ktor-network")) {
ktorExcludesForDaemon.forEach { (group, module) ->
exclude(group = group, module = module)
}
}
otherCompilerModules.forEach {
testCompileOnly(project(it))
}
@@ -59,7 +67,18 @@ dependencies {
testCompileOnly(intellijDep()) { includeJars("openapi", "idea", "idea_rt", "util", "asm-all", rootProject = rootProject) }
testRuntime(project(":kotlin-reflect"))
testRuntime(project(":kotlin-daemon-client"))
testRuntime(project(":kotlin-daemon-client-new"))
testRuntime(project(":compiler:daemon")) // +
testRuntime(project(":compiler:daemon-common-new")) // +
testRuntime(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) {
isTransitive = false
}
testRuntime(commonDep("io.ktor", "ktor-network")) {
ktorExcludesForDaemon.forEach { (group, module) ->
exclude(group = group, module = module)
}
}
testRuntime(androidDxJar())
testRuntime(files(toolsJar()))
@@ -14,10 +14,12 @@ dependencies {
compileOnly(project(":kotlin-preloader"))
compileOnly(project(":compiler:frontend.java"))
compileOnly(project(":compiler:daemon-common"))
compileOnly(project(":compiler:daemon-common-new"))
compile(projectRuntimeJar(":kotlin-daemon-client"))
compileOnly(project(":compiler:util"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
runtimeOnly(projectRuntimeJar(":kotlin-compiler-embeddable"))
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) { isTransitive = false }
}
sourceSets {
+1
View File
@@ -75,6 +75,7 @@ messages/**)
-dontwarn com.intellij.util.io.Decompressor*
-dontwarn org.w3c.dom.Location
-dontwarn org.w3c.dom.Window
-dontwarn org.slf4j.**
#-libraryjars '<rtjar>'
+1
View File
@@ -51,6 +51,7 @@ messages/**)
-dontwarn org.jetbrains.annotations.Mutable
-dontwarn com.intellij.util.io.TarUtil
-dontwarn com.intellij.util.io.Compressor$Tar
-dontwarn org.slf4j.**
# Annotations from intellijCore/annotations.jar that not presented in org.jetbrains.annotations
-dontwarn org.jetbrains.annotations.Async*
+1
View File
@@ -52,6 +52,7 @@ messages/**)
-dontwarn org.jetbrains.annotations.Mutable
-dontwarn com.intellij.util.io.TarUtil
-dontwarn com.intellij.util.io.Compressor$Tar
-dontwarn org.slf4j.**
# Annotations from intellijCore/annotations.jar that not presented in org.jetbrains.annotations
-dontwarn org.jetbrains.annotations.Async*
+17
View File
@@ -1,22 +1,39 @@
import com.sun.javafx.scene.CameraHelper.project
import org.gradle.internal.impldep.org.junit.experimental.categories.Categories.CategoryFilter.exclude
import org.jetbrains.kotlin.gradle.dsl.Coroutines
plugins {
kotlin("jvm")
id("jps-compatible")
}
val ktorExcludesForDaemon : List<Pair<String, String>> by rootProject.extra
dependencies {
compile(project(":compiler:cli"))
compile(project(":compiler:cli-js"))
compile(project(":compiler:daemon-common"))
compile(project(":compiler:daemon-common-new"))
compile(project(":compiler:incremental-compilation-impl"))
compile(project(":kotlin-build-common"))
compile(commonDep("org.fusesource.jansi", "jansi"))
compile(commonDep("org.jline", "jline"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
compileOnly(intellijDep()) { includeIntellijCoreJarDependencies(project) }
runtime(project(":kotlin-reflect"))
compileOnly(project(":kotlin-reflect-api"))
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) { isTransitive = false }
compile(commonDep("io.ktor", "ktor-network")) {
ktorExcludesForDaemon.forEach { (group, module) ->
exclude(group = group, module = module)
}
}
}
sourceSets {
"main" { projectDefault() }
"test" {}
}
kotlin {
experimental.coroutines = Coroutines.ENABLE
}
@@ -0,0 +1,78 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
description = "Kotlin Daemon Client New"
plugins {
kotlin("jvm")
id("jps-compatible")
}
jvmTarget = "1.8"
val nativePlatformVariants = listOf(
"windows-amd64",
"windows-i386",
"osx-amd64",
"osx-i386",
"linux-amd64",
"linux-i386",
"freebsd-amd64-libcpp",
"freebsd-amd64-libstdcpp",
"freebsd-i386-libcpp",
"freebsd-i386-libstdcpp"
)
val ktorExcludesForDaemon : List<Pair<String, String>> by rootProject.extra
dependencies {
compileOnly(project(":compiler:util"))
compileOnly(project(":compiler:cli-common"))
compileOnly(project(":compiler:daemon-common-new"))
compileOnly(project(":kotlin-reflect-api"))
compileOnly(project(":kotlin-daemon-client"))
embeddedComponents(project(":kotlin-daemon-client")) { isTransitive = false }
compileOnly(project(":js:js.frontend"))
compileOnly(commonDep("net.rubygrapefruit", "native-platform"))
compileOnly(intellijDep()) { includeIntellijCoreJarDependencies(project) }
embeddedComponents(project(":compiler:daemon-common")) { isTransitive = false }
embeddedComponents(commonDep("net.rubygrapefruit", "native-platform"))
nativePlatformVariants.forEach {
embeddedComponents(commonDep("net.rubygrapefruit", "native-platform", "-$it"))
}
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) {
isTransitive = false
}
compile(commonDep("io.ktor", "ktor-network")) {
ktorExcludesForDaemon.forEach { (group, module) ->
exclude(group = group, module = module)
}
}
}
sourceSets {
"main" { projectDefault() }
"test" {}
}
publish()
noDefaultJar()
runtimeJar(task<ShadowJar>("shadowJar")) {
from(mainSourceSet.output)
fromEmbeddedComponents()
}
sourcesJar()
javadocJar()
dist()
ideaPlugin()
@@ -0,0 +1,32 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.client.experimental
import io.ktor.network.sockets.Socket
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.daemon.client.reportFromDaemon
import org.jetbrains.kotlin.daemon.common.experimental.*
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Server
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.ServerSocketWrapper
import java.io.File
import java.io.Serializable
open class BasicCompilerServicesWithResultsFacadeServerServerSide(
val messageCollector: MessageCollector,
val outputsCollector: ((File, List<File>) -> Unit)? = null,
override val serverSocketWithPort: ServerSocketWrapper = findCallbackServerSocket()
) : CompilerServicesFacadeBaseServerSide {
override val clients = hashMapOf<Socket, Server.ClientInfo>()
override suspend fun report(category: Int, severity: Int, message: String?, attachment: Serializable?) {
messageCollector.reportFromDaemon(outputsCollector, category, severity, message, attachment)
}
val clientSide: CompilerServicesFacadeBaseClientSide
get() = CompilerServicesFacadeBaseClientSideImpl(serverSocketWithPort.port)
}
@@ -0,0 +1,104 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.client.experimental
import io.ktor.network.sockets.Socket
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.daemon.client.reportFromDaemon
import org.jetbrains.kotlin.daemon.common.experimental.*
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Server
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.ServerSocketWrapper
import org.jetbrains.kotlin.incremental.components.LookupInfo
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
import org.jetbrains.kotlin.modules.TargetId
import org.jetbrains.kotlin.progress.experimental.CompilationCanceledStatus
import org.jetbrains.kotlin.utils.isProcessCanceledException
import java.io.Serializable
open class CompilerCallbackServicesFacadeServerServerSide(
val incrementalCompilationComponents: IncrementalCompilationComponents? = null,
val lookupTracker: LookupTracker? = null,
val compilationCanceledStatus: CompilationCanceledStatus? = null,
val messageCollector: MessageCollector? = null,
override val serverSocketWithPort: ServerSocketWrapper = findCallbackServerSocket()
) : CompilerCallbackServicesFacadeServerSide {
override suspend fun report(category: Int, severity: Int, message: String?, attachment: Serializable?) {
messageCollector?.reportFromDaemon(null, category, severity, message, attachment)
}
override val clients = hashMapOf<Socket, Server.ClientInfo>()
val clientSide : CompilerServicesFacadeBaseClientSide
get() = CompilerCallbackServicesFacadeClientSideImpl(serverSocketWithPort.port)
override suspend fun hasIncrementalCaches(): Boolean = incrementalCompilationComponents != null
override suspend fun hasLookupTracker(): Boolean = lookupTracker != null
override suspend fun hasCompilationCanceledStatus(): Boolean = compilationCanceledStatus != null
// TODO: consider replacing NPE with other reporting, although NPE here means most probably incorrect usage
override suspend fun incrementalCache_getObsoletePackageParts(target: TargetId): Collection<String> =
incrementalCompilationComponents!!.getIncrementalCache(target).getObsoletePackageParts()
override suspend fun incrementalCache_getObsoleteMultifileClassFacades(target: TargetId): Collection<String> =
incrementalCompilationComponents!!.getIncrementalCache(target).getObsoleteMultifileClasses()
override suspend fun incrementalCache_getMultifileFacadeParts(target: TargetId, internalName: String): Collection<String>? =
incrementalCompilationComponents!!.getIncrementalCache(target).getStableMultifileFacadeParts(internalName)
override suspend fun incrementalCache_getPackagePartData(target: TargetId, partInternalName: String): JvmPackagePartProto? =
incrementalCompilationComponents!!.getIncrementalCache(target).getPackagePartData(partInternalName)
override suspend fun incrementalCache_getModuleMappingData(target: TargetId): ByteArray? =
incrementalCompilationComponents!!.getIncrementalCache(target).getModuleMappingData()
// todo: remove (the method it called was relevant only for old IC)
override suspend fun incrementalCache_registerInline(target: TargetId, fromPath: String, jvmSignature: String, toPath: String) {
}
override suspend fun incrementalCache_getClassFilePath(target: TargetId, internalClassName: String): String =
incrementalCompilationComponents!!.getIncrementalCache(target).getClassFilePath(internalClassName)
override suspend fun incrementalCache_close(target: TargetId) {
incrementalCompilationComponents!!.getIncrementalCache(target).close()
}
override suspend fun lookupTracker_requiresPosition() = lookupTracker!!.requiresPosition
override fun lookupTracker_record(lookups: Collection<LookupInfo>) {
val lookupTracker = lookupTracker!!
for (it in lookups) {
lookupTracker.record(it.filePath, it.position, it.scopeFqName, it.scopeKind, it.name)
}
}
private val lookupTracker_isDoNothing: Boolean = lookupTracker === LookupTracker.DO_NOTHING
override suspend fun lookupTracker_isDoNothing(): Boolean = lookupTracker_isDoNothing
override suspend fun compilationCanceledStatus_checkCanceled(): Void? {
try {
compilationCanceledStatus?.checkCanceled()
return null
}
catch (e: Exception) {
// avoid passing exceptions that may have different serialVersionUID on across rmi border
// removing dependency from openapi (this is obsolete part anyway, and will be removed soon)
if (e.isProcessCanceledException())
throw Exception("-TODO- RmiFriendlyCompilationCanceledException()") //RmiFriendlyCompilationCanceledException()
else throw e
}
}
}
@@ -0,0 +1,612 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.client.experimental
import io.ktor.network.sockets.Socket
import kotlinx.coroutines.*
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.daemon.client.CompileServiceSessionAsync
import org.jetbrains.kotlin.daemon.client.KotlinCompilerDaemonClient
import org.jetbrains.kotlin.daemon.client.DaemonReportMessage
import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.experimental.*
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Server
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.ServerSocketWrapper
import org.jetbrains.kotlin.daemon.common.*
import java.io.File
import java.io.Serializable
import java.net.SocketException
import java.nio.channels.ClosedChannelException
import java.rmi.ConnectException
import java.rmi.ConnectIOException
import java.rmi.UnmarshalException
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import java.util.logging.Logger
import kotlin.concurrent.thread
import org.jetbrains.kotlin.daemon.client.launchProcessWithFallback
class KotlinCompilerClient : KotlinCompilerDaemonClient {
init {
println("experimental KotlinCompilerClient is being instantiated")
}
val DAEMON_DEFAULT_STARTUP_TIMEOUT_MS = 10000L
val DAEMON_CONNECT_CYCLE_ATTEMPTS = 3
val verboseReporting = System.getProperty(COMPILE_DAEMON_VERBOSE_REPORT_PROPERTY) != null
private val log = Logger.getLogger("KotlinCompilerClient")
private fun String.info(msg: String) = {}()//log.info("[$this] : $msg")
override fun getOrCreateClientFlagFile(daemonOptions: DaemonOptions): File =
// for jps property is passed from IDEA to JPS in KotlinBuildProcessParametersProvider
System.getProperty(COMPILE_DAEMON_CLIENT_ALIVE_PATH_PROPERTY)
?.let(String::trimQuotes)
?.takeUnless(String::isBlank)
?.let(::File)
?.takeIf(File::exists)
?: makeAutodeletingFlagFile(baseDir = File(daemonOptions.runFilesPathOrDefault))
override suspend fun connectToCompileService(
compilerId: CompilerId,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
reportingTargets: DaemonReportingTargets,
autostart: Boolean,
checkId: Boolean
): CompileServiceAsync? {
log.info("in connectToCompileService")
val flagFile = getOrCreateClientFlagFile(daemonOptions)
return connectToCompileService(
compilerId,
flagFile,
daemonJVMOptions,
daemonOptions,
reportingTargets,
autostart
)
}
override suspend fun connectToCompileService(
compilerId: CompilerId,
clientAliveFlagFile: File,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
reportingTargets: DaemonReportingTargets,
autostart: Boolean
): CompileServiceAsync? {
log.info("connectToCompileService")
return connectAndLease(
compilerId,
clientAliveFlagFile,
daemonJVMOptions,
daemonOptions,
reportingTargets,
autostart,
leaseSession = false,
sessionAliveFlagFile = null
)?.compileService
}
override suspend fun connectAndLease(
compilerId: CompilerId,
clientAliveFlagFile: File,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
reportingTargets: DaemonReportingTargets,
autostart: Boolean,
leaseSession: Boolean,
sessionAliveFlagFile: File?
): CompileServiceSessionAsync? {
return connectLoop(
reportingTargets,
autostart
) { isLastAttempt ->
log.info("connectAndLease")
fun CompileServiceAsync.leaseImpl(): Deferred<CompileServiceSessionAsync?> =
GlobalScope.async {
// the newJVMOptions could be checked here for additional parameters, if needed
log.info("trying registerClient")
println("trying registerClient")
try {
registerClient(clientAliveFlagFile.absolutePath)
} catch (e: Throwable) {
return@async null
}
reportingTargets.report(DaemonReportCategory.DEBUG, "connected to the daemon")
if (!leaseSession)
CompileServiceSessionAsync(this@leaseImpl, CompileService.NO_SESSION)
else
try {
leaseCompileSession(sessionAliveFlagFile?.absolutePath)
} catch (e: Throwable) {
return@async null
}
.takeUnless { it is CompileService.CallResult.Dying }
?.let {
CompileServiceSessionAsync(this@leaseImpl, it.get())
}
}
ensureServerHostnameIsSetUp()
val (service, newJVMOptions) =
tryFindSuitableDaemonOrNewOpts(File(daemonOptions.runFilesPath), compilerId, daemonJVMOptions) { cat, msg ->
GlobalScope.async { reportingTargets.report(cat, msg) }
}.await()
if (service != null) {
service.leaseImpl().await()
} else {
if (!isLastAttempt && autostart) {
log.info("starting daemon...")
if (startDaemon(compilerId, newJVMOptions, daemonOptions, reportingTargets)) {
reportingTargets.report(DaemonReportCategory.DEBUG, "new daemon started, trying to find it")
}
}
null
}
}
}
override suspend fun shutdownCompileService(compilerId: CompilerId, daemonOptions: DaemonOptions) {
connectToCompileService(
compilerId,
DaemonJVMOptions(),
daemonOptions,
DaemonReportingTargets(out = System.out),
autostart = false,
checkId = false
)?.shutdown()
}
override suspend fun leaseCompileSession(compilerService: CompileServiceAsync, aliveFlagPath: String?): Int =
compilerService.leaseCompileSession(aliveFlagPath).get()
override suspend fun releaseCompileSession(compilerService: CompileServiceAsync, sessionId: Int) {
compilerService.releaseCompileSession(sessionId)
}
override suspend fun compile(
compilerService: CompileServiceAsync,
sessionId: Int,
targetPlatform: CompileService.TargetPlatform,
args: Array<out String>,
messageCollector: MessageCollector,
outputsCollector: ((File, List<File>) -> Unit)?,
compilerMode: CompilerMode,
reportSeverity: ReportSeverity,
port: Int,
profiler: Profiler
): Int = profiler.withMeasure(this) {
log.info("Compile")
val services = BasicCompilerServicesWithResultsFacadeServerServerSide(
messageCollector,
outputsCollector,
findCallbackServerSocket()
)
runBlocking {
services.runServer()
compilerService.compile(
sessionId,
args,
CompilationOptions(
compilerMode,
targetPlatform,
arrayOf(
ReportCategory.COMPILER_MESSAGE.code,
ReportCategory.DAEMON_MESSAGE.code,
ReportCategory.EXCEPTION.code,
ReportCategory.OUTPUT_MESSAGE.code
),
reportSeverity.code,
emptyArray()
),
services.clientSide,
createCompResults().clientSide
).get()
}
}
val COMPILE_DAEMON_CLIENT_OPTIONS_PROPERTY: String = "kotlin.daemon.client.options"
data class ClientOptions(
var stop: Boolean = false
) : OptionsGroup {
override val mappers: List<PropMapper<*, *, *>>
get() = listOf(BoolPropMapper(this, ClientOptions::stop))
}
private fun configureClientOptions(opts: ClientOptions): ClientOptions {
System.getProperty(COMPILE_DAEMON_CLIENT_OPTIONS_PROPERTY)?.let {
val unrecognized = it.trimQuotes().split(",").filterExtractProps(opts.mappers, "")
if (unrecognized.any())
throw IllegalArgumentException(
"Unrecognized client options passed via property $COMPILE_DAEMON_OPTIONS_PROPERTY: " + unrecognized.joinToString(" ") +
"\nSupported options: " + opts.mappers.joinToString(", ", transform = { it.names.first() })
)
}
return opts
}
private fun configureClientOptions(): ClientOptions =
configureClientOptions(ClientOptions())
override fun main(vararg args: String) {
runBlocking {
val compilerId = CompilerId()
val daemonOptions = configureDaemonOptions()
val daemonLaunchingOptions = configureDaemonJVMOptions(
inheritMemoryLimits = true,
inheritOtherJvmOptions = false,
inheritAdditionalProperties = true
)
val clientOptions = configureClientOptions()
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")
detectCompilerClasspath()
?.let { compilerId.compilerClasspath = it }
}
if (compilerId.compilerClasspath.none())
throw IllegalArgumentException("Cannot find compiler jar")
else
log.info("desired compiler classpath: " + compilerId.compilerClasspath.joinToString(File.pathSeparator))
}
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 -> {
log.info("Shutdown the daemon")
daemon.shutdown()
log.info("Daemon shut down successfully")
}
filteredArgs.none() -> {
// so far used only in tests
log.info(
"Warning: empty arguments list, only daemon check is performed: checkCompilerId() returns ${
daemon.checkCompilerId(
compilerId
)}"
)
}
else -> {
log.info("Executing daemon compilation with args: " + filteredArgs.joinToString(" "))
val servicesFacade =
CompilerCallbackServicesFacadeServerServerSide()
val serverRun = servicesFacade.runServer()
try {
val memBefore = daemon.getUsedMemory().get() / 1024
val startTime = System.nanoTime()
val compResults = createCompResults()
val compResultsServerRun = compResults.runServer()
val res = daemon.compile(
CompileService.NO_SESSION,
filteredArgs.toList().toTypedArray(),
CompilationOptions(
CompilerMode.NON_INCREMENTAL_COMPILER,
CompileService.TargetPlatform.JVM,
arrayOf(), // TODO ???
ReportSeverity.INFO.code, // TODO ???
arrayOf() // TODO ???
),
servicesFacade.clientSide,
compResults.clientSide
)
val endTime = System.nanoTime()
log.info("Compilation ${if (res.isGood) "succeeded" else "failed"}, result code: ${res.get()}")
val memAfter = daemon.getUsedMemory().get() / 1024
log.info("Compilation time: " + TimeUnit.NANOSECONDS.toMillis(endTime - startTime) + " ms")
log.info("Used memory $memAfter (${"%+d".format(memAfter - memBefore)} kb)")
} finally {
// TODO ??
}
}
}
}
}
override fun createCompResults(): CompilationResultsServerSide = object : CompilationResultsServerSide {
override val clients = hashMapOf<Socket, Server.ClientInfo>()
override val serverSocketWithPort: ServerSocketWrapper
get() = resultsPort
private val resultsPort = findPortForSocket(
COMPILE_DAEMON_FIND_PORT_ATTEMPTS,
RESULTS_SERVER_PORTS_RANGE_START,
RESULTS_SERVER_PORTS_RANGE_END
)
private val resultsMap = hashMapOf<Int, MutableList<Serializable>>()
override val clientSide: CompilationResultsClientSide
get() = CompilationResultsClientSideImpl(resultsPort.port)
override suspend fun add(compilationResultCategory: Int, value: Serializable) {
synchronized(this) {
resultsMap.putIfAbsent(compilationResultCategory, mutableListOf())
resultsMap[compilationResultCategory]!!.add(value)
// TODO logger?
}
}
}
private fun detectCompilerClasspath(): List<String>? =
System.getProperty("java.class.path")
?.split(File.pathSeparator)
?.map { File(it).parentFile }
?.distinct()
?.mapNotNull {
it?.walk()
?.firstOrNull { it.name.equals(COMPILER_JAR_NAME, ignoreCase = true) }
}
?.firstOrNull()
?.let { listOf(it.absolutePath) }
// --- Implementation ---------------------------------------
@Synchronized
private inline fun <R> connectLoop(reportingTargets: DaemonReportingTargets, autostart: Boolean, body: (Boolean) -> R?): R? {
try {
var attempts = 1
while (true) {
val (res, err) = try {
body(attempts >= DAEMON_CONNECT_CYCLE_ATTEMPTS) to null
} catch (e: SocketException) {
null to e
} catch (e: ConnectException) {
null to e
} catch (e: ConnectIOException) {
null to e
} catch (e: UnmarshalException) {
null to e
} catch (e: RuntimeException) {
null to e
} catch (e: ClosedChannelException) {
null to e
}
if (res != null) return res
if (err != null) {
GlobalScope.async {
reportingTargets.report(
DaemonReportCategory.INFO,
(if (attempts >= DAEMON_CONNECT_CYCLE_ATTEMPTS || !autostart) "no more retries on: " else "retrying($attempts) on: ")
+ err.toString()
)
}
}
if (attempts++ > DAEMON_CONNECT_CYCLE_ATTEMPTS || !autostart) {
return null
}
}
} catch (e: Throwable) {
GlobalScope.async { reportingTargets.report(DaemonReportCategory.EXCEPTION, e.toString()) }
}
return null
}
private fun tryFindSuitableDaemonOrNewOpts(
registryDir: File,
compilerId: CompilerId,
daemonJVMOptions: DaemonJVMOptions,
report: (DaemonReportCategory, String) -> Unit
): Deferred<Pair<CompileServiceAsync?, DaemonJVMOptions>> = GlobalScope.async {
log.info("tryFindSuitableDaemonOrNewOpts")
registryDir.mkdirs()
val timestampMarker = createTempFile("kotlin-daemon-client-tsmarker", directory = registryDir)
val aliveWithMetadata = try {
log.info("walkDaemonsAsync... : ${registryDir.path}")
walkDaemonsAsync(registryDir, compilerId, timestampMarker, report = report).also {
log.info(
"daemons (${it.size}): ${it.map { "daemon(params : " + it.jvmOptions.jvmParams.joinToString(", ") + ")" }.joinToString(
", ", "[", "]"
)}"
)
}
} finally {
timestampMarker.delete()
}
log.info("daemons : ${aliveWithMetadata.map { it.daemon::class.java.name }}")
log.info("aliveWithMetadata: ${aliveWithMetadata.map { it.daemon::class.java.name }}")
val comparator = compareBy<DaemonWithMetadataAsync, DaemonJVMOptions>(DaemonJVMOptionsMemoryComparator(), { it.jvmOptions })
.thenBy {
when (it.daemon) {
is CompileServiceAsyncWrapper -> 0
else -> 1
}
}
.thenBy(FileAgeComparator()) { it.runFile }
val optsCopy = daemonJVMOptions.copy()
// if required options fit into fattest running daemon - return the daemon and required options with memory params set to actual ones in the daemon
aliveWithMetadata.maxWith(comparator)
?.takeIf { daemonJVMOptions memorywiseFitsInto it.jvmOptions }
?.let {
Pair(it.daemon, optsCopy.updateMemoryUpperBounds(it.jvmOptions))
}
// else combine all options from running daemon to get fattest option for a new daemon to runServer
?: Pair(null, aliveWithMetadata.fold(optsCopy, { opts, d -> opts.updateMemoryUpperBounds(d.jvmOptions) }))
}
private suspend fun startDaemon(
compilerId: CompilerId,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
reportingTargets: DaemonReportingTargets
): Boolean {
log.info("in startDaemon() - 0")
val javaExecutable = File(File(System.getProperty("java.home"), "bin"), "java")
log.info("in startDaemon() - 0.1")
val serverHostname = System.getProperty(JAVA_RMI_SERVER_HOSTNAME) ?: error("$JAVA_RMI_SERVER_HOSTNAME is not set!")
log.info("in startDaemon() - 0.2")
val platformSpecificOptions = listOf(
// hide daemon window
"-Djava.awt.headless=true",
"-D$JAVA_RMI_SERVER_HOSTNAME=$serverHostname"
)
log.info("in startDaemon() - 0.3")
val args = listOf(
javaExecutable.absolutePath, "-cp", compilerId.compilerClasspath.joinToString(File.pathSeparator)
) +
platformSpecificOptions +
daemonJVMOptions.mappers.flatMap { it.toArgs("-") } +
COMPILER_DAEMON_CLASS_FQN_EXPERIMENTAL +
daemonOptions.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) } +
compilerId.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) }
log.info("in startDaemon() - 1")
reportingTargets.report(DaemonReportCategory.DEBUG, "starting the daemon as: " + args.joinToString(" "))
val processBuilder = ProcessBuilder(args)
log.info("in startDaemon() - 2")
processBuilder.redirectErrorStream(true)
// assuming daemon process is deaf and (mostly) silent, so do not handle streams
log.info("daemon = launchProcessWithFallback")
val daemon =
launchProcessWithFallback(processBuilder, reportingTargets, "daemon client")
val isEchoRead = Semaphore(1)
isEchoRead.acquire()
val stdoutThread =
thread {
try {
daemon.inputStream
.reader()
.forEachLine {
log.info("daemon_process_report : $it")
if (it == COMPILE_DAEMON_IS_READY_MESSAGE) {
GlobalScope.async {
reportingTargets.report(
DaemonReportCategory.DEBUG,
"Received the message signalling that the daemon is ready"
)
}
isEchoRead.release()
//TODO return@forEachLine
} else {
GlobalScope.async { reportingTargets.report(DaemonReportCategory.INFO, it, "daemon") }
}
}
} finally {
daemon.inputStream.close()
daemon.outputStream.close()
daemon.errorStream.close()
isEchoRead.release()
}
}
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()) {
log.info("daemonOptions.runFilesPath.isNotEmpty")
val succeeded = isEchoRead.tryAcquire(daemonStartupTimeout, TimeUnit.MILLISECONDS)
log.info("succeeded : $succeeded")
return when {
!isProcessAlive(daemon) -> {
log.info("!isProcessAlive(daemon)")
reportingTargets.report(
DaemonReportCategory.INFO,
"Daemon terminated unexpectedly with error code: ${daemon.exitValue()}"
)
false
}
!succeeded -> {
log.info("isProcessAlive!")
reportingTargets.report(DaemonReportCategory.INFO, "Unable to get response from daemon in $daemonStartupTimeout ms")
false
}
else -> true
}
} else
log.info("!daemonOptions.runFilesPath.isNotEmpty")
// without startEcho defined waiting for max timeout
Thread.sleep(daemonStartupTimeout)
return true
} 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()
}
reportingTargets.out?.flush()
}
}
}
internal suspend fun DaemonReportingTargets.report(category: DaemonReportCategory, message: String, source: String? = null) {
val sourceMessage: String by lazy { source?.let { "[$it] $message" } ?: message }
out?.println("${category.name}: $sourceMessage")
messages?.add(DaemonReportMessage(category, sourceMessage))
messageCollector?.let {
when (category) {
DaemonReportCategory.DEBUG -> it.report(CompilerMessageSeverity.LOGGING, sourceMessage)
DaemonReportCategory.INFO -> it.report(CompilerMessageSeverity.INFO, sourceMessage)
DaemonReportCategory.EXCEPTION -> it.report(CompilerMessageSeverity.EXCEPTION, sourceMessage)
}
}
compilerServices?.let {
when (category) {
DaemonReportCategory.DEBUG -> it.report(ReportCategory.DAEMON_MESSAGE, ReportSeverity.DEBUG, message, source)
DaemonReportCategory.INFO -> it.report(ReportCategory.DAEMON_MESSAGE, ReportSeverity.INFO, message, source)
DaemonReportCategory.EXCEPTION -> it.report(ReportCategory.EXCEPTION, ReportSeverity.ERROR, message, source)
}
}
}
internal fun isProcessAlive(process: Process) =
try {
process.exitValue()
false
} catch (e: IllegalThreadStateException) {
true
}
@@ -0,0 +1,79 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.client.experimental
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.daemon.client.KotlinRemoteReplCompilerClient
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.common.CompileServiceClientSide
import org.jetbrains.kotlin.daemon.common.experimental.findCallbackServerSocket
import org.jetbrains.kotlin.daemon.common.ReportCategory
import org.jetbrains.kotlin.daemon.common.ReportSeverity
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import org.jetbrains.kotlin.daemon.client.RemoteReplCompilerState
// TODO: reduce number of ports used then SOCKET_ANY_FREE_PORT is passed (same problem with other calls)
open class KotlinRemoteReplCompilerClientAsync(
protected val compileService: CompileServiceAsync,
clientAliveFlagFile: File?,
targetPlatform: CompileService.TargetPlatform,
args: Array<out String>,
messageCollector: MessageCollector,
templateClasspath: List<File>,
templateClassName: String
) : ReplCompiler {
val services = BasicCompilerServicesWithResultsFacadeServerServerSide(
messageCollector,
null,
findCallbackServerSocket()
)
val sessionId = runBlocking {
compileService.leaseReplSession(
clientAliveFlagFile?.absolutePath,
args,
CompilationOptions(
CompilerMode.NON_INCREMENTAL_COMPILER,
targetPlatform,
arrayOf(ReportCategory.COMPILER_MESSAGE.code, ReportCategory.DAEMON_MESSAGE.code, ReportCategory.EXCEPTION.code, ReportCategory.OUTPUT_MESSAGE.code),
ReportSeverity.INFO.code,
emptyArray()),
services.clientSide,
templateClasspath,
templateClassName
).get()
}
// dispose should be called at the end of the repl lifetime to free daemon repl session and appropriate resources
open fun dispose() {
runBlocking {
try {
compileService.releaseReplSession(sessionId)
} catch (ex: java.lang.Exception) {
// assuming that communication failed and daemon most likely is already down
}
}
}
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> =
RemoteReplCompilerStateAsync(
runBlocking { compileService.replCreateState(sessionId).get() },
lock
)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = runBlocking {
compileService.replCheck(sessionId, state.asState(RemoteReplCompilerStateAsync::class.java).replStateFacade.getId(), codeLine).get()
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult = runBlocking {
compileService.replCompile(sessionId, state.asState(RemoteReplCompilerStateAsync::class.java).replStateFacade.getId(), codeLine).get()
}
}
@@ -0,0 +1,49 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.client.experimental
import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets
import org.jetbrains.kotlin.daemon.client.NativePlatformLauncherWrapper
import org.jetbrains.kotlin.daemon.common.DaemonReportCategory
import java.io.IOException
suspend fun launchProcessWithFallback(
processBuilder: ProcessBuilder,
reportingTargets: DaemonReportingTargets,
reportingSource: String = "process launcher"
): Process =
try {
// A separate class to delay classloading until this point, where we can catch class loading errors in case then the native lib is not in the classpath
NativePlatformLauncherWrapper().launch(processBuilder)
} catch (e: UnsatisfiedLinkError) {
reportingTargets.report(
DaemonReportCategory.DEBUG,
"Could not start process with native process launcher, falling back to ProcessBuilder#start ($e)",
reportingSource
)
null
} catch (e: IOException) {
reportingTargets.report(
DaemonReportCategory.DEBUG,
"Could not start process with native process launcher, falling back to ProcessBuilder#start (${e.cause})",
reportingSource
)
null
} catch (e: NoClassDefFoundError) {
reportingTargets.report(
DaemonReportCategory.DEBUG,
"net.rubygrapefruit.platform library is not in the classpath, falling back to ProcessBuilder#start ($e)",
reportingSource
)
null
} catch (e: ClassNotFoundException) {
reportingTargets.report(
DaemonReportCategory.DEBUG,
"net.rubygrapefruit.platform library is not in the classpath, falling back to ProcessBuilder#start ($e)",
reportingSource
)
null
} ?: processBuilder.start()
@@ -0,0 +1,64 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.client.experimental
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.daemon.client.RemoteReplCompilerStateHistory
import org.jetbrains.kotlin.daemon.common.ReplStateFacadeAsync
import org.jetbrains.kotlin.daemon.common.experimental.ReplStateFacadeClientSide
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantReadWriteLock
import org.jetbrains.kotlin.daemon.client.RemoteReplCompilerState
import org.jetbrains.kotlin.daemon.common.toRMI
class RemoteReplCompilerStateHistoryAsync(private val state: RemoteReplCompilerStateAsync) : IReplStageHistory<Unit>,
AbstractList<ReplHistoryRecord<Unit>>() {
override val size: Int
get() = runBlocking { state.replStateFacade.getHistorySize() }
override fun get(index: Int): ReplHistoryRecord<Unit> = runBlocking {
ReplHistoryRecord(state.replStateFacade.historyGet(index), Unit)
}
override fun push(id: ILineId, item: Unit) {
throw NotImplementedError("push to remote history is not supported")
}
override fun pop(): ReplHistoryRecord<Unit>? {
throw NotImplementedError("pop from remote history is not supported")
}
override fun reset(): Iterable<ILineId> = runBlocking {
state.replStateFacade.historyReset().apply {
currentGeneration.incrementAndGet()
}
}
override fun resetTo(id: ILineId): Iterable<ILineId> = runBlocking {
state.replStateFacade.historyResetTo(id).apply {
currentGeneration.incrementAndGet()
}
}
val currentGeneration = AtomicInteger(REPL_CODE_LINE_FIRST_GEN)
override val lock: ReentrantReadWriteLock get() = state.lock
}
class RemoteReplCompilerStateAsync(
internal val replStateFacade: ReplStateFacadeAsync,
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : IReplStageState<Unit> {
override val currentGeneration: Int get() = (history as RemoteReplCompilerStateHistory).currentGeneration.get()
override val history: IReplStageHistory<Unit> =
RemoteReplCompilerStateHistoryAsync(this)
fun toRMI() = RemoteReplCompilerState(replStateFacade.toRMI(), lock)
}
@@ -34,6 +34,10 @@ dependencies {
nativePlatformVariants.forEach {
embedded(commonDep("net.rubygrapefruit", "native-platform", "-$it"))
}
runtime(project(":kotlin-reflect"))
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) {
isTransitive = false
}
}
sourceSets {
@@ -0,0 +1,105 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.client
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.daemon.client.KotlinCompilerDaemonClient.Companion.instantiate
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.common.ReportSeverity
import org.jetbrains.kotlin.daemon.common.DummyProfiler
import org.jetbrains.kotlin.daemon.common.Profiler
import java.io.File
data class CompileServiceSessionAsync(val compileService: CompileServiceAsync, val sessionId: Int)
fun CompileServiceSession.toAsync() = CompileServiceSessionAsync(this.compileService.toClient(), this.sessionId)
fun CompileServiceSessionAsync.toRMI() = CompileServiceSession(this.compileService.toRMI(), this.sessionId)
interface KotlinCompilerDaemonClient {
suspend fun connectToCompileService(
compilerId: CompilerId,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
reportingTargets: DaemonReportingTargets,
autostart: Boolean,
checkId: Boolean
): CompileServiceAsync?
suspend fun connectToCompileService(
compilerId: CompilerId,
clientAliveFlagFile: File,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
reportingTargets: DaemonReportingTargets,
autostart: Boolean
): CompileServiceAsync?
suspend fun connectAndLease(
compilerId: CompilerId,
clientAliveFlagFile: File,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
reportingTargets: DaemonReportingTargets,
autostart: Boolean,
leaseSession: Boolean,
sessionAliveFlagFile: File? = null
): CompileServiceSessionAsync?
suspend fun shutdownCompileService(compilerId: CompilerId, daemonOptions: DaemonOptions)
suspend fun leaseCompileSession(compilerService: CompileServiceAsync, aliveFlagPath: String?): Int
suspend fun releaseCompileSession(compilerService: CompileServiceAsync, sessionId: Int): Unit
suspend fun compile(
compilerService: CompileServiceAsync,
sessionId: Int,
targetPlatform: CompileService.TargetPlatform,
args: Array<out String>,
messageCollector: MessageCollector,
outputsCollector: ((File, List<File>) -> Unit)? = null,
compilerMode: CompilerMode = CompilerMode.NON_INCREMENTAL_COMPILER,
reportSeverity: ReportSeverity = ReportSeverity.INFO,
port: Int = SOCKET_ANY_FREE_PORT,
profiler: Profiler = DummyProfiler()
): Int
fun getOrCreateClientFlagFile(daemonOptions: DaemonOptions): File
fun createCompResults(): CompilationResultsAsync
fun main(vararg args: String)
companion object {
fun instantiate(daemonProtocolVariant: DaemonProtocolVariant): KotlinCompilerDaemonClient =
KotlinCompilerDaemonClient::class.java
.classLoader
.loadClass(
when(daemonProtocolVariant) {
DaemonProtocolVariant.RMI -> "org.jetbrains.kotlin.daemon.client.impls.KotlinCompilerClientImpl"
DaemonProtocolVariant.SOCKETS -> "org.jetbrains.kotlin.daemon.client.experimental.KotlinCompilerClient"
}
)
.newInstance() as KotlinCompilerDaemonClient
}
}
object KotlinCompilerClientInstance {
const val RMI_FLAG = "-old"
const val SOCKETS_FLAG = "-new_with_sockets"
@JvmStatic
fun main(vararg args: String) {
val clientInstance: KotlinCompilerDaemonClient? = when (args.last()) {
SOCKETS_FLAG ->
instantiate(DaemonProtocolVariant.SOCKETS)
else ->
instantiate(DaemonProtocolVariant.RMI)
}
clientInstance?.main(*args.sliceArray(0..args.lastIndex))
}
}
@@ -19,40 +19,40 @@ package org.jetbrains.kotlin.daemon.client
import org.jetbrains.kotlin.daemon.common.DaemonReportCategory
import java.io.IOException
private class NativePlatformLauncherWrapper {
class NativePlatformLauncherWrapper {
private val nativeLauncher: net.rubygrapefruit.platform.ProcessLauncher by lazy {
net.rubygrapefruit.platform.Native.get(net.rubygrapefruit.platform.ProcessLauncher::class.java)
}
fun launch(processBuilder: ProcessBuilder): Process =
try {
nativeLauncher.start(processBuilder)
}
catch (e: net.rubygrapefruit.platform.NativeException) {
throw IOException(e)
}
try {
nativeLauncher.start(processBuilder)
}
catch (e: net.rubygrapefruit.platform.NativeException) {
throw IOException(e)
}
}
fun launchProcessWithFallback(processBuilder: ProcessBuilder, reportingTargets: DaemonReportingTargets, reportingSource: String = "process launcher"): Process =
try {
// A separate class to delay classloading until this point, where we can catch class loading errors in case then the native lib is not in the classpath
NativePlatformLauncherWrapper().launch(processBuilder)
}
catch (e: UnsatisfiedLinkError) {
reportingTargets.report(DaemonReportCategory.DEBUG, "Could not start process with native process launcher, falling back to ProcessBuilder#start ($e)", reportingSource)
null
}
catch (e: IOException) {
reportingTargets.report(DaemonReportCategory.DEBUG, "Could not start process with native process launcher, falling back to ProcessBuilder#start (${e.cause})", reportingSource)
null
}
catch (e: NoClassDefFoundError) {
reportingTargets.report(DaemonReportCategory.DEBUG, "net.rubygrapefruit.platform library is not in the classpath, falling back to ProcessBuilder#start ($e)", reportingSource)
null
}
catch (e: ClassNotFoundException) {
reportingTargets.report(DaemonReportCategory.DEBUG, "net.rubygrapefruit.platform library is not in the classpath, falling back to ProcessBuilder#start ($e)", reportingSource)
null
}
try {
// A separate class to delay classloading until this point, where we can catch class loading errors in case then the native lib is not in the classpath
NativePlatformLauncherWrapper().launch(processBuilder)
}
catch (e: UnsatisfiedLinkError) {
reportingTargets.report(DaemonReportCategory.DEBUG, "Could not start process with native process launcher, falling back to ProcessBuilder#start ($e)", reportingSource)
null
}
catch (e: IOException) {
reportingTargets.report(DaemonReportCategory.DEBUG, "Could not start process with native process launcher, falling back to ProcessBuilder#start (${e.cause})", reportingSource)
null
}
catch (e: NoClassDefFoundError) {
reportingTargets.report(DaemonReportCategory.DEBUG, "net.rubygrapefruit.platform library is not in the classpath, falling back to ProcessBuilder#start ($e)", reportingSource)
null
}
catch (e: ClassNotFoundException) {
reportingTargets.report(DaemonReportCategory.DEBUG, "net.rubygrapefruit.platform library is not in the classpath, falling back to ProcessBuilder#start ($e)", reportingSource)
null
}
?: processBuilder.start()
@@ -50,7 +50,10 @@ class RemoteReplCompilerStateHistory(private val state: RemoteReplCompilerState)
override val lock: ReentrantReadWriteLock get() = state.lock
}
class RemoteReplCompilerState(internal val replStateFacade: ReplStateFacade, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : IReplStageState<Unit> {
class RemoteReplCompilerState(
internal val replStateFacade: ReplStateFacade,
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : IReplStageState<Unit> {
override val currentGeneration: Int get() = (history as RemoteReplCompilerStateHistory).currentGeneration.get()
@@ -0,0 +1,125 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.client.impls
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.daemon.client.*
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.common.ReportSeverity
import org.jetbrains.kotlin.daemon.common.Profiler
import java.io.File
object KotlinCompilerClientImpl : KotlinCompilerDaemonClient {
val oldClient = KotlinCompilerClient
override suspend fun connectToCompileService(
compilerId: CompilerId,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
reportingTargets: DaemonReportingTargets,
autostart: Boolean,
checkId: Boolean
): CompileServiceAsync? = oldClient.connectToCompileService(
compilerId,
daemonJVMOptions,
daemonOptions,
reportingTargets,
autostart,
checkId
)?.toClient()
override suspend fun connectToCompileService(
compilerId: CompilerId,
clientAliveFlagFile: File,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
reportingTargets: DaemonReportingTargets,
autostart: Boolean
): CompileServiceAsync? = oldClient.connectToCompileService(
compilerId,
clientAliveFlagFile,
daemonJVMOptions,
daemonOptions,
reportingTargets,
autostart
)?.toClient()
override suspend fun connectAndLease(
compilerId: CompilerId,
clientAliveFlagFile: File,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
reportingTargets: DaemonReportingTargets,
autostart: Boolean,
leaseSession: Boolean,
sessionAliveFlagFile: File?
): CompileServiceSessionAsync? = oldClient.connectAndLease(
compilerId,
clientAliveFlagFile,
daemonJVMOptions,
daemonOptions,
reportingTargets,
autostart,
leaseSession,
sessionAliveFlagFile
)?.toAsync()
override suspend fun shutdownCompileService(
compilerId: CompilerId,
daemonOptions: DaemonOptions
) = oldClient.shutdownCompileService(
compilerId,
daemonOptions
)
override suspend fun leaseCompileSession(
compilerService: CompileServiceAsync,
aliveFlagPath: String?
) = oldClient.leaseCompileSession(
compilerService.toRMI(),
aliveFlagPath
)
override suspend fun releaseCompileSession(
compilerService: CompileServiceAsync,
sessionId: Int
) = oldClient.releaseCompileSession(
compilerService.toRMI(),
sessionId
)
override suspend fun compile(
compilerService: CompileServiceAsync,
sessionId: Int,
targetPlatform: CompileService.TargetPlatform,
args: Array<out String>,
messageCollector: MessageCollector,
outputsCollector: ((File, List<File>) -> Unit)?,
compilerMode: CompilerMode,
reportSeverity: ReportSeverity,
port: Int,
profiler: Profiler
): Int = oldClient.compile(
compilerService.toRMI(),
sessionId,
targetPlatform,
args,
messageCollector,
outputsCollector,
compilerMode,
reportSeverity,
port,
profiler
)
override fun getOrCreateClientFlagFile(daemonOptions: DaemonOptions): File = oldClient.getOrCreateClientFlagFile(daemonOptions)
override fun createCompResults() = TODO("not implemented")
override fun main(vararg args: String) = oldClient.main(*args)
}
@@ -0,0 +1,46 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
import com.sun.javafx.scene.CameraHelper.project
plugins {
java
kotlin("jvm")
id("jps-compatible")
}
jvmTarget = "1.6"
val ktorExcludesForDaemon : List<Pair<String, String>> by rootProject.extra
dependencies {
compile(project(":core:descriptors"))
compile(project(":core:descriptors.jvm"))
compile(project(":compiler:util"))
compile(project(":compiler:cli-common"))
compileOnly(project(":compiler:daemon-common"))
compile(kotlinStdlib())
compileOnly(project(":js:js.frontend"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
compileOnly(intellijDep()) { includeIntellijCoreJarDependencies(project) }
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) {
isTransitive = false
}
compile(commonDep("io.ktor", "ktor-network")) {
ktorExcludesForDaemon.forEach { (group, module) ->
exclude(group = group, module = module)
}
}
}
sourceSets {
"main" { projectDefault() }
"test" {}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import java.util.concurrent.TimeUnit
val RMI_WRAPPER_PORTS_RANGE_START: Int = 13001
val RMI_WRAPPER_PORTS_RANGE_END: Int = 14000
val REPL_SERVER_PORTS_RANGE_START: Int = 14001
val REPL_SERVER_PORTS_RANGE_END: Int = 15000
val CALLBACK_SERVER_PORTS_RANGE_START: Int = 15001
val CALLBACK_SERVER_PORTS_RANGE_END: Int = 16000
val RESULTS_SERVER_PORTS_RANGE_START: Int = 16001
val RESULTS_SERVER_PORTS_RANGE_END: Int = 17000
val COMPILER_DAEMON_CLASS_FQN_EXPERIMENTAL: String = "org.jetbrains.kotlin.daemon.experimental.KotlinCompileDaemon"
val FIRST_HANDSHAKE_BYTE_TOKEN = byteArrayOf(1, 2, 3, 4)
val AUTH_TIMEOUT_IN_MILLISECONDS = 200L
val DAEMON_PERIODIC_CHECK_INTERVAL_MS = 1000L
val DAEMON_PERIODIC_SELDOM_CHECK_INTERVAL_MS = 60000L
val KEEPALIVE_PERIOD = 2000L
val KEEPALIVE_PERIOD_SERVER = 4000L
@@ -0,0 +1,153 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import kotlinx.coroutines.*
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.runWithTimeout
import org.jetbrains.kotlin.daemon.common.DaemonReportCategory
import org.jetbrains.kotlin.daemon.common.makePortFromRunFilenameExtractor
import java.io.File
import java.rmi.registry.LocateRegistry
import java.util.logging.Logger
/*
1) walkDaemonsAsync = walkDaemons + some async calls inside (also some used classes changed *** -> ***Async)
2) tryConnectToDaemonBySockets / tryConnectToDaemonByRMI
*/
internal val MAX_PORT_NUMBER = 0xffff
private const val ORPHANED_RUN_FILE_AGE_THRESHOLD_MS = 1000000L
data class DaemonWithMetadataAsync(val daemon: CompileServiceAsync, val runFile: File, val jvmOptions: DaemonJVMOptions)
val log = Logger.getLogger("client utils")
// TODO: replace mapNotNull in walkDaemonsAsync with this method.
private suspend fun <T, R : Any> List<T>.mapNotNullAsync(transform: suspend (T) -> R?): List<R> =
this
.map { GlobalScope.async { transform(it) } }
.mapNotNull { it.await() } // await for completion of the last action
// TODO: write metadata into discovery file to speed up selection
// TODO: consider using compiler jar signature (checksum) as a CompilerID (plus java version, plus ???) instead of classpath checksum
// would allow to use same compiler from taken different locations
// reqs: check that plugins (or anything els) should not be part of the CP
suspend fun walkDaemonsAsync(
registryDir: File,
compilerId: CompilerId,
fileToCompareTimestamp: File,
filter: (File, Int) -> Boolean = { _, _ -> true },
report: (DaemonReportCategory, String) -> Unit = { _, _ -> },
useRMI: Boolean = true,
useSockets: Boolean = true
): List<DaemonWithMetadataAsync> { // TODO: replace with Deferred<List<DaemonWithMetadataAsync>> and use mapNotNullAsync to speed this up
val classPathDigest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest().toHexString()
val portExtractor = makePortFromRunFilenameExtractor(classPathDigest)
return registryDir.walk().toList() // list, since walk returns Sequence and Sequence.map{...} is not inline => coroutines dont work
.map { Pair(it, portExtractor(it.name)) }
.filter { (file, port) -> port != null && filter(file, port) }
.mapNotNull { (file, port) ->
// all actions process concurrently
assert(port!! in 1..(MAX_PORT_NUMBER - 1))
val relativeAge = fileToCompareTimestamp.lastModified() - file.lastModified()
val daemon = tryConnectToDaemonAsync(port, report, file, useRMI, useSockets)
// cleaning orphaned file; note: daemon should shut itself down if it detects that the runServer file is deleted
if (daemon == null) {
if (relativeAge - ORPHANED_RUN_FILE_AGE_THRESHOLD_MS <= 0) {
report(
DaemonReportCategory.DEBUG,
"found fresh runServer file '${file.absolutePath}' ($relativeAge ms old), but no daemon, ignoring it"
)
} else {
report(
DaemonReportCategory.DEBUG,
"found seemingly orphaned runServer file '${file.absolutePath}' ($relativeAge ms old), deleting it"
)
if (!file.delete()) {
report(
DaemonReportCategory.INFO,
"WARNING: unable to delete seemingly orphaned file '${file.absolutePath}', cleanup recommended"
)
}
}
}
try {
daemon?.let {
DaemonWithMetadataAsync(it, file, it.getDaemonJVMOptions().get())
}
} catch (e: Exception) {
report(
DaemonReportCategory.INFO,
"ERROR: unable to retrieve daemon JVM options, assuming daemon is dead: ${e.message}"
)
null
}
}
}
private inline fun tryConnectToDaemonByRMI(port: Int, report: (DaemonReportCategory, String) -> Unit): CompileServiceAsync? {
try {
log.info("tryConnectToDaemonByRMI(port = $port)")
val daemon = runBlocking {
runWithTimeout(2 * DAEMON_PERIODIC_CHECK_INTERVAL_MS) {
LocateRegistry.getRegistry(
LoopbackNetworkInterface.loopbackInetAddressName,
port,
LoopbackNetworkInterface.clientLoopbackSocketFactoryRMI
)?.lookup(COMPILER_SERVICE_RMI_NAME)
}
}
when (daemon) {
null -> report(DaemonReportCategory.INFO, "daemon not found")
is CompileService -> return daemon.toClient()
else -> report(DaemonReportCategory.INFO, "Unable to cast compiler service, actual class received: ${daemon::class.java.name}")
}
} catch (e: Throwable) {
report(DaemonReportCategory.INFO, "cannot connect to registry: " + (e.cause?.message ?: e.message ?: "unknown error"))
}
return null
}
private suspend fun tryConnectToDaemonBySockets(
port: Int,
file: File,
report: (DaemonReportCategory, String) -> Unit
): CompileServiceClientSide? {
return CompileServiceClientSideImpl(
port,
LoopbackNetworkInterface.loopbackInetAddressName,
file
).let { daemon ->
try {
log.info("tryConnectToDaemonBySockets(port = $port)")
log.info("daemon($port) = $daemon")
log.info("daemon($port) connecting to server...")
daemon.connectToServer()
log.info("OK - daemon($port) connected to server!!!")
daemon
} catch (e: Throwable) {
report(DaemonReportCategory.INFO, "kcannot find or connect to socket")
daemon.close()
null
}
}
}
private suspend fun tryConnectToDaemonAsync(
port: Int,
report: (DaemonReportCategory, String) -> Unit,
file: File,
useRMI: Boolean = true,
useSockets: Boolean = true
): CompileServiceAsync? =
useSockets.takeIf { it }?.let { tryConnectToDaemonBySockets(port, file, report) }
?: (useRMI.takeIf { it }?.let { tryConnectToDaemonByRMI(port, report) })
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.CompilationResultsAsync
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Client
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.DefaultClient
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Server
import java.io.Serializable
interface CompilationResultsServerSide : CompilationResultsAsync, Server<CompilationResultsServerSide> {
class AddMessage(
val compilationResultCategory: Int,
val value: Serializable
) : Server.Message<CompilationResultsServerSide>() {
override suspend fun processImpl(server: CompilationResultsServerSide, sendReply: (Any?) -> Unit) {
server.add(compilationResultCategory, value)
}
}
}
interface CompilationResultsClientSide : CompilationResultsAsync, Client<CompilationResultsServerSide>
class CompilationResultsClientSideImpl(val socketPort: Int) : CompilationResultsClientSide,
Client<CompilationResultsServerSide> by DefaultClient(socketPort) {
override val clientSide: CompilationResultsAsync
get() = this
override suspend fun add(compilationResultCategory: Int, value: Serializable) {
sendMessage(CompilationResultsServerSide.AddMessage(compilationResultCategory, value))
}
// TODO: consider connecting to server in init-block
}
enum class CompilationResultCategory(val code: Int) {
IC_COMPILE_ITERATION(0)
}
@@ -0,0 +1,15 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("EXPERIMENTAL_FEATURE_WARNING")
package org.jetbrains.kotlin.daemon.common
import org.jetbrains.kotlin.daemon.common.experimental.CompileServiceServerSide
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.*
interface CompileServiceClientSide : CompileServiceAsync, Client<CompileServiceServerSide> {
override val serverPort: Int
}
@@ -0,0 +1,393 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import kotlinx.coroutines.*
import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.*
import org.jetbrains.kotlin.daemon.common.CompileService
import org.jetbrains.kotlin.daemon.common.CompilerServicesFacadeBase
import java.io.File
import java.util.logging.Logger
class CompileServiceClientSideImpl(
override val serverPort: Int,
val serverHost: String,
val serverFile: File
) : CompileServiceClientSide,
Client<CompileServiceServerSide> by object : DefaultAuthorizableClient<CompileServiceServerSide>(
serverPort,
serverHost
) {
private fun nowMillieconds() = System.currentTimeMillis()
@Volatile
private var lastUsedMilliSeconds: Long = nowMillieconds()
private fun millisecondsSinceLastUsed() = nowMillieconds() - lastUsedMilliSeconds
private fun keepAliveSuccess() = millisecondsSinceLastUsed() < KEEPALIVE_PERIOD
override suspend fun authorizeOnServer(serverOutputChannel: ByteWriteChannelWrapper): Boolean =
runWithTimeout {
val signature = serverFile.inputStream().use(::readTokenKeyPairAndSign)
sendSignature(serverOutputChannel, signature)
true
} ?: false
override suspend fun clientHandshake(input: ByteReadChannelWrapper, output: ByteWriteChannelWrapper, log: Logger): Boolean {
return trySendHandshakeMessage(output, log) && tryAcquireHandshakeMessage(input, log)
}
override fun startKeepAlives() {
val keepAliveMessage = Server.KeepAliveMessage<CompileServiceServerSide>()
GlobalScope.async(newSingleThreadContext("keepAliveThread")) {
delay(KEEPALIVE_PERIOD * 4)
while (true) {
delay(KEEPALIVE_PERIOD)
while (keepAliveSuccess()) {
delay(KEEPALIVE_PERIOD - millisecondsSinceLastUsed())
}
runWithTimeout(timeout = KEEPALIVE_PERIOD / 2) {
val id = sendMessage(keepAliveMessage)
readMessage<Server.KeepAliveAcknowledgement<*>>(id)
} ?: if (!keepAliveSuccess()) readActor.send(StopAllRequests()).also {
}
}
}
}
override fun delayKeepAlives() {
lastUsedMilliSeconds = nowMillieconds()
}
} {
override suspend fun classesFqNamesByFiles(sessionId: Int, sourceFiles: Set<File>): CompileService.CallResult<Set<String>> {
val id = sendMessage(ClassesFqNamesByFilesMessage(sessionId, sourceFiles))
return readMessage(id)
}
override suspend fun compile(
sessionId: Int,
compilerArguments: Array<out String>,
compilationOptions: CompilationOptions,
servicesFacade: CompilerServicesFacadeBaseAsync,
compilationResults: CompilationResultsAsync?
): CompileService.CallResult<Int> {
val id = sendMessage(CompileMessage(
sessionId,
compilerArguments,
compilationOptions,
servicesFacade,
compilationResults
))
return readMessage(id)
}
override suspend fun leaseReplSession(
aliveFlagPath: String?,
compilerArguments: Array<out String>,
compilationOptions: CompilationOptions,
servicesFacade: CompilerServicesFacadeBaseAsync,
templateClasspath: List<File>,
templateClassName: String
): CompileService.CallResult<Int> {
val id = sendMessage(
LeaseReplSessionMessage(
aliveFlagPath,
compilerArguments,
compilationOptions,
servicesFacade,
templateClasspath,
templateClassName
)
)
return readMessage(id)
}
// CompileService methods:
override suspend fun checkCompilerId(expectedCompilerId: CompilerId): Boolean {
val id = sendMessage(
CheckCompilerIdMessage(
expectedCompilerId
)
)
return readMessage(id)
}
override suspend fun getUsedMemory(): CompileService.CallResult<Long> {
val id = sendMessage(GetUsedMemoryMessage())
return readMessage(id)
}
override suspend fun getDaemonOptions(): CompileService.CallResult<DaemonOptions> {
val id = sendMessage(GetDaemonOptionsMessage())
return readMessage(id)
}
override suspend fun getDaemonInfo(): CompileService.CallResult<String> {
val id = sendMessage(GetDaemonInfoMessage())
return readMessage(id)
}
override suspend fun getDaemonJVMOptions(): CompileService.CallResult<DaemonJVMOptions> {
val id = sendMessage(GetDaemonJVMOptionsMessage())
val res = readMessage<CompileService.CallResult<DaemonJVMOptions>>(id)
return res
}
override suspend fun registerClient(aliveFlagPath: String?): CompileService.CallResult<Nothing> {
val id = sendMessage(RegisterClientMessage(aliveFlagPath))
return readMessage(id)
}
override suspend fun getClients(): CompileService.CallResult<List<String>> {
val id = sendMessage(GetClientsMessage())
return readMessage(id)
}
override suspend fun leaseCompileSession(aliveFlagPath: String?): CompileService.CallResult<Int> {
val id = sendMessage(
LeaseCompileSessionMessage(
aliveFlagPath
)
)
return readMessage(id)
}
override suspend fun releaseCompileSession(sessionId: Int): CompileService.CallResult<Nothing> {
val id = sendMessage(
ReleaseCompileSessionMessage(
sessionId
)
)
return readMessage(id)
}
override suspend fun shutdown(): CompileService.CallResult<Nothing> {
val id = sendMessage(ShutdownMessage())
val res = readMessage<CompileService.CallResult<Nothing>>(id)
return res
}
override suspend fun scheduleShutdown(graceful: Boolean): CompileService.CallResult<Boolean> {
val id = sendMessage(ScheduleShutdownMessage(graceful))
return readMessage(id)
}
override suspend fun clearJarCache() {
val id = sendMessage(ClearJarCacheMessage())
}
override suspend fun releaseReplSession(sessionId: Int): CompileService.CallResult<Nothing> {
val id = sendMessage(ReleaseReplSessionMessage(sessionId))
return readMessage(id)
}
override suspend fun replCreateState(sessionId: Int): CompileService.CallResult<ReplStateFacadeAsync> {
val id = sendMessage(ReplCreateStateMessage(sessionId))
return readMessage(id)
}
override suspend fun replCheck(
sessionId: Int,
replStateId: Int,
codeLine: ReplCodeLine
): CompileService.CallResult<ReplCheckResult> {
val id = sendMessage(
ReplCheckMessage(
sessionId,
replStateId,
codeLine
)
)
return readMessage(id)
}
override suspend fun replCompile(
sessionId: Int,
replStateId: Int,
codeLine: ReplCodeLine
): CompileService.CallResult<ReplCompileResult> {
val id = sendMessage(
ReplCompileMessage(
sessionId,
replStateId,
codeLine
)
)
return readMessage(id)
}
// Query messages:
class CheckCompilerIdMessage(val expectedCompilerId: CompilerId) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.checkCompilerId(expectedCompilerId))
}
class GetUsedMemoryMessage : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.getUsedMemory())
}
class GetDaemonOptionsMessage : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.getDaemonOptions())
}
class GetDaemonJVMOptionsMessage : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.getDaemonJVMOptions())
}
class GetDaemonInfoMessage : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.getDaemonInfo())
}
class RegisterClientMessage(val aliveFlagPath: String?) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.registerClient(aliveFlagPath))
}
class GetClientsMessage : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.getClients())
}
class LeaseCompileSessionMessage(val aliveFlagPath: String?) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.leaseCompileSession(aliveFlagPath))
}
class ReleaseCompileSessionMessage(val sessionId: Int) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.releaseCompileSession(sessionId))
}
class ShutdownMessage : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.shutdown())
}
class ScheduleShutdownMessage(val graceful: Boolean) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.scheduleShutdown(graceful))
}
class CompileMessage(
val sessionId: Int,
val compilerArguments: Array<out String>,
val compilationOptions: CompilationOptions,
val servicesFacade: CompilerServicesFacadeBaseAsync,
val compilationResults: CompilationResultsAsync?
) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(
server.compile(
sessionId,
compilerArguments,
compilationOptions,
servicesFacade,
compilationResults
)
)
}
class ClassesFqNamesByFilesMessage(
val sessionId: Int,
val sourceFiles: Set<File>
) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(
server.classesFqNamesByFiles(sessionId, sourceFiles)
)
}
class ClearJarCacheMessage : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
server.clearJarCache()
}
class LeaseReplSessionMessage(
val aliveFlagPath: String?,
val compilerArguments: Array<out String>,
val compilationOptions: CompilationOptions,
val servicesFacade: CompilerServicesFacadeBaseAsync,
val templateClasspath: List<File>,
val templateClassName: String
) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(
server.leaseReplSession(
aliveFlagPath,
compilerArguments,
compilationOptions,
servicesFacade,
templateClasspath,
templateClassName
)
)
}
class ReleaseReplSessionMessage(val sessionId: Int) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.releaseReplSession(sessionId))
}
class LeaseReplSession_Short_Message(
val aliveFlagPath: String?,
val compilerArguments: Array<out String>,
val compilationOptions: CompilationOptions,
val servicesFacade: CompilerServicesFacadeBase,
val templateClasspath: List<File>,
val templateClassName: String
) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(
server.leaseReplSession(
aliveFlagPath,
compilerArguments,
compilationOptions,
servicesFacade.toClient(),
templateClasspath,
templateClassName
)
)
}
class ReplCreateStateMessage(val sessionId: Int) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.replCreateState(sessionId))
}
class ReplCheckMessage(
val sessionId: Int,
val replStateId: Int,
val codeLine: ReplCodeLine
) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.replCheck(sessionId, replStateId, codeLine))
}
class ReplCompileMessage(
val sessionId: Int,
val replStateId: Int,
val codeLine: ReplCodeLine
) : Server.Message<CompileServiceServerSide>() {
override suspend fun processImpl(server: CompileServiceServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.replCompile(sessionId, replStateId, codeLine))
}
}
@@ -0,0 +1,234 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.common.LoopbackNetworkInterface
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Client
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.DefaultClientRMIWrapper
import org.jetbrains.kotlin.daemon.common.*
import java.io.File
import java.io.Serializable
import java.rmi.NoSuchObjectException
import java.rmi.server.UnicastRemoteObject
import java.util.*
import java.util.logging.Logger
class CompileServiceRMIWrapper(val server: CompileServiceServerSide, daemonOptions: DaemonOptions, compilerId: CompilerId) :
CompileService {
override fun classesFqNamesByFiles(sessionId: Int, sourceFiles: Set<File>) = runBlocking {
server.classesFqNamesByFiles(sessionId, sourceFiles)
}
val log = Logger.getLogger("CompileServiceRMIWrapper")
private fun deprecated(): Nothing = TODO("NEVER USE DEPRECATED METHODS, PLEASE!") // prints this todo message
override fun checkCompilerId(expectedCompilerId: CompilerId) = runBlocking {
server.checkCompilerId(expectedCompilerId)
}
override fun getUsedMemory() = runBlocking {
server.getUsedMemory()
}
override fun getDaemonOptions() = runBlocking {
server.getDaemonOptions()
}
override fun getDaemonInfo() = runBlocking {
server.getDaemonInfo()
}
override fun getDaemonJVMOptions() = runBlocking {
server.getDaemonJVMOptions()
}
override fun registerClient(aliveFlagPath: String?) = runBlocking {
server.registerClient(aliveFlagPath)
}
override fun getClients() = runBlocking {
server.getClients()
}
override fun leaseCompileSession(aliveFlagPath: String?) = runBlocking {
server.leaseCompileSession(aliveFlagPath)
}
override fun releaseCompileSession(sessionId: Int) = runBlocking {
server.releaseCompileSession(sessionId)
}
override fun shutdown() = runBlocking {
server.shutdown()
}
override fun scheduleShutdown(graceful: Boolean) = runBlocking {
server.scheduleShutdown(graceful)
}
override fun remoteCompile(
sessionId: Int,
targetPlatform: CompileService.TargetPlatform,
args: Array<out String>,
servicesFacade: CompilerCallbackServicesFacade,
compilerOutputStream: RemoteOutputStream,
outputFormat: CompileService.OutputFormat,
serviceOutputStream: RemoteOutputStream,
operationsTracer: RemoteOperationsTracer?
) = deprecated()
override fun remoteIncrementalCompile(
sessionId: Int,
targetPlatform: CompileService.TargetPlatform,
args: Array<out String>,
servicesFacade: CompilerCallbackServicesFacade,
compilerOutputStream: RemoteOutputStream,
compilerOutputFormat: CompileService.OutputFormat,
serviceOutputStream: RemoteOutputStream,
operationsTracer: RemoteOperationsTracer?
) = deprecated()
override fun compile(
sessionId: Int,
compilerArguments: Array<out String>,
compilationOptions: CompilationOptions,
servicesFacade: CompilerServicesFacadeBase,
compilationResults: CompilationResults?
) = runBlocking {
server.compile(
sessionId,
compilerArguments,
compilationOptions,
servicesFacade.toClient(),
compilationResults?.toClient() ?: object : CompilationResultsClientSide,
Client<CompilationResultsServerSide> by DefaultClientRMIWrapper() {
override val clientSide: CompilationResultsAsync
get() = this
override suspend fun add(compilationResultCategory: Int, value: Serializable) {}
}
)
}
override fun clearJarCache() = runBlocking {
server.clearJarCache()
}
override fun leaseReplSession(
aliveFlagPath: String?,
targetPlatform: CompileService.TargetPlatform,
servicesFacade: CompilerCallbackServicesFacade,
templateClasspath: List<File>,
templateClassName: String,
scriptArgs: Array<out Any?>?,
scriptArgsTypes: Array<out Class<out Any>>?,
compilerMessagesOutputStream: RemoteOutputStream,
evalOutputStream: RemoteOutputStream?,
evalErrorStream: RemoteOutputStream?,
evalInputStream: RemoteInputStream?,
operationsTracer: RemoteOperationsTracer?
) = deprecated()
override fun releaseReplSession(sessionId: Int) = runBlocking {
server.releaseReplSession(sessionId)
}
override fun remoteReplLineCheck(sessionId: Int, codeLine: ReplCodeLine) = deprecated()
override fun remoteReplLineCompile(
sessionId: Int,
codeLine: ReplCodeLine,
history: List<ReplCodeLine>?
) = deprecated()
override fun remoteReplLineEval(
sessionId: Int,
codeLine: ReplCodeLine,
history: List<ReplCodeLine>?
) = deprecated()
override fun leaseReplSession(
aliveFlagPath: String?,
compilerArguments: Array<out String>,
compilationOptions: CompilationOptions,
servicesFacade: CompilerServicesFacadeBase,
templateClasspath: List<File>,
templateClassName: String
) = runBlocking {
server.leaseReplSession(
aliveFlagPath,
compilerArguments,
compilationOptions,
servicesFacade.toClient(),
templateClasspath,
templateClassName
)
}
override fun replCreateState(sessionId: Int) = runBlocking {
server.replCreateState(sessionId).toRMI()
}
override fun replCheck(sessionId: Int, replStateId: Int, codeLine: ReplCodeLine) = runBlocking {
server.replCheck(sessionId, replStateId, codeLine)
}
override fun replCompile(sessionId: Int, replStateId: Int, codeLine: ReplCodeLine) = runBlocking {
server.replCompile(sessionId, replStateId, codeLine)
}
init {
try {
// cleanup for the case of incorrect restart and many other situations
UnicastRemoteObject.unexportObject(this, false)
} catch (e: NoSuchObjectException) {
// ignoring if object already exported
}
val (registry, port) = findPortAndCreateRegistry(
COMPILE_DAEMON_FIND_PORT_ATTEMPTS,
RMI_WRAPPER_PORTS_RANGE_START,
RMI_WRAPPER_PORTS_RANGE_END
)
val stub = UnicastRemoteObject.exportObject(
this,
port,
LoopbackNetworkInterface.clientLoopbackSocketFactory,
LoopbackNetworkInterface.serverLoopbackSocketFactory
) as CompileService
registry.rebind(COMPILER_SERVICE_RMI_NAME, stub)
// create file :
val runFileDir = File(daemonOptions.runFilesPathOrDefault)
runFileDir.mkdirs()
val runFile = File(
runFileDir,
makeRunFilenameString(
timestamp = "%tFT%<tH-%<tM-%<tS.%<tLZ".format(Calendar.getInstance(TimeZone.getTimeZone("Z"))),
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest().toHexString(),
port = port.toString()
)
)
try {
if (!runFile.createNewFile()) throw Exception("createNewFile returned false")
} catch (e: Throwable) {
throw IllegalStateException("Unable to create runServer file '${runFile.absolutePath}'", e)
}
runFile.deleteOnExit()
}
}
fun CompileServiceServerSide.toRMIServer(daemonOptions: DaemonOptions, compilerId: CompilerId) =
CompileServiceRMIWrapper(this, daemonOptions, compilerId)
@@ -0,0 +1,13 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.CompileServiceAsync
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Server
interface CompileServiceServerSide : CompileServiceAsync, Server<CompileServiceServerSide> {
override val serverPort: Int
}
@@ -0,0 +1,104 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.CompilerCallbackServicesFacadeAsync
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Client
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.DefaultClient
import org.jetbrains.kotlin.incremental.components.LookupInfo
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
import org.jetbrains.kotlin.modules.TargetId
import java.io.Serializable
interface CompilerCallbackServicesFacadeClientSide : CompilerCallbackServicesFacadeAsync, Client<CompilerServicesFacadeBaseServerSide>, CompilerServicesFacadeBaseClientSide
@Suppress("UNCHECKED_CAST")
class CompilerCallbackServicesFacadeClientSideImpl(serverPort: Int) : CompilerCallbackServicesFacadeClientSide,
Client<CompilerServicesFacadeBaseServerSide> by DefaultClient(serverPort) {
override suspend fun hasIncrementalCaches(): Boolean {
val id = sendMessage(CompilerCallbackServicesFacadeServerSide.HasIncrementalCachesMessage())
return readMessage(id)
}
override suspend fun hasLookupTracker(): Boolean {
val id = sendMessage(CompilerCallbackServicesFacadeServerSide.HasLookupTrackerMessage())
return readMessage(id)
}
override suspend fun hasCompilationCanceledStatus(): Boolean {
val id = sendMessage(CompilerCallbackServicesFacadeServerSide.HasCompilationCanceledStatusMessage())
return readMessage(id)
}
override suspend fun incrementalCache_getObsoletePackageParts(target: TargetId): Collection<String> {
val id = sendMessage(CompilerCallbackServicesFacadeServerSide.IncrementalCache_getObsoletePackagePartsMessage(target))
return readMessage(id)
}
override suspend fun incrementalCache_getObsoleteMultifileClassFacades(target: TargetId): Collection<String> {
val id = sendMessage(CompilerCallbackServicesFacadeServerSide.IncrementalCache_getObsoleteMultifileClassFacadesMessage(target))
return readMessage(id)
}
override suspend fun incrementalCache_getPackagePartData(target: TargetId, partInternalName: String): JvmPackagePartProto? {
val id = sendMessage(CompilerCallbackServicesFacadeServerSide.IncrementalCache_getPackagePartDataMessage(target, partInternalName))
return readMessage(id)
}
override suspend fun incrementalCache_getModuleMappingData(target: TargetId): ByteArray? {
val id = sendMessage(CompilerCallbackServicesFacadeServerSide.IncrementalCache_getModuleMappingDataMessage(target))
return readMessage(id)
}
override suspend fun incrementalCache_registerInline(target: TargetId, fromPath: String, jvmSignature: String, toPath: String) {
sendNoReplyMessage(
CompilerCallbackServicesFacadeServerSide.IncrementalCache_registerInlineMessage(
target,
fromPath,
jvmSignature,
toPath
)
)
}
override suspend fun incrementalCache_getClassFilePath(target: TargetId, internalClassName: String): String {
val id = sendMessage(CompilerCallbackServicesFacadeServerSide.IncrementalCache_getClassFilePathMessage(target, internalClassName))
return readMessage(id)
}
override suspend fun incrementalCache_close(target: TargetId) =
sendNoReplyMessage(CompilerCallbackServicesFacadeServerSide.IncrementalCache_closeMessage(target))
override suspend fun incrementalCache_getMultifileFacadeParts(target: TargetId, internalName: String): Collection<String>? {
val id = sendMessage(CompilerCallbackServicesFacadeServerSide.IncrementalCache_getMultifileFacadePartsMessage(target, internalName))
return readMessage(id)
}
override suspend fun lookupTracker_requiresPosition(): Boolean {
val id = sendMessage(CompilerCallbackServicesFacadeServerSide.LookupTracker_requiresPositionMessage())
return readMessage(id)
}
override fun lookupTracker_record(lookups: Collection<LookupInfo>) =
sendNoReplyMessage(CompilerCallbackServicesFacadeServerSide.LookupTracker_recordMessage(lookups))
override suspend fun lookupTracker_isDoNothing(): Boolean {
val id = sendMessage(CompilerCallbackServicesFacadeServerSide.LookupTracker_isDoNothingMessage())
return readMessage(id)
}
override suspend fun compilationCanceledStatus_checkCanceled(): Void? {
sendNoReplyMessage(CompilerCallbackServicesFacadeServerSide.CompilationCanceledStatus_checkCanceledMessage())
return null
}
override suspend fun report(category: Int, severity: Int, message: String?, attachment: Serializable?) {
sendNoReplyMessage(CompilerServicesFacadeBaseServerSide.ReportMessage(category, severity, message, attachment))
}
}
@@ -0,0 +1,104 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.CompilerCallbackServicesFacadeAsync
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Server.Message
import org.jetbrains.kotlin.incremental.components.LookupInfo
import org.jetbrains.kotlin.modules.TargetId
interface CompilerCallbackServicesFacadeServerSide : CompilerCallbackServicesFacadeAsync, CompilerServicesFacadeBaseServerSide {
class HasIncrementalCachesMessage : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.hasIncrementalCaches())
}
class HasLookupTrackerMessage : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.hasLookupTracker())
}
class HasCompilationCanceledStatusMessage : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.hasCompilationCanceledStatus())
}
// ----------------------------------------------------
// IncrementalCache
class IncrementalCache_getObsoletePackagePartsMessage(val target: TargetId) : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.incrementalCache_getObsoletePackageParts(target))
}
class IncrementalCache_getObsoleteMultifileClassFacadesMessage(val target: TargetId) : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.incrementalCache_getObsoleteMultifileClassFacades(target))
}
class IncrementalCache_getPackagePartDataMessage(val target: TargetId, val partInternalName: String) : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.incrementalCache_getPackagePartData(target, partInternalName))
}
class IncrementalCache_getModuleMappingDataMessage(val target: TargetId) : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.incrementalCache_getModuleMappingData(target))
}
class IncrementalCache_registerInlineMessage(
val target: TargetId,
val fromPath: String,
val jvmSignature: String,
val toPath: String
) : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
server.incrementalCache_registerInline(target, fromPath, jvmSignature, toPath)
}
class IncrementalCache_getClassFilePathMessage(val target: TargetId, val internalClassName: String) : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.incrementalCache_getClassFilePath(target, internalClassName))
}
class IncrementalCache_closeMessage(val target: TargetId) : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
server.incrementalCache_close(target)
}
class IncrementalCache_getMultifileFacadePartsMessage(val target: TargetId, val internalName: String) : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.incrementalCache_getMultifileFacadeParts(target, internalName))
}
// ----------------------------------------------------
// LookupTracker
class LookupTracker_requiresPositionMessage : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) {
server.lookupTracker_requiresPosition()
}
}
class LookupTracker_recordMessage(val lookups: Collection<LookupInfo>) : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.lookupTracker_record(lookups))
}
class LookupTracker_isDoNothingMessage : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.lookupTracker_isDoNothing())
}
// ----------------------------------------------------
// CompilationCanceledStatus
class CompilationCanceledStatus_checkCanceledMessage : Message<CompilerCallbackServicesFacadeServerSide>() {
override suspend fun processImpl(server: CompilerCallbackServicesFacadeServerSide, sendReply: (Any?) -> Unit) {
server.compilationCanceledStatus_checkCanceled()
}
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.CompilerServicesFacadeBaseAsync
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Client
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.DefaultClient
import java.io.Serializable
interface CompilerServicesFacadeBaseClientSide : CompilerServicesFacadeBaseAsync, Client<CompilerServicesFacadeBaseServerSide>
class CompilerServicesFacadeBaseClientSideImpl(val serverPort: Int) :
CompilerServicesFacadeBaseClientSide,
Client<CompilerServicesFacadeBaseServerSide> by DefaultClient(serverPort) {
// TODO: consider invoking connectToServer() in init block
override suspend fun report(category: Int, severity: Int, message: String?, attachment: Serializable?) {
log.info("client $serverPort - fun report")
sendNoReplyMessage(
CompilerServicesFacadeBaseServerSide.ReportMessage(
category, severity, message, attachment
)
)
}
}
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.CompilerServicesFacadeBaseAsync
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Server
import java.io.Serializable
interface CompilerServicesFacadeBaseServerSide : CompilerServicesFacadeBaseAsync, Server<CompilerServicesFacadeBaseServerSide> {
class ReportMessage(
val category: Int,
val severity: Int,
val message: String?,
val attachment: Serializable?
) : Server.Message<CompilerServicesFacadeBaseServerSide>() {
override suspend fun processImpl(server: CompilerServicesFacadeBaseServerSide, sendReply: (Any?) -> Unit) {
server.report(category, severity, message, attachment)
}
}
}
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.IncrementalCompilerServicesFacade
import org.jetbrains.kotlin.daemon.common.SimpleDirtyData
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Client
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.DefaultClientRMIWrapper
import java.io.File
import java.io.Serializable
class IncrementalCompilerServicesFacadeAsyncWrapper(
val rmiImpl: IncrementalCompilerServicesFacade
) : IncrementalCompilerServicesFacadeClientSide, Client<CompilerServicesFacadeBaseServerSide> by DefaultClientRMIWrapper() {
override suspend fun report(category: Int, severity: Int, message: String?, attachment: Serializable?) =
rmiImpl.report(category, severity, message, attachment)
}
fun IncrementalCompilerServicesFacade.toClient() = IncrementalCompilerServicesFacadeAsyncWrapper(this)
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.IncrementalCompilerServicesFacadeAsync
import org.jetbrains.kotlin.daemon.common.SimpleDirtyData
import org.jetbrains.kotlin.daemon.common.experimental.IncrementalCompilerServicesFacadeServerSide.*
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Client
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.DefaultClient
import java.io.File
import java.io.Serializable
interface IncrementalCompilerServicesFacadeClientSide : IncrementalCompilerServicesFacadeAsync, CompilerServicesFacadeBaseClientSide
class IncrementalCompilerServicesFacadeClientSideImpl(val serverPort: Int) :
IncrementalCompilerServicesFacadeClientSide,
Client<CompilerServicesFacadeBaseServerSide> by DefaultClient(serverPort) {
override suspend fun report(category: Int, severity: Int, message: String?, attachment: Serializable?) {
sendNoReplyMessage(CompilerServicesFacadeBaseServerSide.ReportMessage(category, severity, message, attachment))
}
}
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.daemon.common.IncrementalCompilerServicesFacade
import org.jetbrains.kotlin.daemon.common.SimpleDirtyData
import java.io.File
import java.io.Serializable
class IncrementalCompilerServicesFacadeRMIWrapper(val clientSide: IncrementalCompilerServicesFacadeClientSide) :
IncrementalCompilerServicesFacade, Serializable {
override fun report(category: Int, severity: Int, message: String?, attachment: Serializable?) = runBlocking {
clientSide.report(category, severity, message, attachment)
}
// TODO: consider connecting to server right here in init-block
}
fun IncrementalCompilerServicesFacadeClientSide.toRMI() =
if (this is IncrementalCompilerServicesFacadeAsyncWrapper) this.rmiImpl
else IncrementalCompilerServicesFacadeRMIWrapper(this)
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.IncrementalCompilerServicesFacadeAsync
import org.jetbrains.kotlin.daemon.common.SimpleDirtyData
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Server
import java.io.File
interface IncrementalCompilerServicesFacadeServerSide : IncrementalCompilerServicesFacadeAsync, CompilerServicesFacadeBaseServerSide
@@ -0,0 +1,137 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import io.ktor.network.selector.ActorSelectorManager
import io.ktor.network.sockets.aSocket
import kotlinx.coroutines.*
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.ServerSocketWrapper
import org.jetbrains.kotlin.daemon.common.*
import java.io.IOException
import java.io.Serializable
import java.net.*
import java.rmi.server.RMIClientSocketFactory
import java.rmi.server.RMIServerSocketFactory
import java.util.*
// copyed from original(org.jetbrains.kotlin.daemon.common.NetworkUtils) TODO
// unique part :
// - AbstractClientLoopbackSocketFactory / ServerLoopbackSocketFactoryRMI / ServerLoopbackSocketFactoryKtor - Ktor-Sockets instead of java sockets
// - findPortAndCreateSocket
// TODO: get rid of copy-paste here
object LoopbackNetworkInterface {
const val IPV4_LOOPBACK_INET_ADDRESS = "127.0.0.1"
const val IPV6_LOOPBACK_INET_ADDRESS = "::1"
// 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 SERVER_SOCKET_BACKLOG_SIZE by lazy {
System.getProperty(DAEMON_RMI_SOCKET_BACKLOG_SIZE_PROPERTY)?.toIntOrNull() ?: DEFAULT_SERVER_SOCKET_BACKLOG_SIZE
}
val SOCKET_CONNECT_ATTEMPTS by lazy {
System.getProperty(DAEMON_RMI_SOCKET_CONNECT_ATTEMPTS_PROPERTY)?.toIntOrNull() ?: DEFAULT_SOCKET_CONNECT_ATTEMPTS
}
val SOCKET_CONNECT_INTERVAL_MS by lazy {
System.getProperty(DAEMON_RMI_SOCKET_CONNECT_INTERVAL_PROPERTY)?.toLongOrNull() ?: DEFAULT_SOCKET_CONNECT_INTERVAL_MS
}
val serverLoopbackSocketFactoryRMI by lazy { ServerLoopbackSocketFactoryRMI() }
val clientLoopbackSocketFactoryRMI by lazy { ClientLoopbackSocketFactoryRMI() }
val serverLoopbackSocketFactoryKtor by lazy { ServerLoopbackSocketFactoryKtor() }
val clientLoopbackSocketFactoryKtor by lazy { ClientLoopbackSocketFactoryKtor() }
// TODO switch to InetAddress.getLoopbackAddress on java 7+
val loopbackInetAddressName by lazy {
try {
if (InetAddress.getByName(null) 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 ServerLoopbackSocketFactoryRMI : 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): java.net.ServerSocket =
ServerSocket(port, SERVER_SOCKET_BACKLOG_SIZE, InetAddress.getByName(null))
}
val selectorMgr = ActorSelectorManager(Dispatchers.IO)
class ServerLoopbackSocketFactoryKtor : Serializable {
override fun equals(other: Any?): Boolean = other === this || super.equals(other)
override fun hashCode(): Int = super.hashCode()
@Throws(IOException::class)
fun createServerSocket(port: Int) =
aSocket(selectorMgr)
.tcp()
.bind(InetSocketAddress(InetAddress.getByName(null), port)) // TODO : NO BACKLOG SIZE CHANGE =(
}
abstract class AbstractClientLoopbackSocketFactory<SocketType> : Serializable {
override fun equals(other: Any?): Boolean = other === this || super.equals(other)
override fun hashCode(): Int = super.hashCode()
abstract protected fun socketCreate(host: String, port: Int): SocketType
@Throws(IOException::class)
fun createSocket(host: String, port: Int): SocketType {
var attemptsLeft = SOCKET_CONNECT_ATTEMPTS
while (true) {
try {
return socketCreate(host, port)
} catch (e: ConnectException) {
if (--attemptsLeft <= 0) throw e
}
Thread.sleep(SOCKET_CONNECT_INTERVAL_MS)
}
}
}
class ClientLoopbackSocketFactoryRMI : AbstractClientLoopbackSocketFactory<java.net.Socket>(), RMIClientSocketFactory {
override fun socketCreate(host: String, port: Int): Socket = Socket(InetAddress.getByName(null), port)
}
class ClientLoopbackSocketFactoryKtor : AbstractClientLoopbackSocketFactory<io.ktor.network.sockets.Socket>() {
override fun socketCreate(host: String, port: Int): io.ktor.network.sockets.Socket =
runBlocking { aSocket(selectorMgr).tcp().connect(InetSocketAddress(host, port)) }
}
}
private val portSelectionRng = Random()
fun findPortForSocket(attempts: Int, portRangeStart: Int, portRangeEnd: Int): ServerSocketWrapper {
var i = 0
var lastException: Exception? = null
while (i++ < attempts) {
val port = portSelectionRng.nextInt(portRangeEnd - portRangeStart) + portRangeStart
try {
return ServerSocketWrapper(
port,
LoopbackNetworkInterface.serverLoopbackSocketFactoryKtor.createServerSocket(port)
)
} catch (e: Exception) {
// assuming that the socketPort is already taken
lastException = e
}
}
throw IllegalStateException("Cannot find free socketPort in $attempts attempts", lastException)
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
interface RemoteOutputStreamAsync {
/** closeStream() name is chosen since Clients are AutoClosable now
* and Client-implementations of RemoteOutputStreamAsync have conflict of 'close' name **/
suspend fun closeStream()
suspend fun write(data: ByteArray, offset: Int, length: Int)
suspend fun write(dataByte: Int)
}
interface RemoteInputStreamAsync {
/** closeStream() name is chosen since Clients are AutoClosable now
* and Client-implementations of RemoteInputStreamAsync have conflict of 'close' name **/
suspend fun closeStream()
suspend fun read(length: Int): ByteArray
suspend fun read(): Int
}
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Client
interface RemoteOutputStreamAsyncClientSide : RemoteOutputStreamAsync, Client<RemoteOutputStreamAsyncServerSide>
interface RemoteInputStreamClientSide : RemoteInputStreamAsync, Client<RemoteInputStreamServerSide>
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.ByteWriteChannelWrapper
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Server
interface RemoteOutputStreamAsyncServerSide : RemoteOutputStreamAsync, Server<RemoteOutputStreamAsyncServerSide> {
// Query messages:
class CloseMessage : Server.Message<RemoteOutputStreamAsyncServerSide>() {
override suspend fun processImpl(server: RemoteOutputStreamAsyncServerSide, sendReply: (Any?) -> Unit) =
server.closeStream()
}
class WriteMessage(val data: ByteArray, val offset: Int = -1, val length: Int = -1) :
Server.Message<RemoteOutputStreamAsyncServerSide>() {
override suspend fun processImpl(server: RemoteOutputStreamAsyncServerSide, sendReply: (Any?) -> Unit) =
server.write(data, offset, length)
}
class WriteIntMessage(val dataByte: Int) : Server.Message<RemoteOutputStreamAsyncServerSide>() {
override suspend fun processImpl(server: RemoteOutputStreamAsyncServerSide, sendReply: (Any?) -> Unit) =
server.write(dataByte)
}
}
interface RemoteInputStreamServerSide : RemoteInputStreamAsync, Server<RemoteInputStreamServerSide> {
// Query messages:
class CloseMessage : Server.Message<RemoteInputStreamServerSide>() {
override suspend fun processImpl(server: RemoteInputStreamServerSide, sendReply: (Any?) -> Unit) =
server.closeStream()
}
class ReadMessage(val length: Int = -1) : Server.Message<RemoteInputStreamServerSide>() {
override suspend fun processImpl(server: RemoteInputStreamServerSide, sendReply: (Any?) -> Unit) =
sendReply(if (length == -1) server.read() else server.read(length))
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.RemoteInputStream
import org.jetbrains.kotlin.daemon.common.RemoteOutputStream
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Client
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.DefaultClientRMIWrapper
class RemoteOutputStreamAsyncWrapper(val rmiOutput: RemoteOutputStream) : RemoteOutputStreamAsyncClientSide,
Client<RemoteOutputStreamAsyncServerSide> by DefaultClientRMIWrapper() {
override suspend fun closeStream() =
rmiOutput.close()
override suspend fun write(data: ByteArray, offset: Int, length: Int) =
rmiOutput.write(data, offset, length)
override suspend fun write(dataByte: Int) =
rmiOutput.write(dataByte)
}
class RemoteInputStreamAsyncWrapper(private val rmiInput: RemoteInputStream) : RemoteInputStreamClientSide,
Client<RemoteInputStreamServerSide> by DefaultClientRMIWrapper() {
override suspend fun closeStream() =
rmiInput.close()
override suspend fun read() =
rmiInput.read()
override suspend fun read(length: Int) =
rmiInput.read(length)
}
fun RemoteOutputStream.toClient() = RemoteOutputStreamAsyncWrapper(this)
fun RemoteInputStream.toClient() = RemoteInputStreamAsyncWrapper(this)
@@ -0,0 +1,11 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.ReplStateFacadeAsync
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Client
interface ReplStateFacadeClientSide: ReplStateFacadeAsync, Client<ReplStateFacadeServerSide>
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.cli.common.repl.ILineId
import org.jetbrains.kotlin.daemon.common.ReplStateFacadeAsync
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.ByteWriteChannelWrapper
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Server
interface ReplStateFacadeServerSide: ReplStateFacadeAsync, Server<ReplStateFacadeServerSide> {
// Query messages:
class GetIdMessage : Server.Message<ReplStateFacadeServerSide>() {
override suspend fun processImpl(server: ReplStateFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.getId())
}
class GetHistorySizeMessage : Server.Message<ReplStateFacadeServerSide>() {
override suspend fun processImpl(server: ReplStateFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.getHistorySize())
}
class HistoryGetMessage(val index: Int) : Server.Message<ReplStateFacadeServerSide>() {
override suspend fun processImpl(server: ReplStateFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.historyGet(index))
}
class HistoryResetMessage : Server.Message<ReplStateFacadeServerSide>() {
override suspend fun processImpl(server: ReplStateFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.historyReset())
}
class HistoryResetToMessage(val id: ILineId) : Server.Message<ReplStateFacadeServerSide>() {
override suspend fun processImpl(server: ReplStateFacadeServerSide, sendReply: (Any?) -> Unit) =
sendReply(server.historyResetTo(id))
}
}
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("CAST_NEVER_SUCCEEDS")
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.ByteReadChannelWrapper
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.ByteWriteChannelWrapper
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.X509EncodedKeySpec
const val SECURITY_TOKEN_SIZE = 128
private val secureRandom = SecureRandom.getInstance("SHA1PRNG", "SUN");
private val pairGenerator = KeyPairGenerator.getInstance("DSA", "SUN")
private fun generateSecurityToken(): ByteArray {
val tokenBuffer = ByteArray(SECURITY_TOKEN_SIZE)
secureRandom.nextBytes(tokenBuffer)
return tokenBuffer
}
data class SecurityData(val privateKey: PrivateKey, val publicKey: PublicKey, val token: ByteArray)
fun generateKeysAndToken() = pairGenerator.generateKeyPair().let {
SecurityData(it.private, it.public, generateSecurityToken())
}
private fun FileInputStream.readBytesFixedLength(n: Int): ByteArray {
val buffer = ByteArray(n)
var bytesRead = 0
while (bytesRead != n) {
bytesRead += this.read(buffer, bytesRead, n - bytesRead)
}
return buffer
}
// server part :
fun sendTokenKeyPair(output: FileOutputStream, token: ByteArray, privateKey: PrivateKey) {
output.write(token)
ObjectOutputStream(output).use {
it.writeObject(privateKey)
}
}
private fun instantiateDsa() = Signature.getInstance("SHA1withDSA", "SUN")
suspend fun getSignatureAndVerify(input: ByteReadChannelWrapper, expectedToken: ByteArray, publicKey: PublicKey): Boolean {
val signature = input.nextBytes()
val dsa = instantiateDsa()
dsa.initVerify(publicKey)
dsa.update(expectedToken, 0, SECURITY_TOKEN_SIZE)
val verified = dsa.verify(signature)
log.fine("verified : $verified")
return verified
}
// client part :
fun readTokenKeyPairAndSign(input: FileInputStream): ByteArray {
val token = input.readBytesFixedLength(SECURITY_TOKEN_SIZE)
val privateKey = ObjectInputStream(input).use(ObjectInputStream::readObject) as PrivateKey
val dsa = instantiateDsa()
dsa.initSign(privateKey)
dsa.update(token, 0, SECURITY_TOKEN_SIZE)
return dsa.sign()
}
suspend fun sendSignature(output: ByteWriteChannelWrapper, signature: ByteArray) = output.writeBytesAndLength(signature.size, signature)
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common.experimental
import org.jetbrains.kotlin.daemon.common.COMPILE_DAEMON_FIND_PORT_ATTEMPTS
import org.jetbrains.kotlin.daemon.common.experimental.*
fun findCallbackServerSocket() = findPortForSocket(
COMPILE_DAEMON_FIND_PORT_ATTEMPTS,
CALLBACK_SERVER_PORTS_RANGE_START,
CALLBACK_SERVER_PORTS_RANGE_END
)
fun findReplServerSocket() = findPortForSocket(
COMPILE_DAEMON_FIND_PORT_ATTEMPTS,
REPL_SERVER_PORTS_RANGE_START,
REPL_SERVER_PORTS_RANGE_END
)
@@ -0,0 +1,260 @@
package org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure
import io.ktor.network.sockets.Socket
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.*
import org.jetbrains.kotlin.daemon.common.experimental.LoopbackNetworkInterface
import sun.net.ConnectionResetException
import java.beans.Transient
import java.io.IOException
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
import java.util.logging.Logger
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.Server.*
interface Client<ServerType : ServerBase> : Serializable, AutoCloseable {
@Throws(Exception::class)
suspend fun connectToServer()
suspend fun sendMessage(msg: AnyMessage<out ServerType>): Int // returns message unique id
fun sendNoReplyMessage(msg: AnyMessage<out ServerType>)
suspend fun <T> readMessage(id: Int): T
}
@Suppress("UNCHECKED_CAST")
abstract class DefaultAuthorizableClient<ServerType : ServerBase>(
val serverPort: Int,
val serverHost: String = LoopbackNetworkInterface.loopbackInetAddressName
) : Client<ServerType> {
val log: Logger
@Transient get() = Logger.getLogger("default client($serverPort)")//.also { it.setUseParentHandlers(false); }
@kotlin.jvm.Transient
lateinit var input: ByteReadChannelWrapper
@kotlin.jvm.Transient
lateinit var output: ByteWriteChannelWrapper
@kotlin.jvm.Transient
private var socket: Socket? = null
abstract suspend fun authorizeOnServer(serverOutputChannel: ByteWriteChannelWrapper): Boolean
abstract suspend fun clientHandshake(input: ByteReadChannelWrapper, output: ByteWriteChannelWrapper, log: Logger): Boolean
/*
The purpose of ths function is the following : a client starts sending keep-alive requests to the server.
It sends a request every K seconds and awaits a response after K/2 seconds.
In case of not receiving the response from the server we stop waiting for other responses.
This resolves the case when the server is down but the client is still waiting for some calculations.
Keep-alives run in a separate thread on client and on server, hence we can assume that the calculations on server
shouldn't delay keep-alive responses too much
*/
abstract fun startKeepAlives()
/*
`delayKeepAlives` resolves another issue. Imagine that a server and a client have too many short requests/responses,
say, 1000 requests/responses and 0.001 seconds of latency. Then we can miss keep-alive response because of a socket workload,
or, say, if a scheduler decides not to schedule on a keep-alive thread.
In this case we say that any response can stand for a keep-alive message, as well. delayKeepAlives() serves this purpose.
*/
abstract fun delayKeepAlives()
override fun close() {
socket?.close()
}
class MessageReply<T : Any>(val messageId: Int, val reply: T?) : Serializable
protected interface ReadActorQuery
protected data class ExpectReplyQuery(val messageId: Int, val result: CompletableDeferred<MessageReply<*>>) : ReadActorQuery
protected class ReceiveReplyQuery(val reply: MessageReply<*>) : ReadActorQuery
protected interface WriteActorQuery
protected data class SendNoreplyMessageQuery(val message: AnyMessage<*>) : WriteActorQuery
protected data class SendMessageQuery(val message: AnyMessage<*>, val messageId: CompletableDeferred<Any>) : WriteActorQuery
protected class StopAllRequests : ReadActorQuery, WriteActorQuery
@kotlin.jvm.Transient
protected lateinit var readActor: SendChannel<ReadActorQuery>
@kotlin.jvm.Transient
private lateinit var writeActor: SendChannel<WriteActorQuery>
override suspend fun sendMessage(msg: AnyMessage<out ServerType>): Int {
val id = CompletableDeferred<Any>()
writeActor.send(SendMessageQuery(msg, id))
val idVal = id.await()
if (idVal is IOException) {
throw idVal
}
return idVal as Int
}
override fun sendNoReplyMessage(msg: AnyMessage<out ServerType>) {
writeActor.offer(SendNoreplyMessageQuery(msg))
}
override suspend fun <T> readMessage(id: Int): T {
val result = CompletableDeferred<MessageReply<*>>()
try {
readActor.send(ExpectReplyQuery(id, result))
} catch (e: ClosedSendChannelException) {
throw IOException("failed to read message (channel was closed)")
}
val actualResult = result.await().reply
if (actualResult is IOException) {
throw actualResult
}
return actualResult as T
}
override suspend fun connectToServer() {
writeActor = GlobalScope.actor(capacity = Channel.UNLIMITED) {
var firstFreeMessageId = 0
consumeEach { query ->
when (query) {
is SendMessageQuery -> {
val id = firstFreeMessageId++
try {
output.writeObject(query.message.withId(id))
query.messageId.complete(id)
} catch (e: IOException) {
query.messageId.complete(e)
}
}
is SendNoreplyMessageQuery -> {
output.writeObject(query.message.withId(-1))
}
is StopAllRequests -> {
channel.close()
}
}
}
}
class NextObjectQuery
val nextObjectQuery = NextObjectQuery()
val objectReaderActor = GlobalScope.actor<NextObjectQuery>(capacity = Channel.UNLIMITED) {
consumeEach {
try {
val reply = input.nextObject()
when (reply) {
is ServerDownMessage<*> -> throw IOException("connection closed by server")
!is MessageReply<*> -> throw IOException("contrafact message (expected MessageReply<*>)")
else -> readActor.send(ReceiveReplyQuery(reply))
}
} catch (e: IOException) {
readActor.send(StopAllRequests())
}
}
}
readActor = GlobalScope.actor(capacity = Channel.UNLIMITED) {
val receivedMessages = hashMapOf<Int, MessageReply<*>>()
val expectedMessages = hashMapOf<Int, ExpectReplyQuery>()
fun broadcastIOException(e: IOException) {
channel.close()
expectedMessages.forEach { id, deferred ->
deferred.result.complete(MessageReply(id, e))
}
expectedMessages.clear()
receivedMessages.clear()
}
consumeEach { query ->
when (query) {
is ExpectReplyQuery -> {
receivedMessages[query.messageId]?.also { reply ->
query.result.complete(reply)
} ?: expectedMessages.put(query.messageId, query).also {
objectReaderActor.send(nextObjectQuery)
}
}
is ReceiveReplyQuery -> {
val reply = query.reply
expectedMessages[reply.messageId]?.also { expectedMsg ->
expectedMsg.result.complete(reply)
} ?: receivedMessages.put(reply.messageId, reply).also {
objectReaderActor.send(nextObjectQuery)
}
delayKeepAlives()
}
is StopAllRequests -> {
broadcastIOException(IOException("KeepAlive failed"))
writeActor.send(StopAllRequests())
}
}
}
}
try {
socket = LoopbackNetworkInterface.clientLoopbackSocketFactoryKtor.createSocket(
serverHost,
serverPort
)
} catch (e: Throwable) {
close()
throw e
}
socket?.openIO(log)?.also {
input = it.input
output = it.output
if (!clientHandshake(input, output, log)) {
throw ConnectionResetException("failed to establish connection with server (handshake failed)")
}
if (!authorizeOnServer(output)) {
throw ConnectionResetException("failed to establish connection with server (authorization failed)")
}
}
startKeepAlives()
}
@Throws(ClassNotFoundException::class, IOException::class)
private fun readObject(aInputStream: ObjectInputStream) {
aInputStream.defaultReadObject()
println("connecting...")
runBlocking { connectToServer() }
println("connectED")
}
@Throws(IOException::class)
private fun writeObject(aOutputStream: ObjectOutputStream) {
aOutputStream.defaultWriteObject()
}
}
class DefaultClient<ServerType : ServerBase>(
serverPort: Int,
serverHost: String = LoopbackNetworkInterface.loopbackInetAddressName
) : DefaultAuthorizableClient<ServerType>(serverPort, serverHost) {
override suspend fun clientHandshake(input: ByteReadChannelWrapper, output: ByteWriteChannelWrapper, log: Logger) = true
override suspend fun authorizeOnServer(output: ByteWriteChannelWrapper): Boolean = true
override fun startKeepAlives() {}
override fun delayKeepAlives() {}
}
class DefaultClientRMIWrapper<ServerType : ServerBase> : Client<ServerType> {
override suspend fun connectToServer() {}
override suspend fun sendMessage(msg: AnyMessage<out ServerType>) =
throw UnsupportedOperationException("sendMessage is not supported for RMI wrappers")
override fun sendNoReplyMessage(msg: AnyMessage<out ServerType>) =
throw UnsupportedOperationException("sendMessage is not supported for RMI wrappers")
override suspend fun <T> readMessage(id: Int) = throw UnsupportedOperationException("readMessage is not supported for RMI wrappers")
override fun close() {}
}
@@ -0,0 +1,208 @@
package org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure
import io.ktor.network.sockets.ServerSocket
import io.ktor.network.sockets.Socket
import kotlinx.coroutines.*
import org.jetbrains.kotlin.daemon.common.experimental.*
import java.io.Serializable
import java.util.concurrent.TimeUnit
import java.util.logging.Logger
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
data class ServerSocketWrapper(val port: Int, val socket: ServerSocket)
interface ServerBase
@Suppress("UNCHECKED_CAST")
interface Server<out T : ServerBase> : ServerBase {
val serverSocketWithPort: ServerSocketWrapper
val serverPort: Int
get() = serverSocketWithPort.port
private val log: Logger
get() = Logger.getLogger("default server($serverPort)")
enum class State {
WORKING, CLOSED, ERROR, DOWNING, UNVERIFIED
}
fun processMessage(msg: AnyMessage<in T>, output: ByteWriteChannelWrapper): State =
when (msg) {
is Message<in T> -> State.WORKING.also {
msg.process(this as T, output)
}
is EndConnectionMessage<in T> -> {
State.CLOSED
}
is ServerDownMessage<in T> -> State.CLOSED
else -> State.ERROR
}
// TODO: replace GlobalScope here and below with smth. more explicit
fun attachClient(client: Socket): Deferred<State> = GlobalScope.async {
val (input, output) = client.openIO(log)
if (!serverHandshake(input, output, log)) {
return@async State.UNVERIFIED
}
if (!checkClientCanReadFile(input)) {
return@async State.UNVERIFIED
}
clients[client] = ClientInfo(client, input, output)
var finalState = State.WORKING
val keepAliveAcknowledgement = KeepAliveAcknowledgement<T>()
loop@
while (true) {
val message = input.nextObject()
when (message) {
is ServerDownMessage<*> -> {
shutdownClient(client)
break@loop
}
is KeepAliveMessage<*> -> State.WORKING.also {
output.writeObject(
DefaultAuthorizableClient.MessageReply(
message.messageId!!,
keepAliveAcknowledgement
)
)
}
!is AnyMessage<*> -> {
finalState = State.ERROR
break@loop
}
else -> {
val state = processMessage(message as AnyMessage<T>, output)
when (state) {
State.WORKING -> continue@loop
State.ERROR -> {
finalState = State.ERROR
break@loop
}
else -> {
finalState = state
break@loop
}
}
}
}
}
finalState
}
abstract class AnyMessage<ServerType : ServerBase> : Serializable {
var messageId: Int? = null
fun withId(id: Int): AnyMessage<ServerType> {
messageId = id
return this
}
}
abstract class Message<ServerType : ServerBase> : AnyMessage<ServerType>() {
fun process(server: ServerType, output: ByteWriteChannelWrapper) = GlobalScope.async {
log.fine("$server starts processing ${this@Message}")
processImpl(server, {
log.fine("$server finished processing ${this@Message}, sending output")
GlobalScope.async {
log.fine("$server starts sending ${this@Message} to output")
output.writeObject(DefaultAuthorizableClient.MessageReply(messageId ?: -1, it))
log.fine("$server finished sending ${this@Message} to output")
}
})
}
abstract suspend fun processImpl(server: ServerType, sendReply: (Any?) -> Unit)
}
class EndConnectionMessage<ServerType : ServerBase> : AnyMessage<ServerType>()
class KeepAliveAcknowledgement<ServerType : ServerBase> : AnyMessage<ServerType>()
class KeepAliveMessage<ServerType : ServerBase> : AnyMessage<ServerType>()
class ServerDownMessage<ServerType : ServerBase> : AnyMessage<ServerType>()
data class ClientInfo(val socket: Socket, val input: ByteReadChannelWrapper, val output: ByteWriteChannelWrapper)
val clients: HashMap<Socket, ClientInfo>
private fun dealWithClient(client: Socket) = GlobalScope.async {
val state = attachClient(client).await()
when (state) {
State.CLOSED, State.UNVERIFIED -> shutdownClient(client)
State.DOWNING -> shutdownServer()
else -> shutdownClient(client)
}
}
fun runServer(): Deferred<Unit> {
val serverSocket = serverSocketWithPort.socket
return GlobalScope.async {
serverSocket.use {
while (true) {
dealWithClient(serverSocket.accept())
}
}
}
}
fun shutdownServer() {
clients.forEach { socket, info ->
runBlockingWithTimeout {
info.output.writeObject(ServerDownMessage<T>())
info.output.close()
}
socket.close()
}
clients.clear()
serverSocketWithPort.socket.close()
}
private fun shutdownClient(client: Socket) {
clients.remove(client)
client.close()
}
/*
This function writes some message in the server file, and awaits the confirmation from the client that it has read the message
correctly. The purpose here is to check whether the client can actually access file system and read file contents.
*/
suspend fun checkClientCanReadFile(clientInputChannel: ByteReadChannelWrapper): Boolean = true
suspend fun serverHandshake(input: ByteReadChannelWrapper, output: ByteWriteChannelWrapper, log: Logger) = true
}
fun <T> runBlockingWithTimeout(timeout: Long = AUTH_TIMEOUT_IN_MILLISECONDS, block: suspend () -> T) =
runBlocking { runWithTimeout(timeout = timeout) { block() } }
//@Throws(TimeoutException::class)
suspend fun <T> runWithTimeout(
timeout: Long = AUTH_TIMEOUT_IN_MILLISECONDS,
unit: TimeUnit = TimeUnit.MILLISECONDS,
block: suspend CoroutineScope.() -> T
): T? = withTimeoutOrNull(unit.toMillis(timeout)) { block() }
//@Throws(ConnectionResetException::class)
suspend fun tryAcquireHandshakeMessage(input: ByteReadChannelWrapper, log: Logger): Boolean {
val bytes = runWithTimeout {
input.nextBytes()
} ?: return false
if (bytes.zip(FIRST_HANDSHAKE_BYTE_TOKEN).any { it.first != it.second }) {
return false
}
return true
}
//@Throws(ConnectionResetException::class)
suspend fun trySendHandshakeMessage(output: ByteWriteChannelWrapper, log: Logger): Boolean {
runWithTimeout {
output.writeBytesAndLength(FIRST_HANDSHAKE_BYTE_TOKEN.size, FIRST_HANDSHAKE_BYTE_TOKEN)
} ?: return false
return true
}
@@ -0,0 +1,217 @@
package org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure
import io.ktor.network.sockets.Socket
import io.ktor.network.sockets.openReadChannel
import io.ktor.network.sockets.openWriteChannel
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.io.*
import kotlinx.io.core.readBytes
import java.io.*
import java.nio.ByteBuffer
import java.util.logging.Logger
private val DEFAULT_BYTE_ARRAY = byteArrayOf(0, 0, 0, 0)
class ByteReadChannelWrapper(readChannel: ByteReadChannel, private val log: Logger) {
private interface ReadQuery
private open class BytesQuery(val bytes: CompletableDeferred<ByteArray>) : ReadQuery
private class SerObjectQuery(val obj: CompletableDeferred<Any?>) : ReadQuery
suspend fun readLength(readChannel: ByteReadChannel) =
if (readChannel.isClosedForRead)
null
else
try {
readChannel.readPacket(4).readBytes()
} catch (e: Exception) {
log.fine("failed to read message length, ${e.message}")
null
}
suspend fun readPacket(length: Int, readChannel: ByteReadChannel) =
try {
readChannel.readPacket(
length
).readBytes()
} catch (e: Exception) {
log.fine("failed to read packet (${e.message})")
null
}
// TODO : replace GlobalScope with something more explicit here and below.
private val readActor = GlobalScope.actor<ReadQuery>(capacity = Channel.UNLIMITED) {
consumeEach { message ->
if (!readChannel.isClosedForRead) {
readLength(readChannel)?.let { messageLength ->
when (message) {
is BytesQuery -> message.bytes.complete(
readChannel.readPacket(
getLength(messageLength)
).readBytes()
)
is SerObjectQuery -> message.obj.complete(
getObject(
getLength(messageLength),
{ len -> readPacket(len, readChannel) }
)
)
else -> {
}
}
}
} else {
log.fine("read chanel closed " + log.name)
}
}
}
private fun getLength(packet: ByteArray): Int {
val (b1, b2, b3, b4) = packet.map(Byte::toInt)
return (0xFF and b1 shl 24 or (0xFF and b2 shl 16) or
(0xFF and b3 shl 8) or (0xFF and b4)).also { log.fine(" $it") }
}
/** first reads <t>length</t> token (4 bytes) and then -- reads <t>length</t> bytes.
* after deafault timeout returns <tt>DEFAULT_BYTE_ARRAY</tt> */
suspend fun nextBytes(): ByteArray = runWithTimeout {
val expectedBytes = CompletableDeferred<ByteArray>()
readActor.send(BytesQuery(expectedBytes))
expectedBytes.await()
} ?: DEFAULT_BYTE_ARRAY
private suspend fun getObject(length: Int, readPacket: suspend (Int) -> ByteArray?): Any? =
if (length >= 0) {
readPacket(length)?.let { bytes ->
ObjectInputStream(
ByteArrayInputStream(bytes)
).use {
it.readObject()
}
}
} else { // optimize for long strings!
readPacket(-length)?.let { bytes ->
String(
ByteArrayInputStream(
bytes
).readBytes()
)
}
}
/** first reads <t>length</t> token (4 bytes), then reads <t>length</t> bytes and returns deserialized object */
suspend fun nextObject(): Any? {
val obj = CompletableDeferred<Any?>()
readActor.send(SerObjectQuery(obj))
val result = obj.await()
if (result is Server.ServerDownMessage<*>) {
throw IOException("connection closed by server")
}
return result
}
}
class ByteWriteChannelWrapper(writeChannel: ByteWriteChannel, private val log: Logger) {
private interface WriteActorQuery
private open class ByteData(val bytes: ByteArray) : WriteActorQuery {
open fun toByteArray(): ByteArray = bytes
}
private class ObjectWithLength(val lengthBytes: ByteArray, bytes: ByteArray) : ByteData(bytes) {
override fun toByteArray() = lengthBytes + bytes
}
private class CloseMessage : WriteActorQuery
private suspend fun tryWrite(b: ByteArray, writeChannel: ByteWriteChannel) {
if (!writeChannel.isClosedForWrite) {
try {
writeChannel.writeFully(b)
} catch (e: Exception) {
log.fine("failed to print message, ${e.message}")
}
} else {
log.fine("closed chanel (write)")
}
}
private val writeActor = GlobalScope.actor<WriteActorQuery>(capacity = Channel.UNLIMITED) {
consumeEach { message ->
if (!writeChannel.isClosedForWrite) {
when (message) {
is CloseMessage -> {
log.fine("${log.name} closing chanel...")
writeChannel.close()
}
is ByteData -> {
tryWrite(message.toByteArray(), writeChannel)
if (!writeChannel.isClosedForWrite) {
try {
writeChannel.flush()
} catch (e: Exception) {
log.fine("failed to flush byte write chanel")
}
}
}
}
} else {
log.fine("${log.name} write chanel closed")
}
}
}
suspend fun writeBytesAndLength(length: Int, bytes: ByteArray) {
writeActor.send(
ObjectWithLength(
getLengthBytes(length),
bytes
)
)
}
private suspend fun writeObjectImpl(obj: Any?) =
ByteArrayOutputStream().use { bos ->
ObjectOutputStream(bos).use { objOut ->
objOut.writeObject(obj)
objOut.flush()
val bytes = bos.toByteArray()
writeBytesAndLength(bytes.size, bytes)
}
}
private suspend fun writeString(s: String) = writeBytesAndLength(-s.length, s.toByteArray())
fun getLengthBytes(length: Int) =
ByteBuffer
.allocate(4)
.putInt(length)
.array()
suspend fun writeObject(obj: Any?) {
if (obj is String) writeString(obj)
else writeObjectImpl(obj)
}
suspend fun close() = writeActor.send(CloseMessage())
}
fun ByteReadChannel.toWrapper(log: Logger) = ByteReadChannelWrapper(this, log)
fun ByteWriteChannel.toWrapper(log: Logger) = ByteWriteChannelWrapper(this, log)
fun Socket.openAndWrapReadChannel(log: Logger) = this.openReadChannel().toWrapper(log)
fun Socket.openAndWrapWriteChannel(log: Logger) = this.openWriteChannel().toWrapper(log)
data class IOPair(val input: ByteReadChannelWrapper, val output: ByteWriteChannelWrapper)
fun Socket.openIO(log: Logger) = IOPair(this.openAndWrapReadChannel(log), this.openAndWrapWriteChannel(log))
@@ -1,3 +1,4 @@
import com.sun.javafx.scene.CameraHelper.project
plugins {
kotlin("jvm")
@@ -13,6 +14,9 @@ dependencies {
compileOnly(project(":js:js.frontend"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
compileOnly(intellijDep()) { includeIntellijCoreJarDependencies(project) }
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) {
isTransitive = false
}
}
sourceSets {
@@ -29,4 +29,9 @@ enum class CompilationResultCategory(val code: Int) {
IC_COMPILE_ITERATION(0),
BUILD_REPORT_LINES(1),
VERBOSE_BUILD_REPORT_LINES(2),
}
interface CompilationResultsAsync {
suspend fun add(compilationResultCategory: Int, value: Serializable)
val clientSide: CompilationResultsAsync
}
@@ -0,0 +1,36 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
import kotlinx.coroutines.runBlocking
import java.io.Serializable
class CompilationResultsAsyncWrapper(val rmiImpl: CompilationResults) : CompilationResultsAsync {
override val clientSide: CompilationResultsAsync
get() = this
override suspend fun add(compilationResultCategory: Int, value: Serializable) {
rmiImpl.add(compilationResultCategory, value)
}
}
class CompilationResultsRMIWrapper(val clientSide: CompilationResultsAsync) : CompilationResults, Serializable {
override fun add(compilationResultCategory: Int, value: Serializable) = runBlocking {
clientSide.add(compilationResultCategory, value)
}
}
fun CompilationResults.toClient() =
if (this is CompilationResultsRMIWrapper) this.clientSide
else CompilationResultsAsyncWrapper(this)
fun CompilationResultsAsync.toRMI() =
if (this is CompilationResultsAsyncWrapper) this.rmiImpl
else CompilationResultsRMIWrapper(this)
@@ -0,0 +1,79 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
import java.io.File
interface CompileServiceAsync {
suspend fun checkCompilerId(expectedCompilerId: CompilerId): Boolean
suspend fun getUsedMemory(): CompileService.CallResult<Long>
suspend fun getDaemonOptions(): CompileService.CallResult<DaemonOptions>
suspend fun getDaemonInfo(): CompileService.CallResult<String>
suspend fun getDaemonJVMOptions(): CompileService.CallResult<DaemonJVMOptions>
suspend fun registerClient(aliveFlagPath: String?): CompileService.CallResult<Nothing>
// TODO: (-old-) consider adding another client alive checking mechanism, e.g. socket/socketPort
suspend fun getClients(): CompileService.CallResult<List<String>>
suspend fun leaseCompileSession(aliveFlagPath: String?): CompileService.CallResult<Int>
suspend fun releaseCompileSession(sessionId: Int): CompileService.CallResult<Nothing>
suspend fun shutdown(): CompileService.CallResult<Nothing>
suspend fun scheduleShutdown(graceful: Boolean): CompileService.CallResult<Boolean>
suspend fun compile(
sessionId: Int,
compilerArguments: Array<out String>,
compilationOptions: CompilationOptions,
servicesFacade: CompilerServicesFacadeBaseAsync,
compilationResults: CompilationResultsAsync?
): CompileService.CallResult<Int>
suspend fun clearJarCache()
suspend fun releaseReplSession(sessionId: Int): CompileService.CallResult<Nothing>
suspend fun leaseReplSession(
aliveFlagPath: String?,
compilerArguments: Array<out String>,
compilationOptions: CompilationOptions,
servicesFacade: CompilerServicesFacadeBaseAsync,
templateClasspath: List<java.io.File>,
templateClassName: String
): CompileService.CallResult<Int>
suspend fun replCreateState(sessionId: Int): CompileService.CallResult<ReplStateFacadeAsync>
suspend fun replCheck(
sessionId: Int,
replStateId: Int,
codeLine: ReplCodeLine
): CompileService.CallResult<ReplCheckResult>
suspend fun replCompile(
sessionId: Int,
replStateId: Int,
codeLine: ReplCodeLine
): CompileService.CallResult<ReplCompileResult>
suspend fun classesFqNamesByFiles(sessionId: Int, sourceFiles: Set<File>): CompileService.CallResult<Set<String>>
val serverPort: Int
get() = 0
}
@@ -0,0 +1,109 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import java.io.File
class CompileServiceAsyncWrapper(
val rmiCompileService: CompileService
) : CompileServiceAsync {
override suspend fun classesFqNamesByFiles(sessionId: Int, sourceFiles: Set<File>) =
rmiCompileService.classesFqNamesByFiles(sessionId, sourceFiles)
override suspend fun compile(
sessionId: Int,
compilerArguments: Array<out String>,
compilationOptions: CompilationOptions,
servicesFacade: CompilerServicesFacadeBaseAsync,
compilationResults: CompilationResultsAsync?
) = rmiCompileService.compile(
sessionId,
compilerArguments,
compilationOptions,
servicesFacade.toRMI(),
compilationResults?.toRMI()
)
override suspend fun leaseReplSession(
aliveFlagPath: String?,
compilerArguments: Array<out String>,
compilationOptions: CompilationOptions,
servicesFacade: CompilerServicesFacadeBaseAsync,
templateClasspath: List<File>,
templateClassName: String
) = rmiCompileService.leaseReplSession(
aliveFlagPath,
compilerArguments,
compilationOptions,
servicesFacade.toRMI(),
templateClasspath,
templateClassName
)
override suspend fun replCreateState(sessionId: Int) =
rmiCompileService.replCreateState(sessionId).toClient()
override suspend fun getUsedMemory() =
rmiCompileService.getUsedMemory()
override suspend fun getDaemonOptions() =
rmiCompileService.getDaemonOptions()
override suspend fun getDaemonInfo() =
rmiCompileService.getDaemonInfo()
override suspend fun getDaemonJVMOptions() =
rmiCompileService.getDaemonJVMOptions()
override suspend fun registerClient(aliveFlagPath: String?) =
rmiCompileService.registerClient(aliveFlagPath)
override suspend fun getClients() =
rmiCompileService.getClients()
override suspend fun leaseCompileSession(aliveFlagPath: String?) =
rmiCompileService.leaseCompileSession(aliveFlagPath)
override suspend fun releaseCompileSession(sessionId: Int) =
rmiCompileService.releaseCompileSession(sessionId)
override suspend fun shutdown() =
rmiCompileService.shutdown()
override suspend fun scheduleShutdown(graceful: Boolean) =
rmiCompileService.scheduleShutdown(graceful)
override suspend fun clearJarCache() =
rmiCompileService.clearJarCache()
override suspend fun releaseReplSession(sessionId: Int) =
rmiCompileService.releaseReplSession(sessionId)
override suspend fun replCheck(sessionId: Int, replStateId: Int, codeLine: ReplCodeLine) =
rmiCompileService.replCheck(sessionId, replStateId, codeLine)
override suspend fun replCompile(
sessionId: Int,
replStateId: Int,
codeLine: ReplCodeLine
) = rmiCompileService.replCompile(sessionId, replStateId, codeLine)
override suspend fun checkCompilerId(expectedCompilerId: CompilerId) =
rmiCompileService.checkCompilerId(expectedCompilerId)
}
fun CompileService.toClient() = CompileServiceAsyncWrapper(this)
@@ -0,0 +1,205 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
import java.io.File
class CompileServiceClientRMIWrapper(
val asyncCompileService: CompileServiceAsync
) : CompileService {
override fun classesFqNamesByFiles(sessionId: Int, sourceFiles: Set<File>) = runBlocking {
asyncCompileService.classesFqNamesByFiles(sessionId, sourceFiles)
}
private fun reportNotImplemented(): Nothing = throw IllegalStateException("Unexpected call to deprecated method")
// deprecated methods :
override fun remoteCompile(
sessionId: Int,
targetPlatform: CompileService.TargetPlatform,
args: Array<out String>,
servicesFacade: CompilerCallbackServicesFacade,
compilerOutputStream: RemoteOutputStream,
outputFormat: CompileService.OutputFormat,
serviceOutputStream: RemoteOutputStream,
operationsTracer: RemoteOperationsTracer?
): CompileService.CallResult<Int> {
reportNotImplemented()
}
override fun remoteIncrementalCompile(
sessionId: Int,
targetPlatform: CompileService.TargetPlatform,
args: Array<out String>,
servicesFacade: CompilerCallbackServicesFacade,
compilerOutputStream: RemoteOutputStream,
compilerOutputFormat: CompileService.OutputFormat,
serviceOutputStream: RemoteOutputStream,
operationsTracer: RemoteOperationsTracer?
): CompileService.CallResult<Int> {
reportNotImplemented()
}
override fun leaseReplSession(
aliveFlagPath: String?,
targetPlatform: CompileService.TargetPlatform,
servicesFacade: CompilerCallbackServicesFacade,
templateClasspath: List<File>,
templateClassName: String,
scriptArgs: Array<out Any?>?,
scriptArgsTypes: Array<out Class<out Any>>?,
compilerMessagesOutputStream: RemoteOutputStream,
evalOutputStream: RemoteOutputStream?,
evalErrorStream: RemoteOutputStream?,
evalInputStream: RemoteInputStream?,
operationsTracer: RemoteOperationsTracer?
): CompileService.CallResult<Int> {
reportNotImplemented()
}
override fun remoteReplLineCheck(sessionId: Int, codeLine: ReplCodeLine): CompileService.CallResult<ReplCheckResult> {
reportNotImplemented()
}
override fun remoteReplLineCompile(
sessionId: Int,
codeLine: ReplCodeLine,
history: List<ReplCodeLine>?
): CompileService.CallResult<ReplCompileResult> {
reportNotImplemented()
}
override fun remoteReplLineEval(
sessionId: Int,
codeLine: ReplCodeLine,
history: List<ReplCodeLine>?
): CompileService.CallResult<ReplEvalResult> {
reportNotImplemented()
}
// normal methods:
override fun compile(
sessionId: Int,
compilerArguments: Array<out String>,
compilationOptions: CompilationOptions,
servicesFacade: CompilerServicesFacadeBase,
compilationResults: CompilationResults?
) = runBlocking {
asyncCompileService.compile(
sessionId,
compilerArguments,
compilationOptions,
servicesFacade.toClient(),
compilationResults?.toClient() // TODO
)
}
override fun leaseReplSession(
aliveFlagPath: String?,
compilerArguments: Array<out String>,
compilationOptions: CompilationOptions,
servicesFacade: CompilerServicesFacadeBase,
templateClasspath: List<File>,
templateClassName: String
) = runBlocking {
asyncCompileService.leaseReplSession(
aliveFlagPath,
compilerArguments,
compilationOptions,
servicesFacade.toClient(),
templateClasspath,
templateClassName
)
}
override fun replCreateState(sessionId: Int) = runBlocking {
asyncCompileService.replCreateState(sessionId)
}.toRMI()
override fun getUsedMemory() = runBlocking {
asyncCompileService.getUsedMemory()
}
override fun getDaemonOptions() = runBlocking {
asyncCompileService.getDaemonOptions()
}
override fun getDaemonInfo() = runBlocking {
asyncCompileService.getDaemonInfo()
}
override fun getDaemonJVMOptions() = runBlocking {
asyncCompileService.getDaemonJVMOptions()
}
override fun registerClient(aliveFlagPath: String?) = runBlocking {
asyncCompileService.registerClient(aliveFlagPath)
}
override fun getClients() = runBlocking {
asyncCompileService.getClients()
}
override fun leaseCompileSession(aliveFlagPath: String?) = runBlocking {
asyncCompileService.leaseCompileSession(aliveFlagPath)
}
override fun releaseCompileSession(sessionId: Int) = runBlocking {
asyncCompileService.releaseCompileSession(sessionId)
}
override fun shutdown() = runBlocking {
asyncCompileService.shutdown()
}
override fun scheduleShutdown(graceful: Boolean) = runBlocking {
asyncCompileService.scheduleShutdown(graceful)
}
override fun clearJarCache() = runBlocking {
asyncCompileService.clearJarCache()
}
override fun releaseReplSession(sessionId: Int) = runBlocking {
asyncCompileService.releaseReplSession(sessionId)
}
override fun replCheck(sessionId: Int, replStateId: Int, codeLine: ReplCodeLine) = runBlocking {
asyncCompileService.replCheck(sessionId, replStateId, codeLine)
}
override fun replCompile(
sessionId: Int,
replStateId: Int,
codeLine: ReplCodeLine
) = runBlocking {
asyncCompileService.replCompile(sessionId, replStateId, codeLine)
}
override fun checkCompilerId(expectedCompilerId: CompilerId) = runBlocking {
asyncCompileService.checkCompilerId(expectedCompilerId)
}
}
fun CompileServiceAsync.toRMI() = when (this) {
is CompileServiceAsyncWrapper -> this.rmiCompileService
else -> CompileServiceClientRMIWrapper(this)
}
@@ -0,0 +1,49 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
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
interface CompilerCallbackServicesFacadeAsync : CompilerServicesFacadeBaseAsync {
suspend fun hasIncrementalCaches(): Boolean
suspend fun hasLookupTracker(): Boolean
suspend fun hasCompilationCanceledStatus(): Boolean
// ----------------------------------------------------
// IncrementalCache
suspend fun incrementalCache_getObsoletePackageParts(target: TargetId): Collection<String>
suspend fun incrementalCache_getObsoleteMultifileClassFacades(target: TargetId): Collection<String>
suspend fun incrementalCache_getPackagePartData(target: TargetId, partInternalName: String): JvmPackagePartProto?
suspend fun incrementalCache_getModuleMappingData(target: TargetId): ByteArray?
suspend fun incrementalCache_registerInline(target: TargetId, fromPath: String, jvmSignature: String, toPath: String)
suspend fun incrementalCache_getClassFilePath(target: TargetId, internalClassName: String): String
suspend fun incrementalCache_close(target: TargetId)
suspend fun incrementalCache_getMultifileFacadeParts(target: TargetId, internalName: String): Collection<String>?
// ----------------------------------------------------
// LookupTracker
suspend fun lookupTracker_requiresPosition(): Boolean
fun lookupTracker_record(lookups: Collection<LookupInfo>)
suspend fun lookupTracker_isDoNothing(): Boolean
// ----------------------------------------------------
// CompilationCanceledStatus
suspend fun compilationCanceledStatus_checkCanceled(): Void?
}
@@ -0,0 +1,25 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
import java.io.Serializable
interface CompilerServicesFacadeBaseAsync {
/**
* Reports different kind of diagnostic messages from compile daemon to compile daemon clients (jps, gradle, ...)
*/
suspend fun report(category: Int, severity: Int, message: String?, attachment: Serializable?)
}
suspend fun CompilerServicesFacadeBaseAsync.report(
category: ReportCategory,
severity: ReportSeverity,
message: String? = null,
attachment: Serializable? = null
) {
report(category.code, severity.code, message, attachment)
}
@@ -0,0 +1,22 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
import java.io.Serializable
class CompilerServicesFacadeBaseAsyncWrapper(
val rmiImpl: CompilerServicesFacadeBase
) : CompilerServicesFacadeBaseAsync {
override suspend fun report(category: Int, severity: Int, message: String?, attachment: Serializable?) =
rmiImpl.report(category, severity, message, attachment)
}
fun CompilerServicesFacadeBase.toClient() =
if (this is CompilerServicesFacadeBaseRMIWrapper) this.clientSide
else CompilerServicesFacadeBaseAsyncWrapper(this)
@@ -0,0 +1,21 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
import kotlinx.coroutines.runBlocking
import java.io.Serializable
class CompilerServicesFacadeBaseRMIWrapper(val clientSide: CompilerServicesFacadeBaseAsync) : CompilerServicesFacadeBase, Serializable {
override fun report(category: Int, severity: Int, message: String?, attachment: Serializable?) = runBlocking {
clientSide.report(category, severity, message, attachment)
}
}
fun CompilerServicesFacadeBaseAsync.toRMI() =
if (this is CompilerServicesFacadeBaseAsyncWrapper) this.rmiImpl
else CompilerServicesFacadeBaseRMIWrapper(this)
@@ -0,0 +1,10 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
enum class DaemonProtocolVariant {
RMI, SOCKETS
}
@@ -0,0 +1,9 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
interface IncrementalCompilerServicesFacadeAsync : CompilerServicesFacadeBaseAsync
@@ -34,7 +34,15 @@ interface Profiler {
fun getCounters(): Map<Any?, PerfCounters>
fun getTotalCounters(): PerfCounters
fun<R> withMeasure(obj: Any?, body: () -> R): R
fun beginMeasure(obj: Any?) : List<Long> = listOf()
fun endMeasure(obj: Any?, startState: List<Long>) {}
}
inline fun<R> Profiler.withMeasure(obj: Any?, body: () -> R): R {
val startState = beginMeasure(obj)
val res = body()
endMeasure(obj, startState)
return res
}
@@ -85,57 +93,64 @@ inline fun usedMemory(withGC: Boolean): Long {
}
inline fun<R> withMeasureWallTime(perfCounters: PerfCounters, body: () -> R): R {
val startTime = System.nanoTime()
val res = body()
inline fun<R> beginWithMeasureWallTime(perfCounters: PerfCounters) = listOf(System.nanoTime())
inline fun<R> endWithMeasureWallTime(perfCounters: PerfCounters, startState: List<Long>) {
val (startTime) = startState
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 {
inline fun beginWithMeasureWallAndThreadTimes(perfCounters: PerfCounters, threadMXBean: ThreadMXBean): List<Long> {
val startTime = System.nanoTime()
val startThreadTime = threadMXBean.threadCpuTime()
val startThreadUserTime = threadMXBean.threadUserTime()
return listOf(startTime, startThreadTime, startThreadUserTime)
}
val res = body()
inline fun endWithMeasureWallAndThreadTimes(perfCounters: PerfCounters, threadMXBean: ThreadMXBean, startState: List<Long>) {
val (startTime, startThreadTime, startThreadUserTime) = startState
// 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 beginWithMeasureWallAndThreadTimes(perfCounters: PerfCounters) =
beginWithMeasureWallAndThreadTimes(perfCounters, ManagementFactory.getThreadMXBean())
inline fun endWithMeasureWallAndThreadTimes(perfCounters: PerfCounters, startState: List<Long>) =
endWithMeasureWallAndThreadTimes(perfCounters, ManagementFactory.getThreadMXBean(), startState)
inline fun<R> withMeasureWallAndThreadTimesAndMemory(perfCounters: PerfCounters, withGC: Boolean = false, threadMXBean: ThreadMXBean, body: () -> R): R {
inline fun beginWithMeasureWallAndThreadTimesAndMemory(perfCounters: PerfCounters, withGC: Boolean = false, threadMXBean: ThreadMXBean): List<Long> {
val startMem = usedMemory(withGC)
val startTime = System.nanoTime()
val startThreadTime = threadMXBean.threadCpuTime()
val startThreadUserTime = threadMXBean.threadUserTime()
val res = body()
return listOf(startMem, startTime, startThreadTime, startThreadUserTime)
}
inline fun endWithMeasureWallAndThreadTimesAndMemory(perfCounters: PerfCounters, withGC: Boolean = false, threadMXBean: ThreadMXBean, startState: List<Long>){
val (startMem, startTime, startThreadTime, startThreadUserTime) = startState
// 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)
inline fun<R> beginWithMeasureWallAndThreadTimesAndMemory(perfCounters: PerfCounters, withGC: Boolean) =
beginWithMeasureWallAndThreadTimesAndMemory(perfCounters, withGC, ManagementFactory.getThreadMXBean())
inline fun<R> endWithMeasureWallAndThreadTimesAndMemory(perfCounters: PerfCounters, withGC: Boolean, startState: List<Long>) =
endWithMeasureWallAndThreadTimesAndMemory(perfCounters, withGC, ManagementFactory.getThreadMXBean(), startState)
class DummyProfiler : Profiler {
override fun getCounters(): Map<Any?, PerfCounters> = mapOf(null to SimplePerfCounters())
override fun getTotalCounters(): PerfCounters = SimplePerfCounters()
@Suppress("OVERRIDE_BY_INLINE")
override inline fun <R> withMeasure(obj: Any?, body: () -> R): R = body()
}
@@ -151,19 +166,27 @@ abstract class TotalProfiler : Profiler {
class WallTotalProfiler : TotalProfiler() {
@Suppress("OVERRIDE_BY_INLINE")
override inline fun <R> withMeasure(obj: Any?, body: () -> R): R = withMeasureWallTime(total, body)
override inline fun beginMeasure(obj: Any?) = beginWithMeasureWallTime(total)
@Suppress("OVERRIDE_BY_INLINE")
override inline fun endMeasure(obj: Any?, startState: List<Long>) = endWithMeasureWallTime(total, startState)
}
class WallAndThreadTotalProfiler : TotalProfiler() {
@Suppress("OVERRIDE_BY_INLINE")
override inline fun <R> withMeasure(obj: Any?, body: () -> R): R = withMeasureWallAndThreadTimes(total, threadMXBean, body)
override inline fun beginMeasure(obj: Any?) = beginWithMeasureWallAndThreadTimes(total, threadMXBean)
@Suppress("OVERRIDE_BY_INLINE")
override inline fun endMeasure(obj: Any?, startState: List<Long>) = endWithMeasureWallAndThreadTimes(total, threadMXBean, startState)
}
class WallAndThreadAndMemoryTotalProfiler(val withGC: Boolean) : TotalProfiler() {
@Suppress("OVERRIDE_BY_INLINE")
override inline fun <R> withMeasure(obj: Any?, body: () -> R): R = withMeasureWallAndThreadTimesAndMemory(total, withGC, threadMXBean, body)
override inline fun beginMeasure(obj: Any?) =
beginWithMeasureWallAndThreadTimesAndMemory(total, withGC, threadMXBean)
@Suppress("OVERRIDE_BY_INLINE")
override inline fun endMeasure(obj: Any?, startState: List<Long>) =
endWithMeasureWallAndThreadTimesAndMemory(total, withGC, threadMXBean, startState)
}
@@ -174,6 +197,9 @@ class WallAndThreadByClassProfiler() : TotalProfiler() {
override fun getCounters(): Map<Any?, PerfCounters> = counters
@Suppress("OVERRIDE_BY_INLINE")
override inline fun <R> withMeasure(obj: Any?, body: () -> R): R =
withMeasureWallAndThreadTimes(counters.getOrPut(obj?.javaClass?.name, { SimplePerfCountersWithTotal(total) }), threadMXBean, body)
override inline fun beginMeasure(obj: Any?) =
beginWithMeasureWallAndThreadTimes(counters.getOrPut(obj?.javaClass?.name, { SimplePerfCountersWithTotal(total) }), threadMXBean)
@Suppress("OVERRIDE_BY_INLINE")
override inline fun endMeasure(obj: Any?, startState: List<Long>) =
endWithMeasureWallAndThreadTimes(counters.getOrPut(obj?.javaClass?.name, { SimplePerfCountersWithTotal(total) }), threadMXBean, startState)
}
@@ -0,0 +1,20 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
import org.jetbrains.kotlin.cli.common.repl.ILineId
interface ReplStateFacadeAsync {
suspend fun getId(): Int
suspend fun getHistorySize(): Int
suspend fun historyGet(index: Int): ILineId
suspend fun historyReset(): List<ILineId>
suspend fun historyResetTo(id: ILineId): List<ILineId>
}
@@ -0,0 +1,30 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
import org.jetbrains.kotlin.cli.common.repl.ILineId
class ReplStateFacadeAsyncWrapper(val rmiReplStateFacade: ReplStateFacade) : ReplStateFacadeAsync {
override suspend fun getId() = rmiReplStateFacade.getId()
override suspend fun getHistorySize() = rmiReplStateFacade.getHistorySize()
override suspend fun historyGet(index: Int) = rmiReplStateFacade.historyGet(index)
override suspend fun historyReset() = rmiReplStateFacade.historyReset()
override suspend fun historyResetTo(id: ILineId) = rmiReplStateFacade.historyResetTo(id)
}
fun ReplStateFacade.toClient() = ReplStateFacadeAsyncWrapper(this)
fun CompileService.CallResult<ReplStateFacade>.toClient() = when (this) {
is CompileService.CallResult.Good -> CompileService.CallResult.Good(this.result.toClient())
is CompileService.CallResult.Dying -> this
is CompileService.CallResult.Error -> this
is CompileService.CallResult.Ok -> this
}
@@ -0,0 +1,32 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.repl.ILineId
import java.io.Serializable
class ReplStateFacadeRMIWrapper(val clientSide: ReplStateFacadeAsync) : ReplStateFacade, Serializable {
override fun getId() = runBlocking { clientSide.getId() }
override fun getHistorySize() = runBlocking { clientSide.getHistorySize() }
override fun historyGet(index: Int) = runBlocking { clientSide.historyGet(index) }
override fun historyReset() = runBlocking { clientSide.historyReset() }
override fun historyResetTo(id: ILineId) = runBlocking { clientSide.historyResetTo(id) }
}
fun ReplStateFacadeAsync.toRMI() = ReplStateFacadeRMIWrapper(this)
fun CompileService.CallResult<ReplStateFacadeAsync>.toRMI() = when (this) {
is CompileService.CallResult.Good -> CompileService.CallResult.Good(this.result.toRMI())
is CompileService.CallResult.Dying -> this
is CompileService.CallResult.Error -> this
is CompileService.CallResult.Ok -> this
}
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
import kotlinx.coroutines.runBlocking
fun <R> Profiler.withMeasureBlocking(obj: Any?, body: suspend () -> R): R = runBlocking { withMeasure<R>(obj) { body() } }
File diff suppressed because it is too large Load Diff
@@ -161,6 +161,7 @@ object KotlinCompileDaemon {
timer.cancel()
}
})
compilerService.startDaemonLife()
println(COMPILE_DAEMON_IS_READY_MESSAGE)
log.info("daemon is listening on port: $port")
@@ -40,15 +40,11 @@ import java.util.logging.Logger
import kotlin.concurrent.read
import kotlin.concurrent.write
open class KotlinJvmReplService(
disposable: Disposable,
val portForServers: Int,
val compilerId: CompilerId,
templateClasspath: List<File>,
templateClassName: String,
protected val messageCollector: MessageCollector,
@Deprecated("drop it")
protected val operationsTracer: RemoteOperationsTracer?
abstract class KotlinJvmReplServiceBase(
disposable: Disposable,
val compilerId: CompilerId,templateClasspath: List<File>,
templateClassName: String,
protected val messageCollector: MessageCollector
) : ReplCompileAction, ReplCheckAction, CreateReplStageStateAction {
private val log by lazy { Logger.getLogger("replService") }
@@ -64,7 +60,28 @@ open class KotlinJvmReplService(
configureScripting(compilerId)
}
private val replCompiler: ReplCompiler? by lazy {
protected fun makeScriptDefinition(templateClasspath: List<File>, templateClassName: String): KotlinScriptDefinition? {
val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), this::class.java.classLoader)
try {
val cls = classloader.loadClass(templateClassName)
val def = KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, emptyMap())
messageCollector.report(INFO, "New script definition $templateClassName: files pattern = \"${def.scriptFilePattern}\", " +
"resolver = ${def.dependencyResolver.javaClass.name}")
return def
}
catch (ex: ClassNotFoundException) {
messageCollector.report(ERROR, "Cannot find script definition template class $templateClassName")
}
catch (ex: Exception) {
messageCollector.report(ERROR, "Error processing script definition template $templateClassName: ${ex.message}")
}
return null
}
private val scriptDef = makeScriptDefinition(templateClasspath, templateClassName)
protected val replCompiler: ReplCompiler? by lazy {
try {
val projectEnvironment =
KotlinCoreEnvironment.ProjectEnvironment(
@@ -94,35 +111,56 @@ open class KotlinJvmReplService(
}
protected val statesLock = ReentrantReadWriteLock()
// TODO: consider using values here for session cleanup
protected val states = WeakHashMap<RemoteReplStateFacadeServer, Boolean>() // used as (missing) WeakHashSet
protected val stateIdCounter = AtomicInteger()
@Deprecated("remove after removal state-less check/compile/eval methods")
protected val defaultStateFacade: RemoteReplStateFacadeServer by lazy { createRemoteState() }
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> =
replCompiler?.createState(lock) ?: throw IllegalStateException("repl compiler is not initialized properly")
replCompiler?.createState(lock) ?: throw IllegalStateException("repl compiler is not initialized properly")
protected open fun before(s: String) {}
protected open fun after(s: String) {}
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult {
operationsTracer?.before("check")
before("check")
try {
return replCompiler?.check(state, codeLine) ?: ReplCheckResult.Error("Initialization error")
}
finally {
operationsTracer?.after("check")
} finally {
after("check")
}
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult {
operationsTracer?.before("compile")
before("compile")
try {
return replCompiler?.compile(state, codeLine) ?: ReplCompileResult.Error("Initialization error")
}
finally {
operationsTracer?.after("compile")
} finally {
after("compile")
}
}
}
open class KotlinJvmReplService(
disposable: Disposable,
val portForServers: Int,
templateClasspath: List<File>,
templateClassName: String,
messageCollector: MessageCollector,
@Deprecated("drop it")
protected val operationsTracer: RemoteOperationsTracer?
) : KotlinJvmReplServiceBase(disposable, templateClasspath, templateClassName, messageCollector) {
override fun before(s: String) {
operationsTracer?.before(s)
}
override fun after(s: String) {
operationsTracer?.after(s)
}
protected val states = WeakHashMap<RemoteReplStateFacadeServer, Boolean>() // used as (missing) WeakHashSet
@Deprecated("remove after removal state-less check/compile/eval methods")
protected val defaultStateFacade: RemoteReplStateFacadeServer by lazy { createRemoteState() }
@Deprecated("Use check(state, line) instead")
fun check(codeLine: ReplCodeLine): ReplCheckResult = check(defaultStateFacade.state, codeLine)
@@ -130,17 +168,17 @@ open class KotlinJvmReplService(
fun compile(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>?): ReplCompileResult = compile(defaultStateFacade.state, codeLine)
fun createRemoteState(port: Int = portForServers): RemoteReplStateFacadeServer = statesLock.write {
val id = getValidId(stateIdCounter) { id -> states.none { it.key.getId() == id} }
val id = getValidId(stateIdCounter) { id -> states.none { it.key.getId() == id } }
val stateFacade = RemoteReplStateFacadeServer(id, createState(), port)
states.put(stateFacade, true)
stateFacade
}
fun<R> withValidReplState(stateId: Int, body: (IReplStageState<*>) -> R): CompileService.CallResult<R> = statesLock.read {
fun <R> withValidReplState(stateId: Int, body: (IReplStageState<*>) -> R): CompileService.CallResult<R> = statesLock.read {
states.keys.firstOrNull { it.getId() == stateId }?.let {
CompileService.CallResult.Good(body(it.state))
}
?: CompileService.CallResult.Error("No REPL state with id $stateId found")
?: CompileService.CallResult.Error("No REPL state with id $stateId found")
}
}
@@ -165,9 +203,9 @@ internal class KeepFirstErrorMessageCollector(compilerMessagesStream: PrintStrea
}
}
internal val internalRng = Random()
val internalRng = Random()
inline internal fun getValidId(counter: AtomicInteger, check: (Int) -> Boolean): Int {
inline fun getValidId(counter: AtomicInteger, check: (Int) -> Boolean): Int {
// fighting hypothetical integer wrapping
var newId = counter.incrementAndGet()
var attemptsLeft = 100
@@ -181,7 +219,7 @@ inline internal fun getValidId(counter: AtomicInteger, check: (Int) -> Boolean):
return newId
}
private fun CompilerConfiguration.configureScripting(compilerId: CompilerId) {
fun CompilerConfiguration.configureScripting(compilerId: CompilerId) {
val error = try {
val componentRegistrars =
(this::class.java.classLoader as? URLClassLoader)?.let {
@@ -16,11 +16,8 @@
package org.jetbrains.kotlin.daemon
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
import org.jetbrains.kotlin.daemon.common.CompilerCallbackServicesFacade
import org.jetbrains.kotlin.daemon.common.DummyProfiler
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.RmiFriendlyCompilationCanceledException
import org.jetbrains.kotlin.progress.CompilationCanceledException
import java.util.concurrent.TimeUnit
import java.util.logging.Logger
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.daemon
import org.jetbrains.kotlin.daemon.common.CompilerCallbackServicesFacade
import org.jetbrains.kotlin.daemon.common.DummyProfiler
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.withMeasure
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import java.io.File
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.daemon
import org.jetbrains.kotlin.daemon.common.CompilerCallbackServicesFacade
import org.jetbrains.kotlin.daemon.common.DummyProfiler
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.withMeasure
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
import org.jetbrains.kotlin.modules.TargetId
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.daemon
import org.jetbrains.kotlin.daemon.common.CompilerCallbackServicesFacade
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.withMeasure
import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider
import org.jetbrains.kotlin.incremental.js.TranslationResultValue
import java.io.File
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.daemon
import org.jetbrains.kotlin.daemon.common.CompilerCallbackServicesFacade
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.withMeasure
import org.jetbrains.kotlin.incremental.js.FunctionWithSourceInfo
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer
import org.jetbrains.kotlin.incremental.js.JsInlineFunctionHash
@@ -15,7 +16,8 @@ import java.io.File
class RemoteIncrementalResultsConsumer(val facade: CompilerCallbackServicesFacade, eventManager: EventManager, val rpcProfiler: Profiler) :
IncrementalResultsConsumer {
init {
eventManager.onCompilationFinished(this::flush)
eventManager.
onCompilationFinished(this::flush)
}
override fun processHeader(headerMetadata: ByteArray) {
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.daemon
import org.jetbrains.kotlin.daemon.common.DummyProfiler
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.RemoteInputStream
import org.jetbrains.kotlin.daemon.common.withMeasure
import java.io.InputStream
class RemoteInputStreamClient(val remote: RemoteInputStream, val profiler: Profiler = DummyProfiler()): InputStream() {
@@ -22,6 +22,7 @@ import gnu.trove.THashSet
import org.jetbrains.kotlin.daemon.common.CompilerCallbackServicesFacade
import org.jetbrains.kotlin.daemon.common.DummyProfiler
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.withMeasure
import org.jetbrains.kotlin.incremental.components.LookupInfo
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.incremental.components.Position
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.daemon
import org.jetbrains.kotlin.daemon.common.DummyProfiler
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.RemoteOutputStream
import org.jetbrains.kotlin.daemon.common.withMeasure
import java.io.OutputStream
class RemoteOutputStreamClient(val remote: RemoteOutputStream, val profiler: Profiler = DummyProfiler()): OutputStream() {
@@ -0,0 +1,749 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("UNCHECKED_CAST")
package org.jetbrains.kotlin.daemon.experimental
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.impl.ZipHandler
import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.channels.consumeEach
import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
import org.jetbrains.kotlin.cli.js.K2JSCompiler
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.daemon.CompileServiceImplBase
import org.jetbrains.kotlin.daemon.CompilerSelector
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.experimental.CompileServiceTaskScheduler.*
import org.jetbrains.kotlin.daemon.nowSeconds
import org.jetbrains.kotlin.daemon.report.experimental.CompileServicesFacadeMessageCollector
import org.jetbrains.kotlin.daemon.report.experimental.DaemonMessageReporterAsync
import org.jetbrains.kotlin.daemon.report.experimental.getICReporterAsync
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.incremental.parsing.classesFqNames
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
import org.jetbrains.kotlin.progress.experimental.CompilationCanceledStatus
import java.io.File
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.logging.Level
import java.util.logging.Logger
import kotlin.concurrent.schedule
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.*
import org.jetbrains.kotlin.daemon.common.experimental.*
import io.ktor.network.sockets.*
import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY
import org.jetbrains.kotlin.daemon.EventManager
import org.jetbrains.kotlin.daemon.report.DaemonMessageReporter
// TODO: this classes should replace their non-experimental versions eventually.
private class CompileServiceTaskScheduler(log: Logger) {
interface CompileServiceTask
interface CompileServiceTaskWithResult : CompileServiceTask
open class ExclusiveTask(val completed: CompletableDeferred<Boolean>, val shutdownAction: suspend () -> Any) : CompileServiceTask
open class ShutdownTaskWithResult(val result: CompletableDeferred<Any>, shutdownAction: suspend () -> Any) :
ExclusiveTask(CompletableDeferred(), shutdownAction), CompileServiceTaskWithResult
open class OrdinaryTask(val completed: CompletableDeferred<Boolean>, val action: suspend () -> Any) : CompileServiceTask
class OrdinaryTaskWithResult(val result: CompletableDeferred<Any>, action: suspend () -> Any) :
OrdinaryTask(CompletableDeferred(), action),
CompileServiceTaskWithResult
class TaskFinished(val taskId: Int) : CompileServiceTask
class ExclusiveTaskFinished : CompileServiceTask
private var shutdownActionInProgress = false
private var readLocksCount = 0
suspend fun scheduleTask(task: CompileServiceTask) = queriesActor.send(task)
fun getReadLocksCnt() = readLocksCount
fun isShutdownActionInProgress() = shutdownActionInProgress
private val queriesActor = GlobalScope.actor<CompileServiceTask>(capacity = Channel.UNLIMITED) {
var currentTaskId = 0
var shutdownTask: ExclusiveTask? = null
val activeTaskIds = arrayListOf<Int>()
val waitingTasks = arrayListOf<CompileServiceTask>()
fun shutdownIfInactive(reason: String) {
log.fine("invoked \'shutdownIfInactive\', reason : $reason")
if (activeTaskIds.isEmpty()) {
shutdownTask?.let { task ->
shutdownActionInProgress = true
GlobalScope.async {
val res = task.shutdownAction()
task.completed.complete(true)
if (task is ShutdownTaskWithResult) {
task.result.complete(res)
}
channel.send(ExclusiveTaskFinished())
}
}
}
}
consumeEach { task ->
when (task) {
is ExclusiveTask -> {
if (shutdownTask == null) {
shutdownTask = task
shutdownIfInactive("ExclusiveTask")
} else {
waitingTasks.add(task)
}
}
is OrdinaryTask -> {
if (shutdownTask == null) {
val id = currentTaskId++
activeTaskIds.add(id)
readLocksCount++
GlobalScope.async {
val res = task.action()
if (task is OrdinaryTaskWithResult) {
task.result.complete(res)
}
task.completed.complete(true)
channel.send(TaskFinished(id))
}
} else {
waitingTasks.add(task)
}
}
is TaskFinished -> {
activeTaskIds.remove(task.taskId)
readLocksCount--
shutdownIfInactive("TaskFinished")
}
is ExclusiveTaskFinished -> {
shutdownTask = null
shutdownActionInProgress = false
waitingTasks.forEach {
channel.send(it)
}
waitingTasks.clear()
}
}
}
}
}
class CompileServiceServerSideImpl(
override val serverSocketWithPort: ServerSocketWrapper,
val compiler: CompilerSelector,
compilerId: CompilerId,
daemonOptions: DaemonOptions,
val daemonJVMOptions: DaemonJVMOptions,
port: Int,
timer: Timer,
val onShutdown: () -> Unit
) : CompileServiceServerSide, CompileServiceImplBase(daemonOptions, compilerId, port, timer) {
lateinit var rmiServer: CompileServiceRMIWrapper
private inline fun <R> withValidRepl(
sessionId: Int,
body: KotlinJvmReplServiceAsync.() -> CompileService.CallResult<R>
) = withValidReplImpl(sessionId, body)
override val serverPort: Int
get() = serverSocketWithPort.port
override val clients = hashMapOf<Socket, Server.ClientInfo>()
object KeepAliveServer : Server<ServerBase> {
override val serverSocketWithPort = findCallbackServerSocket()
override val clients = hashMapOf<Socket, Server.ClientInfo>()
}
override suspend fun checkClientCanReadFile(clientInputChannel: ByteReadChannelWrapper): Boolean = runWithTimeout {
getSignatureAndVerify(clientInputChannel, securityData.token, securityData.publicKey)
} ?: false
override suspend fun serverHandshake(input: ByteReadChannelWrapper, output: ByteWriteChannelWrapper, log: Logger): Boolean {
return tryAcquireHandshakeMessage(input, log) && trySendHandshakeMessage(output, log)
}
private lateinit var scheduler: CompileServiceTaskScheduler
constructor(
serverSocket: ServerSocketWrapper,
compilerId: CompilerId,
daemonOptions: DaemonOptions,
daemonJVMOptions: DaemonJVMOptions,
port: Int,
timer: Timer,
onShutdown: () -> Unit
) : this(
serverSocket,
object : CompilerSelector {
private val jvm by lazy { K2JVMCompiler() }
private val js by lazy { K2JSCompiler() }
private val metadata by lazy { K2MetadataCompiler() }
override fun get(targetPlatform: CompileService.TargetPlatform): CLICompiler<*> = when (targetPlatform) {
CompileService.TargetPlatform.JVM -> jvm
CompileService.TargetPlatform.JS -> js
CompileService.TargetPlatform.METADATA -> metadata
}
},
compilerId,
daemonOptions,
daemonJVMOptions,
port,
timer,
onShutdown
)
override val lastUsedSeconds: Long
get() = (if (scheduler.getReadLocksCnt() > 1 || scheduler.isShutdownActionInProgress()) nowSeconds() else _lastUsedSeconds).also {
log.fine(
"lastUsedSeconds .. isReadLockedCNT : ${scheduler.getReadLocksCnt()} , " +
"shutdownActionInProgress : ${scheduler.isShutdownActionInProgress()}"
)
}
private var securityData: SecurityData = generateKeysAndToken().also { sdata ->
runFile.outputStream().use {
sendTokenKeyPair(it, sdata.token, sdata.privateKey)
}
}
// RMI-exposed API
override suspend fun getDaemonInfo(): CompileService.CallResult<String> =
ifAlive(minAliveness = Aliveness.Dying) {
CompileService.CallResult.Good("Kotlin daemon on socketPort $port")
}
override suspend fun getDaemonOptions(): CompileService.CallResult<DaemonOptions> = ifAlive {
CompileService.CallResult.Good(daemonOptions)
}
override suspend fun getDaemonJVMOptions(): CompileService.CallResult<DaemonJVMOptions> = ifAlive {
log.info("getDaemonJVMOptions: $daemonJVMOptions")// + daemonJVMOptions.mappers.flatMap { it.toArgs("-") })
CompileService.CallResult.Good(daemonJVMOptions)
}
override suspend fun registerClient(aliveFlagPath: String?): CompileService.CallResult<Nothing> {
log.fine("fun registerClient")
return ifAlive(minAliveness = Aliveness.Alive) {
registerClientImpl(aliveFlagPath)
}
}
override suspend fun classesFqNamesByFiles(
sessionId: Int, sourceFiles: Set<File>
): CompileService.CallResult<Set<String>> =
ifAlive {
withValidClientOrSessionProxy(sessionId) {
CompileService.CallResult.Good(classesFqNames(sourceFiles))
}
}
private fun registerClientImpl(aliveFlagPath: String?): CompileService.CallResult<Nothing> {
state.addClient(aliveFlagPath)
log.info("Registered a client alive file: $aliveFlagPath")
return CompileService.CallResult.Ok()
}
override suspend fun getClients(): CompileService.CallResult<List<String>> = ifAlive {
getClientsImpl()
}
private fun getClientsImpl() = CompileService.CallResult.Good(state.getClientsFlagPaths())
// TODO: consider tying a session to a client and use this info to cleanup
override suspend fun leaseCompileSession(aliveFlagPath: String?): CompileService.CallResult<Int> =
ifAlive(minAliveness = Aliveness.Alive) {
CompileService.CallResult.Good(
state.sessions.leaseSession(ClientOrSessionProxy<Any>(aliveFlagPath)).apply {
log.info("leased a new session $this, session alive file: $aliveFlagPath")
})
}
override suspend fun releaseCompileSession(sessionId: Int) = ifAlive(minAliveness = Aliveness.LastSession) {
state.sessions.remove(sessionId)
log.info("cleaning after session $sessionId")
val completed = CompletableDeferred<Boolean>()
scheduler.scheduleTask(ExclusiveTask(completed, { clearJarCache() }))
completed.await()
postReleaseCompileSession()
}
override suspend fun checkCompilerId(expectedCompilerId: CompilerId): Boolean =
(compilerId.compilerVersion.isEmpty() || compilerId.compilerVersion == expectedCompilerId.compilerVersion) &&
(compilerId.compilerClasspath.all { expectedCompilerId.compilerClasspath.contains(it) }) &&
!classpathWatcher.isChanged
override suspend fun getUsedMemory(): CompileService.CallResult<Long> =
ifAlive { CompileService.CallResult.Good(usedMemory(withGC = true)) }
override suspend fun shutdown(): CompileService.CallResult<Nothing> =
ifAliveExclusive(minAliveness = Aliveness.LastSession) {
shutdownWithDelay()
CompileService.CallResult.Ok()
}
override suspend fun scheduleShutdown(graceful: Boolean): CompileService.CallResult<Boolean> =
ifAlive(minAliveness = Aliveness.LastSession) {
scheduleShutdownImpl(graceful)
}
private fun scheduleShutdownImpl(graceful: Boolean): CompileService.CallResult<Boolean> {
val res = when {
graceful -> gracefulShutdown(true)
else -> {
shutdownWithDelay()
true
}
}
return CompileService.CallResult.Good(res)
}
override suspend fun compile(
sessionId: Int,
compilerArguments: Array<out String>,
compilationOptions: CompilationOptions,
servicesFacade: CompilerServicesFacadeBaseAsync,
compilationResults: CompilationResultsAsync?
) = compileImpl(
sessionId,
compilerArguments,
compilationOptions,
servicesFacade,
compilationResults,
hasIncrementalCaches = CompilerCallbackServicesFacadeClientSide::hasIncrementalCaches,
createMessageCollector = ::CompileServicesFacadeMessageCollector,
createReporter = ::DaemonMessageReporterAsync,
createServices = this::createCompileServices,
getICReporter = ::getICReporterAsync
)
override suspend fun leaseReplSession(
aliveFlagPath: String?,
compilerArguments: Array<out String>,
compilationOptions: CompilationOptions,
servicesFacade: CompilerServicesFacadeBaseAsync,
templateClasspath: List<File>,
templateClassName: String
): CompileService.CallResult<Int> = ifAlive(minAliveness = Aliveness.Alive) {
if (compilationOptions.targetPlatform != CompileService.TargetPlatform.JVM)
CompileService.CallResult.Error("Sorry, only JVM target platform is supported now")
else {
val disposable = Disposer.newDisposable()
val messageCollector =
CompileServicesFacadeMessageCollector(servicesFacade, compilationOptions)
val repl = KotlinJvmReplServiceAsync(
disposable, serverSocketWithPort, templateClasspath, templateClassName,
messageCollector
)
val sessionId = state.sessions.leaseSession(ClientOrSessionProxy(aliveFlagPath, repl, disposable))
CompileService.CallResult.Good(sessionId)
}
}
// TODO: add more checks (e.g. is it a repl session)
override suspend fun releaseReplSession(sessionId: Int): CompileService.CallResult<Nothing> = releaseCompileSession(sessionId)
override suspend fun replCreateState(sessionId: Int): CompileService.CallResult<ReplStateFacadeClientSide> =
ifAlive(minAliveness = Aliveness.Alive) {
withValidRepl(sessionId) {
CompileService.CallResult.Good(
createRemoteState(findReplServerSocket()).clientSide
)
}
}
override suspend fun replCheck(
sessionId: Int,
replStateId: Int,
codeLine: ReplCodeLine
): CompileService.CallResult<ReplCheckResult> = ifAlive(minAliveness = Aliveness.Alive) {
withValidRepl(sessionId) {
withValidReplState(replStateId) { state ->
check(state, codeLine)
}
}
}
override suspend fun replCompile(
sessionId: Int,
replStateId: Int,
codeLine: ReplCodeLine
): CompileService.CallResult<ReplCompileResult> =
ifAlive(minAliveness = Aliveness.Alive) {
withValidRepl(sessionId) {
withValidReplState(replStateId) { state ->
compile(state, codeLine)
}
}
}
init {
scheduler = CompileServiceTaskScheduler(log)
// assuming logically synchronized
System.setProperty(KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY, "true")
// TODO UNCOMMENT THIS : this.toRMIServer(daemonOptions, compilerId) // also create RMI server in order to support old clients
rmiServer = this.toRMIServer(daemonOptions, compilerId)
KeepAliveServer.runServer()
}
override fun periodicAndAfterSessionCheck() {
if (state.delayedShutdownQueued.get()) return
val anyDead = state.sessions.cleanDead() || state.cleanDeadClients()
GlobalScope.async {
ifAliveUnit(minAliveness = Aliveness.LastSession) {
when {
// check if in graceful shutdown state and all sessions are closed
state.alive.get() == Aliveness.LastSession.ordinal && state.sessions.isEmpty() -> {
log.info("All sessions finished")
shutdownWithDelay()
return@ifAliveUnit
}
state.aliveClientsCount == 0 -> {
log.info("No more clients left")
shutdownWithDelay()
return@ifAliveUnit
}
// discovery file removed - shutdown
!runFile.exists() -> {
log.info("Run file removed")
shutdownWithDelay()
return@ifAliveUnit
}
}
}
ifAliveUnit(minAliveness = Aliveness.Alive) {
when {
daemonOptions.autoshutdownUnusedSeconds != COMPILE_DAEMON_TIMEOUT_INFINITE_S && compilationsCounter.get() == 0 && nowSeconds() - lastUsedSeconds > daemonOptions.autoshutdownUnusedSeconds -> {
log.info("Unused timeout exceeded ${daemonOptions.autoshutdownUnusedSeconds}s")
gracefulShutdown(false)
}
daemonOptions.autoshutdownIdleSeconds != COMPILE_DAEMON_TIMEOUT_INFINITE_S && nowSeconds() - lastUsedSeconds > daemonOptions.autoshutdownIdleSeconds -> {
log.info("Idle timeout exceeded ${daemonOptions.autoshutdownIdleSeconds}s")
gracefulShutdown(false)
}
anyDead -> {
clearJarCache()
}
}
}
}
}
override fun periodicSeldomCheck() {
GlobalScope.async {
ifAliveUnit(minAliveness = Aliveness.Alive) {
// compiler changed (seldom check) - shutdown
if (classpathWatcher.isChanged) {
log.info("Compiler changed.")
gracefulShutdown(false)
}
}
}
}
// TODO: handover should include mechanism for client to switch to a new daemon then previous "handed over responsibilities" and shot down
override fun initiateElections() {
runBlocking(Dispatchers.Unconfined) {
ifAliveUnit {
log.info("initiate elections")
val aliveWithOpts = walkDaemonsAsync(
File(daemonOptions.runFilesPathOrDefault),
compilerId,
runFile,
filter = { _, p -> p != port },
report = { _, msg -> log.info(msg) },
useRMI = false
)
log.fine("aliveWithOpts : ${aliveWithOpts.map { it.daemon.javaClass.name }}")
val comparator = compareByDescending<DaemonWithMetadataAsync, DaemonJVMOptions>(
DaemonJVMOptionsMemoryComparator(),
{ it.jvmOptions }
)
.thenBy {
when (it.daemon) {
is CompileServiceAsyncWrapper -> 0
else -> 1
}
}
.thenBy(FileAgeComparator()) { it.runFile }
.thenBy { it.daemon.serverPort }
aliveWithOpts.maxWith(comparator)?.let { bestDaemonWithMetadata ->
val fattestOpts = bestDaemonWithMetadata.jvmOptions
if (fattestOpts memorywiseFitsInto daemonJVMOptions && FileAgeComparator().compare(
bestDaemonWithMetadata.runFile,
runFile
) < 0
) {
// all others are smaller that me, take overs' clients and shut them down
log.info("$LOG_PREFIX_ASSUMING_OTHER_DAEMONS_HAVE lower prio, taking clients from them and schedule them to shutdown: my runfile: ${runFile.name} (${runFile.lastModified()}) vs best other runfile: ${bestDaemonWithMetadata.runFile.name} (${bestDaemonWithMetadata.runFile.lastModified()})")
aliveWithOpts.forEach { (daemon, runFile, _) ->
try {
log.fine("other : $daemon")
daemon.getClients().takeIf { it.isGood }?.let {
it.get().forEach { clientAliveFile ->
registerClientImpl(clientAliveFile)
}
}
log.fine("other : CLIENTS_OK")
daemon.scheduleShutdown(true)
log.fine("other : SHUTDOWN_OK")
} catch (e: Throwable) {
log.info("Cannot connect to a daemon, assuming dying ('${runFile.canonicalPath}'): ${e.message}")
}
}
}
// TODO: seems that the second part of condition is incorrect, reconsider:
// the comment by @tsvtkv from review:
// Algorithm in plain english:
// (1) If the best daemon fits into me and the best daemon is younger than me, then I take over all other daemons clients.
// (2) If I fit into the best daemon and the best daemon is older than me, then I give my clients to that daemon.
//
// For example:
//
// daemon A starts with params: maxMem=100, codeCache=50
// daemon B starts with params: maxMem=200, codeCache=50
// daemon C starts with params: maxMem=150, codeCache=100
// A performs election: (1) is false because neither B nor C does not fit into A, (2) is false because both B and C are younger than A.
// B performs election: (1) is false because neither A nor C does not fit into B, (2) is false because B does not fit into neither A nor C.
// C performs election: (1) is false because B is better than A and B does not fit into C, (2) is false C does not fit into neither A nor B.
// Result: all daemons are alive and well.
else if (daemonJVMOptions memorywiseFitsInto fattestOpts && FileAgeComparator().compare(
bestDaemonWithMetadata.runFile,
runFile
) > 0
) {
// there is at least one bigger, handover my clients to it and shutdown
log.info("$LOG_PREFIX_ASSUMING_OTHER_DAEMONS_HAVE higher prio, handover clients to it and schedule shutdown: my runfile: ${runFile.name} (${runFile.lastModified()}) vs best other runfile: ${bestDaemonWithMetadata.runFile.name} (${bestDaemonWithMetadata.runFile.lastModified()})")
getClientsImpl().takeIf { it.isGood }?.let {
it.get().forEach { bestDaemonWithMetadata.daemon.registerClient(it) }
}
scheduleShutdownImpl(true)
} else {
// undecided, do nothing
log.info("$LOG_PREFIX_ASSUMING_OTHER_DAEMONS_HAVE equal prio, continue: ${runFile.name} (${runFile.lastModified()}) vs best other runfile: ${bestDaemonWithMetadata.runFile.name} (${bestDaemonWithMetadata.runFile.lastModified()})")
// TODO: implement some behaviour here, e.g.:
// - shutdown/takeover smaller daemon
// - runServer (or better persuade client to runServer) a bigger daemon (in fact may be even simple shutdown will do, because of client's daemon choosing logic)
}
}
}
}
}
private fun shutdownNow() {
log.info("Shutdown started")
fun Long.mb() = this / (1024 * 1024)
with(Runtime.getRuntime()) {
log.info("Memory stats: total: ${totalMemory().mb()}mb, free: ${freeMemory().mb()}mb, max: ${maxMemory().mb()}mb")
}
state.alive.set(Aliveness.Dying.ordinal)
shutdownServer()
log.info("Shutdown complete")
onShutdown()
log.handlers.forEach { it.flush() }
}
private fun shutdownWithDelayImpl(currentClientsCount: Int, currentSessionId: Int, currentCompilationsCount: Int) {
log.fine("${log.name} .......shutdowning........")
log.fine("${log.name} currentCompilationsCount = $currentCompilationsCount, compilationsCounter.get(): ${compilationsCounter.get()}")
state.delayedShutdownQueued.set(false)
if (currentClientsCount == state.clientsCounter &&
currentCompilationsCount == compilationsCounter.get() &&
currentSessionId == state.sessions.lastSessionId
) {
log.fine("currentCompilationsCount == compilationsCounter.get()")
runBlocking(Dispatchers.Unconfined) {
ifAliveExclusiveUnit(minAliveness = Aliveness.LastSession) {
log.info("Execute delayed shutdown")
shutdownNow()
}
}
} else {
log.info("Cancel delayed shutdown due to a new activity")
}
}
private fun shutdownWithDelay() {
state.delayedShutdownQueued.set(true)
val currentClientsCount = state.clientsCounter
val currentSessionId = state.sessions.lastSessionId
val currentCompilationsCount = compilationsCounter.get()
log.info("Delayed shutdown in ${daemonOptions.shutdownDelayMilliseconds}ms")
timer.schedule(daemonOptions.shutdownDelayMilliseconds) {
shutdownWithDelayImpl(currentClientsCount, currentSessionId, currentCompilationsCount)
}
}
private fun gracefulShutdown(onAnotherThread: Boolean): Boolean {
if (!state.alive.compareAndSet(Aliveness.Alive.ordinal, Aliveness.LastSession.ordinal)) {
log.info("Invalid state for graceful shutdown: ${state.alive.get().toAlivenessName()}")
return false
}
log.info("Graceful shutdown signalled")
if (!onAnotherThread) {
shutdownIfIdle()
} else {
timer.schedule(1) {
gracefulShutdownImpl()
}
}
return true
}
private fun gracefulShutdownImpl() {
runBlocking(Dispatchers.Unconfined) {
ifAliveExclusiveUnit(minAliveness = Aliveness.LastSession) {
shutdownIfIdle()
}
}
}
private fun shutdownIfIdle() = when {
state.sessions.isEmpty() -> shutdownWithDelay()
else -> {
daemonOptions.autoshutdownIdleSeconds =
TimeUnit.MILLISECONDS.toSeconds(daemonOptions.forceShutdownTimeoutMilliseconds).toInt()
daemonOptions.autoshutdownUnusedSeconds = daemonOptions.autoshutdownIdleSeconds
log.info("Some sessions are active, waiting for them to finish")
log.info("Unused/idle timeouts are set to ${daemonOptions.autoshutdownUnusedSeconds}/${daemonOptions.autoshutdownIdleSeconds}s")
}
}
private suspend fun createCompileServices(
facade: CompilerCallbackServicesFacadeClientSide,
eventManager: EventManager,
rpcProfiler: Profiler
): Services {
val builder = Services.Builder()
if (facade.hasIncrementalCaches()) {
builder.register(
IncrementalCompilationComponents::class.java,
RemoteIncrementalCompilationComponentsClient(facade, eventManager, rpcProfiler)
)
}
if (facade.hasLookupTracker()) {
builder.register(LookupTracker::class.java, RemoteLookupTrackerClient(facade, eventManager, rpcProfiler))
}
if (facade.hasCompilationCanceledStatus()) {
log.fine("facade.hasCompilationCanceledStatus() = true")
builder.register(CompilationCanceledStatus::class.java, RemoteCompilationCanceledStatusClient(facade, rpcProfiler))
} else {
log.fine("facade.hasCompilationCanceledStatus() = false")
}
return builder.build()
}
override suspend fun clearJarCache() {
ZipHandler.clearFileAccessorCache()
(KotlinCoreEnvironment.applicationEnvironment?.jarFileSystem as? CoreJarFileSystem)?.clearHandlersCache()
}
private suspend fun <R> ifAlive(
minAliveness: Aliveness = Aliveness.LastSession,
body: suspend () -> CompileService.CallResult<R>
): CompileService.CallResult<R> {
val result = CompletableDeferred<Any>()
scheduler.scheduleTask(OrdinaryTaskWithResult(result) {
ifAliveChecksImplSuspend(minAliveness, body)
})
return result.await() as CompileService.CallResult<R>
}
private suspend fun ifAliveUnit(
minAliveness: Aliveness = Aliveness.LastSession,
body: suspend () -> Unit
) {
val completed = CompletableDeferred<Boolean>()
scheduler.scheduleTask(
OrdinaryTask(completed) {
ifAliveChecksImplSuspend(minAliveness) {
body()
CompileService.CallResult.Ok()
}
}
)
completed.await()
}
private suspend fun <R> ifAliveExclusive(
minAliveness: Aliveness = Aliveness.LastSession,
body: suspend () -> CompileService.CallResult<R>
): CompileService.CallResult<R> {
val result = CompletableDeferred<Any>()
scheduler.scheduleTask(ShutdownTaskWithResult(result) {
ifAliveChecksImplSuspend(minAliveness, body)
})
return result.await() as CompileService.CallResult<R>
}
private suspend fun ifAliveExclusiveUnit(
minAliveness: Aliveness = Aliveness.LastSession,
body: suspend () -> Unit
): CompileService.CallResult<Unit> {
val result = CompletableDeferred<Any>()
scheduler.scheduleTask(ShutdownTaskWithResult(result) {
ifAliveChecksImplSuspend(minAliveness) {
body()
CompileService.CallResult.Ok()
}
})
return result.await() as CompileService.CallResult<Unit>
}
private suspend fun <R> ifAliveChecksImplSuspend(
minAliveness: Aliveness = Aliveness.LastSession,
body: suspend () -> CompileService.CallResult<R>
): CompileService.CallResult<R> {
val curState = state.alive.get()
return when {
curState < minAliveness.ordinal -> {
log.info("Cannot perform operation, requested state: ${minAliveness.name} > actual: ${curState.toAlivenessName()}")
CompileService.CallResult.Dying()
}
else -> {
try {
body()
} catch (e: Throwable) {
log.log(Level.SEVERE, "Exception", e)
CompileService.CallResult.Error(e.message ?: "unknown")
}
}
}
}
}
@@ -0,0 +1,202 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.experimental
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.cli.js.K2JSCompiler
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler
import org.jetbrains.kotlin.daemon.CompilerSelector
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.common.experimental.findPortForSocket
import org.jetbrains.kotlin.daemon.common.ensureServerHostnameIsSetUp
import java.io.File
import java.io.IOException
import java.io.OutputStream
import java.io.PrintStream
import java.lang.management.ManagementFactory
import java.net.URLClassLoader
import java.text.SimpleDateFormat
import java.util.*
import java.util.jar.Manifest
import java.util.logging.Level
import java.util.logging.LogManager
import java.util.logging.Logger
import kotlin.concurrent.schedule
class LogStream(name: String) : OutputStream() {
val log by lazy { Logger.getLogger(name) }
val lineBuf = StringBuilder()
override fun write(byte: Int) {
if (byte.toChar() == '\n') flush()
else lineBuf.append(byte.toChar())
}
override fun flush() {
log.info(lineBuf.toString())
lineBuf.setLength(0)
}
}
object KotlinCompileDaemon {
init {
val logTime: String = SimpleDateFormat("yyyy-MM-dd.HH-mm-ss-SSS").format(Date())
val (logPath: String, fileIsGiven: Boolean) =
System.getProperty(COMPILE_DAEMON_LOG_PATH_PROPERTY)?.trimQuotes()?.let { Pair(it, File(it).isFile) } ?: Pair("%t", false)
val cfg: String =
"handlers = java.util.logging.FileHandler\n" +
"java.util.logging.FileHandler.level = ALL\n" +
"java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter\n" +
"java.util.logging.FileHandler.encoding = UTF-8\n" +
"java.util.logging.FileHandler.limit = ${if (fileIsGiven) 0 else (1 shl 20)}\n" + // if file is provided - disabled, else - 1Mb
"java.util.logging.FileHandler.count = ${if (fileIsGiven) 1 else 3}\n" +
"java.util.logging.FileHandler.append = $fileIsGiven\n" +
"java.util.logging.FileHandler.pattern = ${if (fileIsGiven) logPath else (logPath + File.separator + "$COMPILE_DAEMON_DEFAULT_FILES_PREFIX.$logTime.%u%g.log")}\n" +
"java.util.logging.SimpleFormatter.format = %1\$tF %1\$tT.%1\$tL [%3\$s] %4\$s: %5\$s%n\n"
LogManager.getLogManager().readConfiguration(cfg.byteInputStream())
}
val log by lazy { Logger.getLogger("daemon") }
private fun loadVersionFromResource(): String? {
(KotlinCompileDaemon::class.java.classLoader as? URLClassLoader)
?.findResource("META-INF/MANIFEST.MF")
?.let {
try {
return Manifest(it.openStream()).mainAttributes.getValue("Implementation-Version") ?: null
} catch (e: IOException) {
}
}
return null
}
@JvmStatic
fun main(args: Array<String>) {
ensureServerHostnameIsSetUp()
val jvmArguments = ManagementFactory.getRuntimeMXBean().inputArguments
log.info("Kotlin compiler daemon version " + (loadVersionFromResource() ?: "<unknown>"))
log.info("daemon JVM args: " + jvmArguments.joinToString(" "))
log.info("daemon args: " + args.joinToString(" "))
setIdeaIoUseFallback()
val compilerId = CompilerId()
val daemonOptions = DaemonOptions()
runBlocking {
var serverRun: Deferred<Unit>?
try {
val daemonJVMOptions = configureDaemonJVMOptions(
inheritMemoryLimits = true,
inheritOtherJvmOptions = true,
inheritAdditionalProperties = true
)
val filteredArgs = args.asIterable()
.filterExtractProps(
compilerId,
daemonOptions,
prefix = COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX
)
if (filteredArgs.any()) {
val helpLine = "usage: <daemon> <compilerId options> <daemon options>"
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())
//
// setDaemonPermissions(daemonOptions.socketPort)
val port = findPortForSocket(
COMPILE_DAEMON_FIND_PORT_ATTEMPTS,
COMPILE_DAEMON_PORTS_RANGE_START,
COMPILE_DAEMON_PORTS_RANGE_END
)
val compilerSelector = object : CompilerSelector {
private val jvm by lazy { K2JVMCompiler() }
private val js by lazy { K2JSCompiler() }
private val metadata by lazy { K2MetadataCompiler() }
override fun get(targetPlatform: CompileService.TargetPlatform): CLICompiler<*> = when (targetPlatform) {
CompileService.TargetPlatform.JVM -> jvm
CompileService.TargetPlatform.JS -> js
CompileService.TargetPlatform.METADATA -> metadata
}
}
// timer with a daemon thread, meaning it should not prevent JVM to exit normally
val timer = Timer(true)
val compilerService = CompileServiceServerSideImpl(
port,
compilerSelector,
compilerId,
daemonOptions,
daemonJVMOptions,
port.port,
timer,
{
if (daemonOptions.forceShutdownTimeoutMilliseconds != COMPILE_DAEMON_TIMEOUT_INFINITE_MS) {
// running a watcher thread that ensures that if the daemon is not exited normally (may be due to RMI leftovers), it's forced to exit
timer.schedule(daemonOptions.forceShutdownTimeoutMilliseconds) {
cancel()
log.info("force JVM shutdown")
System.exit(0)
}
} else {
timer.cancel()
}
})
compilerService.startDaemonLife()
serverRun = compilerService.runServer()
println(COMPILE_DAEMON_IS_READY_MESSAGE)
log.info("daemon is listening on port: ${port.port}")
// this supposed to stop redirected streams reader(s) on the client side and prevent some situations with hanging threads, but doesn't work reliably
// TODO: implement more reliable scheme
System.out.close()
System.err.close()
System.setErr(PrintStream(LogStream("stderr")))
System.setOut(PrintStream(LogStream("stdout")))
} catch (e: Exception) {
log.log(Level.ALL, "Exception: " + e.message)
e.printStackTrace(System.err)
// repeating it to log for the cases when stderr is not redirected yet
log.log(Level.INFO, "Exception: ", e)
// TODO consider exiting without throwing
throw e
}
serverRun.await()
}
}
}
@@ -0,0 +1,51 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.experimental
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.IReplStageState
import org.jetbrains.kotlin.cli.jvm.repl.GenericReplCompilerState
import org.jetbrains.kotlin.daemon.KotlinJvmReplServiceBase
import org.jetbrains.kotlin.daemon.common.CompileService
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.ServerSocketWrapper
import org.jetbrains.kotlin.daemon.getValidId
import java.io.File
import java.util.*
import kotlin.concurrent.read
import kotlin.concurrent.write
open class KotlinJvmReplServiceAsync(
disposable: Disposable,
val portForServers: ServerSocketWrapper,
templateClasspath: List<File>,
templateClassName: String,
messageCollector: MessageCollector
) : KotlinJvmReplServiceBase(disposable, templateClasspath, templateClassName, messageCollector) {
protected val states = WeakHashMap<RemoteReplStateFacadeServerSide, Boolean>() // used as (missing) WeakHashSet
suspend fun createRemoteState(port: ServerSocketWrapper = portForServers): RemoteReplStateFacadeServerSide = statesLock.write {
val id = getValidId(stateIdCounter) { id -> states.none { it.key.getId() == id } }
val stateFacade = RemoteReplStateFacadeServerSide(
id,
createState().asState(GenericReplCompilerState::class.java),
port
)
stateFacade.runServer()
states.put(stateFacade, true)
stateFacade
}
suspend fun <R> withValidReplState(
stateId: Int,
body: (IReplStageState<*>) -> R
): CompileService.CallResult<R> = statesLock.read {
states.keys.firstOrNull { it.getId() == stateId }?.let {
CompileService.CallResult.Good(body(it.state))
} ?: CompileService.CallResult.Error("No REPL state with id $stateId found")
}
}
@@ -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.daemon.experimental
import org.jetbrains.kotlin.daemon.common.RmiFriendlyCompilationCanceledException
import org.jetbrains.kotlin.daemon.common.experimental.CompilerCallbackServicesFacadeClientSide
import org.jetbrains.kotlin.daemon.common.DummyProfiler
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.withMeasure
import org.jetbrains.kotlin.progress.CompilationCanceledException
import org.jetbrains.kotlin.progress.experimental.CompilationCanceledStatus
import java.util.concurrent.TimeUnit
import java.util.logging.Logger
val CANCELED_STATUS_CHECK_THRESHOLD_NS = TimeUnit.MILLISECONDS.toNanos(100)
class RemoteCompilationCanceledStatusClient(
val facade: CompilerCallbackServicesFacadeClientSide,
val profiler: Profiler = DummyProfiler()
) : CompilationCanceledStatus {
private val log by lazy { Logger.getLogger("RemoteCompilationCanceledStatusClient") }
@Volatile
var lastChecked: Long = System.nanoTime()
override suspend fun checkCanceled() {
fun cancelOnError(e: Exception) {
log.warning("error communicating with host, assuming compilation canceled (${e.message})")
throw CompilationCanceledException()
}
val curNanos = System.nanoTime()
if (curNanos - lastChecked > CANCELED_STATUS_CHECK_THRESHOLD_NS) {
profiler.withMeasure(this) {
try {
facade.compilationCanceledStatus_checkCanceled()
} catch (e: RmiFriendlyCompilationCanceledException) {
throw CompilationCanceledException()
} catch (e: java.rmi.ConnectIOException) {
cancelOnError(e)
} catch (e: java.rmi.ConnectException) {
cancelOnError(e)
} catch (e: java.rmi.NoSuchObjectException) {
// this was added mostly for tests since others are more difficult to emulate
cancelOnError(e)
} catch (e: java.rmi.UnmarshalException) {
cancelOnError(e)
}
}
lastChecked = curNanos
}
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.experimental
import org.jetbrains.kotlin.daemon.common.experimental.CompilerCallbackServicesFacadeClientSide
import org.jetbrains.kotlin.daemon.common.DummyProfiler
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.withMeasureBlocking
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
import org.jetbrains.kotlin.modules.TargetId
class RemoteIncrementalCacheClient(val facade: CompilerCallbackServicesFacadeClientSide, val target: TargetId, val profiler: Profiler = DummyProfiler()):
IncrementalCache {
override fun getObsoletePackageParts(): Collection<String> = profiler.withMeasureBlocking(this) { facade.incrementalCache_getObsoletePackageParts(target) }
override fun getObsoleteMultifileClasses(): Collection<String> = profiler.withMeasureBlocking(this) { facade.incrementalCache_getObsoleteMultifileClassFacades(target) }
override fun getStableMultifileFacadeParts(facadeInternalName: String): Collection<String>? = profiler.withMeasureBlocking(this) { facade.incrementalCache_getMultifileFacadeParts(target, facadeInternalName) }
override fun getPackagePartData(partInternalName: String): JvmPackagePartProto? = profiler.withMeasureBlocking(this) { facade.incrementalCache_getPackagePartData(target, partInternalName) }
override fun getModuleMappingData(): ByteArray? = profiler.withMeasureBlocking(this) { facade.incrementalCache_getModuleMappingData(target) }
override fun getClassFilePath(internalClassName: String): String = profiler.withMeasureBlocking(this) { facade.incrementalCache_getClassFilePath(target, internalClassName) }
override fun close(): Unit = profiler.withMeasureBlocking(this) { facade.incrementalCache_close(target) }
}
@@ -0,0 +1,18 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.experimental
import org.jetbrains.kotlin.daemon.EventManager
import org.jetbrains.kotlin.daemon.common.experimental.CompilerCallbackServicesFacadeClientSide
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.daemon.common.DummyProfiler
import org.jetbrains.kotlin.daemon.common.Profiler
class RemoteIncrementalCompilationComponentsClient(val facade: CompilerCallbackServicesFacadeClientSide, eventManager: EventManager, val profiler: Profiler = DummyProfiler()) : IncrementalCompilationComponents {
override fun getIncrementalCache(target: TargetId): IncrementalCache = RemoteIncrementalCacheClient(facade, target, profiler)
}
@@ -0,0 +1,59 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.experimental
import com.intellij.util.containers.StringInterner
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.daemon.EventManager
import org.jetbrains.kotlin.daemon.common.experimental.CompilerCallbackServicesFacadeClientSide
import org.jetbrains.kotlin.daemon.common.DummyProfiler
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.withMeasure
import org.jetbrains.kotlin.daemon.common.withMeasureBlocking
import org.jetbrains.kotlin.incremental.components.LookupInfo
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.incremental.components.Position
import org.jetbrains.kotlin.incremental.components.ScopeKind
class RemoteLookupTrackerClient(
val facade: CompilerCallbackServicesFacadeClientSide,
eventManager: EventManager,
val profiler: Profiler = DummyProfiler()
) : LookupTracker {
private val isDoNothing = runBlocking { profiler.withMeasure(this) { facade.lookupTracker_isDoNothing() } }
private val lookups = hashSetOf<LookupInfo>()
private val interner = StringInterner()
override val requiresPosition: Boolean = runBlocking { profiler.withMeasure(this) { facade.lookupTracker_requiresPosition() } }
override fun record(filePath: String, position: Position, scopeFqName: String, scopeKind: ScopeKind, name: String) {
if (isDoNothing) return
val internedFilePath = interner.intern(filePath)
val internedScopeFqName = interner.intern(scopeFqName)
val internedName = interner.intern(name)
lookups.add(LookupInfo(internedFilePath, position, internedScopeFqName, scopeKind, internedName))
}
init {
runBlocking {
eventManager.onCompilationFinished { flush() }
}
}
private fun flush() {
if (isDoNothing || lookups.isEmpty()) return
profiler.withMeasureBlocking(this) {
facade.lookupTracker_record(lookups)
}
lookups.clear()
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.experimental
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.daemon.common.experimental.RemoteOutputStreamAsyncClientSide
import org.jetbrains.kotlin.daemon.common.DummyProfiler
import org.jetbrains.kotlin.daemon.common.Profiler
import org.jetbrains.kotlin.daemon.common.withMeasure
import java.io.OutputStream
class RemoteOutputStreamClient(val remote: RemoteOutputStreamAsyncClientSide, val profiler: Profiler = DummyProfiler()) : OutputStream() {
override fun write(data: ByteArray) = runBlocking {
profiler.withMeasure(this) { remote.write(data, 0, data.size) }
}
override fun write(data: ByteArray, offset: Int, length: Int) = runBlocking {
profiler.withMeasure(this) { remote.write(data, offset, length) }
}
override fun write(byte: Int) = runBlocking {
profiler.withMeasure(this) { remote.write(byte) }
}
}
@@ -0,0 +1,73 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.experimental
import io.ktor.network.sockets.Socket
import org.jetbrains.kotlin.cli.common.repl.ILineId
import org.jetbrains.kotlin.cli.jvm.repl.GenericReplCompilerState
import org.jetbrains.kotlin.daemon.common.COMPILE_DAEMON_FIND_PORT_ATTEMPTS
import org.jetbrains.kotlin.daemon.common.experimental.*
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.*
@Suppress("UNCHECKED_CAST")
class RemoteReplStateFacadeServerSide(
val _id: Int,
val state: GenericReplCompilerState,
override val serverSocketWithPort: ServerSocketWrapper = findPortForSocket(
COMPILE_DAEMON_FIND_PORT_ATTEMPTS,
REPL_SERVER_PORTS_RANGE_START,
REPL_SERVER_PORTS_RANGE_END
)
) : ReplStateFacadeServerSide {
override val clients = hashMapOf<Socket, Server.ClientInfo>()
override suspend fun getId(): Int = _id
override suspend fun getHistorySize(): Int = state.history.size
override suspend fun historyGet(index: Int): ILineId = state.history[index].id
override suspend fun historyReset(): List<ILineId> = state.history.reset().toList()
override suspend fun historyResetTo(id: ILineId): List<ILineId> = state.history.resetTo(id).toList()
val clientSide: RemoteReplStateFacadeClientSide
get() = RemoteReplStateFacadeClientSide(serverSocketWithPort.port)
}
class RemoteReplStateFacadeClientSide(val serverPort: Int) : ReplStateFacadeClientSide, Client<ReplStateFacadeServerSide> by DefaultClient(serverPort) {
override suspend fun getId(): Int {
val id = sendMessage(ReplStateFacadeServerSide.GetIdMessage())
return readMessage(id)
}
override suspend fun getHistorySize(): Int {
val id = sendMessage(ReplStateFacadeServerSide.GetHistorySizeMessage())
return readMessage(id)
}
override suspend fun historyGet(index: Int): ILineId {
val id = sendMessage(ReplStateFacadeServerSide.HistoryGetMessage(index))
return readMessage(id)
}
override suspend fun historyReset(): List<ILineId> {
val id = sendMessage(ReplStateFacadeServerSide.HistoryResetMessage())
return readMessage(id)
}
override suspend fun historyResetTo(id: ILineId): List<ILineId> {
val id = sendMessage(ReplStateFacadeServerSide.HistoryResetToMessage(id))
return readMessage(id)
}
}
@@ -19,11 +19,11 @@ package org.jetbrains.kotlin.daemon.report
import org.jetbrains.kotlin.daemon.common.*
import java.io.PrintStream
internal interface DaemonMessageReporter {
interface DaemonMessageReporter {
fun report(severity: ReportSeverity, message: String)
}
internal fun DaemonMessageReporter(
fun DaemonMessageReporter(
servicesFacade: CompilerServicesFacadeBase,
compilationOptions: CompilationOptions
): DaemonMessageReporter =
@@ -7,6 +7,6 @@ package org.jetbrains.kotlin.daemon.report
import org.jetbrains.kotlin.incremental.ICReporter
internal interface RemoteICReporter : ICReporter {
interface RemoteICReporter : ICReporter {
fun flush()
}
@@ -0,0 +1,60 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.report.experimental
import kotlinx.coroutines.*
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.daemon.KotlinCompileDaemon.log
import org.jetbrains.kotlin.daemon.common.CompilationOptions
import org.jetbrains.kotlin.daemon.common.ReportCategory
import org.jetbrains.kotlin.daemon.common.ReportSeverity
import org.jetbrains.kotlin.daemon.common.CompilerServicesFacadeBaseAsync
import org.jetbrains.kotlin.daemon.common.report
internal class CompileServicesFacadeMessageCollector(
private val servicesFacade: CompilerServicesFacadeBaseAsync,
compilationOptions: CompilationOptions
) : MessageCollector {
private val mySeverity = compilationOptions.reportSeverity
private var hasErrors = false
override fun clear() {
hasErrors = false
}
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
GlobalScope.async {
log.info("Message: " + MessageRenderer.WITHOUT_PATHS.render(severity, message, location))
when (severity) {
CompilerMessageSeverity.OUTPUT -> {
servicesFacade.report(ReportCategory.OUTPUT_MESSAGE, ReportSeverity.ERROR, message)
}
CompilerMessageSeverity.EXCEPTION -> {
servicesFacade.report(ReportCategory.EXCEPTION, ReportSeverity.ERROR, message)
}
else -> {
val reportSeverity = when (severity) {
CompilerMessageSeverity.ERROR -> ReportSeverity.ERROR
CompilerMessageSeverity.WARNING, CompilerMessageSeverity.STRONG_WARNING -> ReportSeverity.WARNING
CompilerMessageSeverity.INFO -> ReportSeverity.INFO
else -> ReportSeverity.DEBUG
}
if (reportSeverity.code <= mySeverity) {
servicesFacade.report(ReportCategory.COMPILER_MESSAGE, reportSeverity, message, location)
}
}
}
hasErrors = hasErrors || severity.isError
}
}
override fun hasErrors(): Boolean = hasErrors
}
@@ -0,0 +1,49 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.report.experimental
import kotlinx.coroutines.*
import org.jetbrains.kotlin.daemon.common.CompilationOptions
import org.jetbrains.kotlin.daemon.common.ReportCategory
import org.jetbrains.kotlin.daemon.common.ReportSeverity
import org.jetbrains.kotlin.daemon.common.CompilerServicesFacadeBaseAsync
import org.jetbrains.kotlin.daemon.report.DaemonMessageReporter
import java.io.PrintStream
internal fun DaemonMessageReporterAsync(
servicesFacade: CompilerServicesFacadeBaseAsync,
compilationOptions: CompilationOptions
): DaemonMessageReporter =
if (ReportCategory.DAEMON_MESSAGE.code in compilationOptions.reportCategories) {
val mySeverity = ReportSeverity.fromCode(compilationOptions.reportSeverity)!!
DaemonMessageReporterAsyncAsyncImpl(servicesFacade, mySeverity)
} else {
DummyDaemonMessageReporterAsync
}
internal class DaemonMessageReporterAsyncPrintStreamAdapter(private val out: PrintStream) : DaemonMessageReporter {
override fun report(severity: ReportSeverity, message: String) {
out.print("[Kotlin compile daemon][$severity] $message")
}
}
private class DaemonMessageReporterAsyncAsyncImpl(
private val servicesFacade: CompilerServicesFacadeBaseAsync,
private val mySeverity: ReportSeverity
) : DaemonMessageReporter {
override fun report(severity: ReportSeverity, message: String) {
GlobalScope.async {
if (severity.code <= mySeverity.code) {
servicesFacade.report(ReportCategory.DAEMON_MESSAGE.code, severity.code, message, attachment = null)
}
}
}
}
private object DummyDaemonMessageReporterAsync : DaemonMessageReporter {
override fun report(severity: ReportSeverity, message: String) {
}
}
@@ -0,0 +1,170 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.report.experimental
import kotlinx.coroutines.*
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.report.RemoteICReporter
import org.jetbrains.kotlin.incremental.ICReporter
import org.jetbrains.kotlin.incremental.ICReporterBase
import java.io.File
import java.io.Serializable
internal class DebugMessagesICReporterAsync(
private val servicesFacade: CompilerServicesFacadeBaseAsync,
rootDir: File,
private val isVerbose: Boolean
) : ICReporterBase(rootDir), RemoteICReporter {
override fun report(message: () -> String) {
GlobalScope.async {
servicesFacade.report(
ReportCategory.IC_MESSAGE,
ReportSeverity.DEBUG, message()
)
}
}
override fun reportVerbose(message: () -> String) {
if (isVerbose) {
report(message)
}
}
override fun reportCompileIteration(incremental: Boolean, sourceFiles: Collection<File>, exitCode: ExitCode) {
}
override fun flush() {
}
}
internal class CompileIterationICReporterAsync(
private val compilationResults: CompilationResultsAsync?
) : ICReporterBase(), RemoteICReporter {
override fun reportCompileIteration(incremental: Boolean, sourceFiles: Collection<File>, exitCode: ExitCode) {
GlobalScope.async {
compilationResults?.add(
CompilationResultCategory.IC_COMPILE_ITERATION.code,
CompileIterationResult(sourceFiles, exitCode.toString())
)
}
}
override fun report(message: () -> String) {
}
override fun reportVerbose(message: () -> String) {
}
override fun flush() {
}
}
internal class BuildReportICReporterAsync(
private val compilationResults: CompilationResultsAsync?,
rootDir: File,
private val isVerbose: Boolean = false
) : ICReporterBase(rootDir), RemoteICReporter {
private val icLogLines = arrayListOf<String>()
private val recompilationReason = HashMap<File, String>()
override fun report(message: () -> String) {
icLogLines.add(message())
}
override fun reportVerbose(message: () -> String) {
if (isVerbose) {
report(message)
}
}
override fun reportCompileIteration(incremental: Boolean, sourceFiles: Collection<File>, exitCode: ExitCode) {
if (!incremental) return
icLogLines.add("Compile iteration:")
for (file in sourceFiles) {
val reason = recompilationReason[file]?.let { " <- $it" } ?: ""
icLogLines.add(" ${file.relativeOrCanonical()}$reason")
}
recompilationReason.clear()
}
override fun reportMarkDirty(affectedFiles: Iterable<File>, reason: String) {
affectedFiles.forEach { recompilationReason[it] = reason }
}
override fun flush() {
GlobalScope.async {
compilationResults?.add(CompilationResultCategory.BUILD_REPORT_LINES.code, icLogLines)
}
}
}
internal class CompositeICReporterAsync(private val reporters: Iterable<RemoteICReporter>) :
RemoteICReporter {
override fun report(message: () -> String) {
reporters.forEach { it.report(message) }
}
override fun reportVerbose(message: () -> String) {
reporters.forEach { it.reportVerbose(message) }
}
override fun reportCompileIteration(incremental: Boolean, sourceFiles: Collection<File>, exitCode: ExitCode) {
reporters.forEach { it.reportCompileIteration(incremental, sourceFiles, exitCode) }
}
override fun reportMarkDirtyClass(affectedFiles: Iterable<File>, classFqName: String) {
reporters.forEach { it.reportMarkDirtyClass(affectedFiles, classFqName) }
}
override fun reportMarkDirtyMember(affectedFiles: Iterable<File>, scope: String, name: String) {
reporters.forEach { it.reportMarkDirtyMember(affectedFiles, scope, name) }
}
override fun reportMarkDirty(affectedFiles: Iterable<File>, reason: String) {
reporters.forEach { it.reportMarkDirty(affectedFiles, reason) }
}
override fun flush() {
reporters.forEach { it.flush() }
}
}
fun getICReporterAsync(
servicesFacade: CompilerServicesFacadeBaseAsync,
compilationResults: CompilationResultsAsync?,
compilationOptions: IncrementalCompilationOptions
): RemoteICReporter {
val root = compilationOptions.modulesInfo.projectRoot
val reporters = ArrayList<RemoteICReporter>()
if (ReportCategory.IC_MESSAGE.code in compilationOptions.reportCategories) {
val isVerbose = compilationOptions.reportSeverity == ReportSeverity.DEBUG.code
reporters.add(DebugMessagesICReporterAsync(servicesFacade, root, isVerbose = isVerbose))
}
val requestedResults = compilationOptions
.requestedCompilationResults
.mapNotNullTo(HashSet()) { resultCode ->
CompilationResultCategory.values().getOrNull(resultCode)
}
requestedResults.mapTo(reporters) { requestedResult ->
when (requestedResult) {
CompilationResultCategory.IC_COMPILE_ITERATION -> {
CompileIterationICReporterAsync(compilationResults)
}
CompilationResultCategory.BUILD_REPORT_LINES -> {
BuildReportICReporterAsync(compilationResults, root)
}
CompilationResultCategory.VERBOSE_BUILD_REPORT_LINES -> {
BuildReportICReporterAsync(compilationResults, root, isVerbose = true)
}
}
}
return CompositeICReporterAsync(reporters)
}
@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.daemon.common.*
import java.io.File
import java.util.*
internal fun getICReporter(
fun getICReporter(
servicesFacade: CompilerServicesFacadeBase,
compilationResults: CompilationResults,
compilationOptions: IncrementalCompilationOptions
+1
View File
@@ -27,6 +27,7 @@ dependencies {
testCompile(project(":compiler:serialization"))
testCompile(project(":kotlin-preloader"))
testCompile(project(":compiler:daemon-common"))
testCompile(project(":compiler:daemon-common-new"))
testCompile(project(":js:js.serializer"))
testCompile(project(":js:js.frontend"))
testCompile(project(":js:js.translator"))
@@ -0,0 +1,326 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.experimental.integration
import com.intellij.openapi.application.ApplicationManager
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.daemon.client.KotlinCompilerDaemonClient
import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.common.ReportSeverity
import org.jetbrains.kotlin.daemon.loggerCompatiblePath
import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase
import org.jetbrains.kotlin.scripts.captureOut
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import org.junit.Assert
import java.io.File
import java.net.URLClassLoader
import java.util.logging.LogManager
import java.util.logging.Logger
private val logFiles = arrayListOf<String>()
// TODO: remove ignore annotation from tests.
class CompilerApiTest : KotlinIntegrationTestBase() {
val kotlinCompilerClient = KotlinCompilerDaemonClient
.instantiate(DaemonProtocolVariant.RMI) // TODO(SOCKETS)
private val compilerLibDir = getCompilerLib()
private fun createNewLogFile(): File {
println("creating logFile")
val newLogFile = createTempFile("kotlin-daemon-experimental-test.", ".log")
println("logFile created (${newLogFile.loggerCompatiblePath})")
logFiles.add(newLogFile.loggerCompatiblePath)
return newLogFile
}
private val currentLogFile: File by lazy {
val newLogFile = createNewLogFile()
val cfg: String =
"handlers = java.util.logging.FileHandler\n" +
"java.util.logging.FileHandler.level = ALL\n" +
"java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter\n" +
"java.util.logging.FileHandler.encoding = UTF-8\n" +
"java.util.logging.FileHandler.limit = 0\n" + // if file is provided - disabled, else - 1Mb
"java.util.logging.FileHandler.count = 1\n" +
"java.util.logging.FileHandler.append = true\n" +
"java.util.logging.FileHandler.pattern = ${newLogFile.loggerCompatiblePath}\n" +
"java.util.logging.SimpleFormatter.format = %1\$tF %1\$tT.%1\$tL [%3\$s] %4\$s: %5\$s%n\n"
LogManager.getLogManager().readConfiguration(cfg.byteInputStream())
newLogFile
}
private val externalLogFile: File by lazy { createNewLogFile() }
private val log by lazy {
currentLogFile
Logger.getLogger("test")
}
val compilerClassPath = listOf(
File(compilerLibDir, "kotlin-compiler.jar")
)
val scriptRuntimeClassPath = listOf(
File(compilerLibDir, "kotlin-runtime.jar"),
File(compilerLibDir, "kotlin-script-runtime.jar")
)
val compilerId by lazy(LazyThreadSafetyMode.NONE) { CompilerId.makeCompilerId(compilerClassPath) }
private fun compileLocally(
messageCollector: TestMessageCollector,
vararg args: String
): Pair<Int, Collection<OutputMessageUtil.Output>> {
val application = ApplicationManager.getApplication()
try {
val code = K2JVMCompiler().exec(messageCollector,
Services.EMPTY,
K2JVMCompilerArguments().apply { K2JVMCompiler().parseArguments(args, this) }).code
val outputs = messageCollector.messages.filter { it.severity == CompilerMessageSeverity.OUTPUT }.mapNotNull {
OutputMessageUtil.parseOutputMessage(it.message)?.let { outs ->
outs.outputFile?.let { OutputMessageUtil.Output(outs.sourceFiles, it) }
}
}
return code to outputs
} finally {
KtUsefulTestCase.resetApplicationToNull(application)
}
}
private fun compileOnDaemon(
clientAliveFile: File,
compilerId: CompilerId,
daemonJVMOptions: DaemonJVMOptions,
daemonOptions: DaemonOptions,
messageCollector: MessageCollector,
vararg args: String
): Pair<Int, Collection<OutputMessageUtil.Output>> = runBlocking {
log.info("kotlinCompilerClient.connectToCompileService() call")
val daemon = kotlinCompilerClient.connectToCompileService(
compilerId,
clientAliveFile,
daemonJVMOptions,
daemonOptions,
DaemonReportingTargets(messageCollector = messageCollector),
autostart = true
)
log.info("kotlinCompilerClient.connectToCompileService() called! (daemon = $daemon)")
assertNotNull("failed to connect daemon", daemon)
log.info("runBlocking { ")
log.info("register client...")
daemon?.registerClient(clientAliveFile.absolutePath)
log.info(" client registered")
log.info("} ^ runBlocking")
val outputs = arrayListOf<OutputMessageUtil.Output>()
val code = kotlinCompilerClient.compile(
daemon!!,
CompileService.NO_SESSION,
CompileService.TargetPlatform.JVM,
args,
messageCollector,
{ outFile, srcFiles -> outputs.add(OutputMessageUtil.Output(srcFiles, outFile)) },
reportSeverity = ReportSeverity.DEBUG
)
code to outputs
}
private fun getHelloAppBaseDir(): String = KotlinTestUtils.getTestDataPathBase() + "/integration/smoke/helloApp"
private fun getSimpleScriptBaseDir(): String = KotlinTestUtils.getTestDataPathBase() + "/integration/smoke/simpleScript"
private fun run(baseDir: String, logName: String, vararg args: String): Int = runJava(baseDir, logName, *args)
private fun runScriptWithArgs(
testDataDir: String,
logName: String?,
scriptClassName: String,
classpath: List<File>,
vararg arguments: String
) {
val cl = URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray())
val scriptClass = cl.loadClass(scriptClassName)
val scriptOut = captureOut { scriptClass.constructors.first().newInstance(arguments) }
if (logName != null) {
val expectedFile = File(testDataDir, logName + ".expected")
val normalizedContent = normalizeOutput(File(testDataDir), "OUT:\n$scriptOut\nReturn code: 0")
KotlinTestUtils.assertEqualsToFile(expectedFile, normalizedContent)
}
}
fun ignore_testHelloAppLocal() {
val messageCollector = TestMessageCollector()
val jar = tmpdir.absolutePath + File.separator + "hello.jar"
val (code, outputs) = compileLocally(
messageCollector, "-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath,
"-d", jar, "-Xreport-output-files"
)
Assert.assertEquals(0, code)
Assert.assertTrue(outputs.isNotEmpty())
Assert.assertEquals(jar, outputs.first().outputFile?.absolutePath)
run(getHelloAppBaseDir(), "hello.run", "-cp", jar, "Hello.HelloKt")
}
private fun terminate(daemonOptions: DaemonOptions) {
println("\n\nkillall -9 Console && open ${logFiles.joinToString(" ")}\n\n")
log.info("in finally")
// runBlocking {
// log.info("in runBlocking")
// delay(1000L)
// kotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
// }
// currentLogFile.delete()
// externalLogFile.delete()
}
fun ignore_testHelloApp() {
withFlagFile(getTestName(true), ".alive") { flagFile ->
log.info("sarting test...")
log.info("assigning daemonOptions")
val daemonOptions = DaemonOptions(
runFilesPath = File(tmpdir, getTestName(true)).absolutePath,
verbose = true,
reportPerf = true
)
log.info("daemonOptions assigned")
log.info("creating daemonJVMOptions")
val daemonJVMOptions = configureDaemonJVMOptions(
"D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${externalLogFile.loggerCompatiblePath}\"",
inheritMemoryLimits = false,
inheritOtherJvmOptions = false,
inheritAdditionalProperties = false
)
log.info("daemonJVMOptions created")
log.info("creating jar")
val jar = tmpdir.absolutePath + File.separator + "hello.jar"
log.info("jar created")
try {
log.info("compileOnDaemon call")
val (code, outputs) = compileOnDaemon(
flagFile,
compilerId,
daemonJVMOptions,
daemonOptions,
TestMessageCollector(),
"-include-runtime",
File(getHelloAppBaseDir(), "hello.kt").absolutePath,
"-d",
jar,
"-Xreport-output-files"
)
log.info("compileOnDaemon called")
Assert.assertEquals(0, code)
Assert.assertTrue(outputs.isNotEmpty())
Assert.assertEquals(jar, outputs.first().outputFile?.absolutePath)
run(getHelloAppBaseDir(), "hello.run", "-cp", jar, "Hello.HelloKt")
} finally {
terminate(daemonOptions)
}
println("test passed")
}
}
fun ignore_testSimpleScriptLocal() {
val messageCollector = TestMessageCollector()
val (code, outputs) = compileLocally(
messageCollector,
File(getSimpleScriptBaseDir(), "script.kts").absolutePath,
"-d",
tmpdir.absolutePath,
"-Xreport-output-files"
)
Assert.assertEquals(0, code)
Assert.assertTrue(outputs.isNotEmpty())
Assert.assertEquals(File(tmpdir, "Script.class").absolutePath, outputs.first().outputFile?.absolutePath)
runScriptWithArgs(getSimpleScriptBaseDir(), "script", "Script", scriptRuntimeClassPath + tmpdir, "hi", "there")
}
fun ignore_testSimpleScript() {
withFlagFile(getTestName(true), ".alive") { flagFile ->
val daemonOptions = DaemonOptions(
runFilesPath = File(tmpdir, getTestName(true)).absolutePath,
verbose = true,
reportPerf = true
)
val daemonJVMOptions = configureDaemonJVMOptions(
"D$COMPILE_DAEMON_LOG_PATH_PROPERTY=\"${externalLogFile.loggerCompatiblePath}\"",
inheritMemoryLimits = false, inheritOtherJvmOptions = false, inheritAdditionalProperties = false
)
try {
val (code, outputs) = compileOnDaemon(
flagFile,
compilerId,
daemonJVMOptions,
daemonOptions,
TestMessageCollector(),
File(getSimpleScriptBaseDir(), "script.kts").absolutePath,
"-Xreport-output-files",
"-d",
tmpdir.absolutePath
)
Assert.assertEquals(0, code)
Assert.assertTrue(outputs.isNotEmpty())
Assert.assertEquals(File(tmpdir, "Script.class").absolutePath, outputs.first().outputFile?.absolutePath)
runScriptWithArgs(getSimpleScriptBaseDir(), "script", "Script", scriptRuntimeClassPath + tmpdir, "hi", "there")
} finally {
terminate(daemonOptions)
}
}
}
}
class TestMessageCollector : MessageCollector {
data class Message(val severity: CompilerMessageSeverity, val message: String, val location: CompilerMessageLocation?)
val messages = arrayListOf<Message>()
override fun clear() {
messages.clear()
}
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
messages.add(Message(severity, message, location))
}
override fun hasErrors(): Boolean =
messages.any { it.severity == CompilerMessageSeverity.EXCEPTION || it.severity == CompilerMessageSeverity.ERROR }
override fun toString(): String {
return messages.joinToString("\n") { "${it.severity}: ${it.message}${it.location?.let { " at $it" } ?: ""}" }
}
}
fun TestMessageCollector.assertHasMessage(msg: String, desiredSeverity: CompilerMessageSeverity? = null) {
assert(messages.any { it.message.contains(msg) && (desiredSeverity == null || it.severity == desiredSeverity) }) {
"Expecting message \"$msg\" with severity ${desiredSeverity?.toString() ?: "Any"}, actual:\n" +
messages.joinToString("\n") { it.severity.toString() + ": " + it.message }
}
}
@@ -0,0 +1,91 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.experimental.unit
import io.ktor.network.selector.ActorSelectorManager
import io.ktor.network.selector.SelectorManager
import io.ktor.network.sockets.aSocket
import kotlinx.coroutines.*
import org.jetbrains.kotlin.daemon.common.experimental.CompilerServicesFacadeBaseClientSideImpl
import org.jetbrains.kotlin.daemon.common.experimental.LoopbackNetworkInterface.selectorMgr
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.DefaultClient
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.ServerBase
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.openIO
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.runWithTimeout
import org.jetbrains.kotlin.daemon.common.toRMI
import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.net.InetSocketAddress
import java.util.logging.Logger
class TestServer(val serverPort: Int = 6999) {
private val serverSocket = aSocket(selectorMgr).tcp().bind(InetSocketAddress(serverPort))
private val log = Logger.getLogger("TestServer")
fun awaitClient() = GlobalScope.async {
log.info("accepting clientSocket...")
val client = serverSocket.accept()
log.info("client accepted! (${client.remoteAddress})")
val (input, output) = client.openIO(log)
true
}
}
val testServer = TestServer()
@Suppress("UNCHECKED_CAST")
class ClientSerializationTest : KotlinIntegrationTestBase() {
val file = createTempFile()
val log = Logger.getLogger("ClientSerializationTest")
private inline fun <reified T> abstractSerializationTest(initClient: () -> T, vararg additionalTests: (T, T) -> Unit) {
val client = initClient()
log.info("created")
file.outputStream().use {
ObjectOutputStream(it).use {
it.writeObject(client)
}
}
log.info("printed")
var client2: T? = null
var connected = false
runBlocking {
val clientAwait = testServer.awaitClient()
client2 = file.inputStream().use {
ObjectInputStream(it).use {
it.readObject() as T
}
}
connected = runWithTimeout { clientAwait.await() } ?: false
}
assert(connected)
log.info("read")
assert(client2 != null)
additionalTests.forEach { it(client, client2!!) }
log.info("test passed")
}
fun testDefaultClient() = abstractSerializationTest(
{ DefaultClient<ServerBase>(testServer.serverPort) },
{ client, client2 -> assert(client.serverPort == client2.serverPort) },
{ client, client2 ->
client2.log.info("abacaba (2)")
log.info("test passed")
}
)
fun testCompilerServicesFacadeBaseClientSide() = abstractSerializationTest(
{ CompilerServicesFacadeBaseClientSideImpl(testServer.serverPort) },
{ client, client2 -> assert(client.serverPort == client2.serverPort) }
)
fun testRMIWrapper() = abstractSerializationTest({ CompilerServicesFacadeBaseClientSideImpl(testServer.serverPort).toRMI() })
}
@@ -0,0 +1,380 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.experimental.unit
import kotlinx.coroutines.*
import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.js.K2JSCompiler
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler
import org.jetbrains.kotlin.daemon.CompileServiceImpl
import org.jetbrains.kotlin.daemon.CompilerSelector
import org.jetbrains.kotlin.daemon.client.KotlinCompilerDaemonClient
import org.jetbrains.kotlin.daemon.client.experimental.BasicCompilerServicesWithResultsFacadeServerServerSide
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.common.experimental.*
import org.jetbrains.kotlin.daemon.experimental.CompileServiceServerSideImpl
import org.jetbrains.kotlin.daemon.loggerCompatiblePath
import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import java.io.PrintStream
import java.util.*
import java.util.logging.LogManager
import java.util.logging.Logger
import kotlin.concurrent.schedule
class ConnectionsTest : KotlinIntegrationTestBase() {
val kotlinCompilerClient = KotlinCompilerDaemonClient.instantiate(DaemonProtocolVariant.SOCKETS)
private val logFile = createTempFile("/Users/jetbrains/Documents/kotlin/my_fork/kotlin", ".txt")
private val cfg = "handlers = java.util.logging.FileHandler\n" +
"java.util.logging.FileHandler.level = ALL\n" +
"java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter\n" +
"java.util.logging.FileHandler.encoding = UTF-8\n" +
"java.util.logging.FileHandler.limit = 0\n" + // if file is provided - disabled, else - 1Mb
"java.util.logging.FileHandler.count = 1\n" +
"java.util.logging.FileHandler.append = true\n" +
"java.util.logging.FileHandler.pattern = ${logFile.loggerCompatiblePath}\n" +
"java.util.logging.SimpleFormatter.format = %1\$tF %1\$tT.%1\$tL [%3\$s] %4\$s: %5\$s%n\n"
init {
LogManager.getLogManager().readConfiguration(cfg.byteInputStream())
}
val log by lazy { Logger.getLogger("ConnectionsTest") }
private val daemonJVMOptions by lazy {
configureDaemonJVMOptions(
inheritMemoryLimits = true,
inheritOtherJvmOptions = true,
inheritAdditionalProperties = true
)
}
private val compilerId by lazy { CompilerId() }
private val daemonOptions by lazy { DaemonOptions() }
private val port
get() = findPortForSocket(
COMPILE_DAEMON_FIND_PORT_ATTEMPTS,
COMPILE_DAEMON_PORTS_RANGE_START,
COMPILE_DAEMON_PORTS_RANGE_END
)
private val timer by lazy { Timer(true) }
private val runFile by lazy {
val runFileDir = File(daemonOptions.runFilesPathOrDefault)
runFileDir.mkdirs()
File(
runFileDir,
makeRunFilenameString(
timestamp = "%tFT%<tH-%<tM-%<tS.%<tLZ".format(Calendar.getInstance(TimeZone.getTimeZone("Z"))),
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest().toHexString(),
port = port.toString()
)
)
}
private val onShutdown: () -> Unit = {
if (daemonOptions.forceShutdownTimeoutMilliseconds != COMPILE_DAEMON_TIMEOUT_INFINITE_MS) {
// running a watcher thread that ensures that if the daemon is not exited normally (may be due to RMI leftovers), it's forced to exit
timer.schedule(daemonOptions.forceShutdownTimeoutMilliseconds) {
cancel()
org.jetbrains.kotlin.daemon.KotlinCompileDaemon.log.info("force JVM shutdown")
System.exit(0)
}
} else {
timer.cancel()
}
}
private fun getNewDaemonsOrAsyncWrappers() = runBlocking {
walkDaemonsAsync(
File(daemonOptions.runFilesPathOrDefault),
compilerId,
runFile,
filter = { _, _ -> true },
report = { _, msg -> log.info(msg) },
useRMI = true,
useSockets = true
).toList()
}
private fun getOldDaemonsOrRMIWrappers() = runBlocking {
walkDaemons(
File(daemonOptions.runFilesPathOrDefault),
compilerId,
runFile,
filter = { _, _ -> true },
report = { _, msg -> log.info(msg) }
).toList()
}
companion object {
}
private fun runNewServer(): Deferred<Unit> =
port.let { serverPort ->
CompileServiceServerSideImpl(
serverPort,
compilerId,
daemonOptions,
daemonJVMOptions,
serverPort.port,
timer,
onShutdown
).let {
log.info("service created")
it.startDaemonLife()
it.runServer()
}
}
private fun runOldServer() {
val (registry, serverPort) = findPortAndCreateRegistry(
COMPILE_DAEMON_FIND_PORT_ATTEMPTS,
COMPILE_DAEMON_PORTS_RANGE_START,
COMPILE_DAEMON_PORTS_RANGE_END
)
val compilerSelector = object : CompilerSelector {
private val jvm by lazy { K2JVMCompiler() }
private val js by lazy { K2JSCompiler() }
private val metadata by lazy { K2MetadataCompiler() }
override fun get(targetPlatform: CompileService.TargetPlatform): CLICompiler<*> = when (targetPlatform) {
CompileService.TargetPlatform.JVM -> jvm
CompileService.TargetPlatform.JS -> js
CompileService.TargetPlatform.METADATA -> metadata
}
}
log.info("old server run: (port= $serverPort, reg= $registry)")
CompileServiceImpl(
registry = registry,
compiler = compilerSelector,
compilerId = compilerId,
daemonOptions = daemonOptions,
daemonJVMOptions = daemonJVMOptions,
port = serverPort,
timer = timer,
onShutdown = onShutdown
).startDaemonLife()
}
val comparator = compareByDescending<DaemonWithMetadataAsync, DaemonJVMOptions>(
DaemonJVMOptionsMemoryComparator(),
{ it.jvmOptions }
)
.thenBy {
when (it.daemon) {
is CompileServiceAsyncWrapper -> 0
else -> 1
}
}
.thenBy(FileAgeComparator()) { it.runFile }
.thenBy { it.daemon.serverPort }
private fun <DaemonWithMeta, Daemon> expectDaemon(
getDaemons: () -> List<DaemonWithMeta>,
chooseDaemon: (List<DaemonWithMeta>) -> Daemon,
getInfo: (Daemon) -> CompileService.CallResult<String>,
registerClient: (Daemon) -> Unit,
port: (Daemon) -> Int,
expectedDaemonCount: Int?,
extraAction: (Daemon) -> Unit = {}
) {
val daemons = getDaemons()
log.info("daemons (${daemons.size}) : ${daemons.map { (it ?: 0)::class.java.name }.toList()}\n\n")
expectedDaemonCount?.let {
log.info("expected $it daemons, found ${daemons.size}")
assertTrue(
"daemons.size : ${daemons.size}, but expected : $expectedDaemonCount",
daemons.size == it
)
}
val daemon = chooseDaemon(daemons)
log.info("chosen : $daemon (port = ${port(daemon)})")
val info = getInfo(daemon)
log.info("info : $info")
assertTrue("bad info", info.isGood)
registerClient(daemon)
extraAction(daemon)
}
private enum class ServerType(val instancesNumber: Int?) {
OLD(1), NEW(2), ANY(null)
}
private fun expectNewDaemon(serverType: ServerType, extraAction: (CompileServiceAsync) -> Unit = {}) = expectDaemon(
getDaemons = ::getNewDaemonsOrAsyncWrappers,
chooseDaemon = { daemons -> daemons.maxWith(comparator)!!.daemon },
getInfo = { d -> runBlocking { d.getDaemonInfo() } },
registerClient = { d -> runBlocking { d.registerClient(generateClient()) } },
port = { d -> d.serverPort },
expectedDaemonCount = serverType.instancesNumber,
extraAction = extraAction
)
private fun expectOldDaemon(shouldCheckNumber: Boolean = true, extraAction: (CompileService) -> Unit = {}) = expectDaemon(
::getOldDaemonsOrRMIWrappers,
{ daemons -> daemons[0].daemon },
{ d -> d.getDaemonInfo() },
{ d -> d.registerClient(generateClient()) },
{ d -> -1 },
1.takeIf { shouldCheckNumber },
extraAction
)
private val clientFiles = arrayListOf<File>()
private fun generateClient(): String {
val file = createTempFile(getTestName(true), ".alive")
clientFiles.add(file)
return file.absolutePath
}
private fun deleteClients() {
clientFiles.forEach { it.delete() }
}
private fun endTest() {
deleteClients()
}
fun testConnectionMechanism_OldClient_OldServer() {
runOldServer()
expectOldDaemon()
endTest()
}
fun testConnectionMechanism_NewClient_NewServer() {
runNewServer()
expectNewDaemon(ServerType.NEW)
endTest()
}
fun testConnectionMechanism_OldClient_NewServer() {
runNewServer()
expectOldDaemon()
endTest()
}
fun testConnectionMechanism_NewClient_OldServer() {
runOldServer()
expectNewDaemon(ServerType.OLD)
endTest()
}
fun testConnections_OldDaemon_DifferentClients() {
runOldServer()
(0..20).forEach {
expectNewDaemon(ServerType.OLD)
expectOldDaemon()
}
endTest()
}
fun testConnections_NewDaemon_DifferentClients() {
runNewServer()
(0..4).forEach {
expectNewDaemon(ServerType.NEW)
expectOldDaemon()
}
endTest()
}
fun testConnections_MultipleDaemons_MultipleClients() {
(0..3).forEach {
runNewServer()
runOldServer()
}
(0..4).forEach {
expectNewDaemon(ServerType.ANY)
expectOldDaemon(shouldCheckNumber = false)
}
endTest()
}
fun testShutdown() {
runNewServer()
expectNewDaemon(ServerType.NEW) { daemon ->
runBlocking {
daemon.shutdown()
delay(1000L)
val mem: Long = try {
daemon.getUsedMemory().get()
} catch (e: IOException) {
-100500L
}
assertTrue(mem == -100500L)
}
}
}
fun testCompile() {
runNewServer()
expectNewDaemon(ServerType.NEW) { daemon ->
runBlocking {
assertTrue("daemon is wrapper", daemon !is CompileServiceAsyncWrapper)
val outStream = ByteArrayOutputStream()
val msgCollector = PrintingMessageCollector(PrintStream(outStream), MessageRenderer.WITHOUT_PATHS, true)
val codes = (0 until 10).toMutableList()
val services = BasicCompilerServicesWithResultsFacadeServerServerSide(
msgCollector,
{ _, _ -> },
findCallbackServerSocket()
)
services.runServer()
val servicesClient = services.clientSide
val compResultsClient = kotlinCompilerClient.createCompResults().clientSide
val threadCount = 10
fun runThread(i: Int) =
async(newSingleThreadContext("thread_$i")) {
val jar = tmpdir.absolutePath + File.separator + "hello.$i.jar"
val code =
daemon.compile(
CompileService.NO_SESSION,
arrayOf(
"-include-runtime",
File(KotlinTestUtils.getTestDataPathBase() + "/integration/smoke/helloApp", "hello.kt").absolutePath,
"-d",
jar
),
CompilationOptions(
CompilerMode.NON_INCREMENTAL_COMPILER,
CompileService.TargetPlatform.JVM,
arrayOf(
ReportCategory.COMPILER_MESSAGE.code,
ReportCategory.DAEMON_MESSAGE.code,
ReportCategory.EXCEPTION.code,
ReportCategory.OUTPUT_MESSAGE.code
),
ReportSeverity.INFO.code,
emptyArray()
),
servicesClient,
compResultsClient
).get()
codes[i] = code
}
(0 until threadCount).map(::runThread).map { it.await() }
assertTrue("not-null code", codes.all { it == 0 })
}
}
}
}
@@ -0,0 +1,10 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.progress.experimental
interface CompilationCanceledStatus {
suspend fun checkCanceled()
}
+1
View File
@@ -27,6 +27,7 @@ dependencies {
compile(project(":compiler:util"))
compile(project(":kotlin-build-common"))
compile(project(":compiler:daemon-common"))
compile(project(":compiler:daemon-common-new"))
compile(projectRuntimeJar(":kotlin-daemon-client"))
compile(project(":kotlin-compiler-runner")) { isTransitive = false }
compile(project(":compiler:plugin-api"))
+1
View File
@@ -11,6 +11,7 @@ dependencies {
compile(project(":core:descriptors.jvm"))
compile(project(":kotlin-compiler-runner"))
compile(project(":compiler:daemon-common"))
compile(project(":compiler:daemon-common-new"))
compile(projectRuntimeJar(":kotlin-daemon-client"))
compile(project(":compiler:frontend.java"))
compile(project(":js:js.frontend"))
+2 -2
View File
@@ -7,7 +7,7 @@
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
as the successor of the GNU Library Public License, Version 2, hence
the version number 2.1.]
Preamble
@@ -227,7 +227,7 @@ the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
that they refer to the ordinary GNU General Public License, Version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in

Some files were not shown because too many files have changed in this diff Show More