Introduce new Kotlin Daemon without RMI abstraction
This commit is contained in:
@@ -34443,7 +34443,7 @@ public final class DebugProtoBuf {
|
|||||||
"tbrains.kotlin.metadata.VersionRequireme" +
|
"tbrains.kotlin.metadata.VersionRequireme" +
|
||||||
"nt.Level:\005ERROR\022\022\n\nerror_code\030\004 \001(\005\022\025\n\007m" +
|
"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" +
|
"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" +
|
"Requirement.VersionKind:\020LANGUAGE_VERSIO" +
|
||||||
"N\"+\n\005Level\022\013\n\007WARNING\020\000\022\t\n\005ERROR\020\001\022\n\n\006HI" +
|
"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" +
|
"DDEN\020\002\"J\n\013VersionKind\022\024\n\020LANGUAGE_VERSIO" +
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ buildscript {
|
|||||||
repositories.withRedirector(project) {
|
repositories.withRedirector(project) {
|
||||||
bootstrapKotlinRepo?.let(::maven)
|
bootstrapKotlinRepo?.let(::maven)
|
||||||
maven("https://plugins.gradle.org/m2")
|
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
|
// 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 ideaPluginDir = "$artifactsDir/ideaPlugin/Kotlin"
|
||||||
val ideaUltimatePluginDir = "$artifactsDir/ideaUltimatePlugin/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
|
// TODO: use "by extra()" syntax where possible
|
||||||
extra["distLibDir"] = project.file(distLibDir)
|
extra["distLibDir"] = project.file(distLibDir)
|
||||||
extra["libsDir"] = 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.markdown"] = "0.1.25"
|
||||||
extra["versions.trove4j"] = "1.0.20181211"
|
extra["versions.trove4j"] = "1.0.20181211"
|
||||||
extra["versions.kotlin-native-shared"] = "1.0-dev-57"
|
extra["versions.kotlin-native-shared"] = "1.0-dev-57"
|
||||||
|
extra["versions.ktor-network"] = "1.0.1"
|
||||||
|
|
||||||
if (!project.hasProperty("versions.kotlin-native")) {
|
if (!project.hasProperty("versions.kotlin-native")) {
|
||||||
extra["versions.kotlin-native"] = "1.3-dev-9780"
|
extra["versions.kotlin-native"] = "1.3-dev-9780"
|
||||||
@@ -198,6 +211,7 @@ extra["compilerModules"] = arrayOf(
|
|||||||
":compiler:frontend.java",
|
":compiler:frontend.java",
|
||||||
":compiler:cli-common",
|
":compiler:cli-common",
|
||||||
":compiler:daemon-common",
|
":compiler:daemon-common",
|
||||||
|
":compiler:daemon-common-new",
|
||||||
":compiler:daemon",
|
":compiler:daemon",
|
||||||
":compiler:ir.tree",
|
":compiler:ir.tree",
|
||||||
":compiler:ir.psi2ir",
|
":compiler:ir.psi2ir",
|
||||||
@@ -319,6 +333,7 @@ allprojects {
|
|||||||
maven(intellijRepo)
|
maven(intellijRepo)
|
||||||
maven(bootstrapKotlinRepo!!.replace("artifacts/content/maven/", "artifacts/content/internal/repo"))
|
maven(bootstrapKotlinRepo!!.replace("artifacts/content/maven/", "artifacts/content/internal/repo"))
|
||||||
maven(kotlinNativeRepo)
|
maven(kotlinNativeRepo)
|
||||||
|
maven("https://dl.bintray.com/kotlin/ktor")
|
||||||
}
|
}
|
||||||
|
|
||||||
configureJvmProject(javaHome!!, jvmTarget!!)
|
configureJvmProject(javaHome!!, jvmTarget!!)
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ fun configureFreeCompilerArg(isEnabled: Boolean, compilerArgument: String) {
|
|||||||
|
|
||||||
val antLauncherJar by configurations.creating
|
val antLauncherJar by configurations.creating
|
||||||
|
|
||||||
|
val ktorExcludesForDaemon : List<Pair<String, String>> by rootProject.extra
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testRuntime(intellijDep()) // Should come before compiler, because of "progarded" stuff needed for tests
|
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(":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-scripting-compiler-impl"))
|
||||||
testCompile(project(":kotlin-script-util"))
|
testCompile(project(":kotlin-script-util"))
|
||||||
testCompileOnly(projectRuntimeJar(":kotlin-daemon-client"))
|
testCompileOnly(projectRuntimeJar(":kotlin-daemon-client-new"))
|
||||||
testCompileOnly(project(":kotlin-reflect-api"))
|
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 {
|
otherCompilerModules.forEach {
|
||||||
testCompileOnly(project(it))
|
testCompileOnly(project(it))
|
||||||
}
|
}
|
||||||
@@ -59,7 +67,18 @@ dependencies {
|
|||||||
testCompileOnly(intellijDep()) { includeJars("openapi", "idea", "idea_rt", "util", "asm-all", rootProject = rootProject) }
|
testCompileOnly(intellijDep()) { includeJars("openapi", "idea", "idea_rt", "util", "asm-all", rootProject = rootProject) }
|
||||||
|
|
||||||
testRuntime(project(":kotlin-reflect"))
|
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(androidDxJar())
|
||||||
testRuntime(files(toolsJar()))
|
testRuntime(files(toolsJar()))
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ dependencies {
|
|||||||
compileOnly(project(":kotlin-preloader"))
|
compileOnly(project(":kotlin-preloader"))
|
||||||
compileOnly(project(":compiler:frontend.java"))
|
compileOnly(project(":compiler:frontend.java"))
|
||||||
compileOnly(project(":compiler:daemon-common"))
|
compileOnly(project(":compiler:daemon-common"))
|
||||||
|
compileOnly(project(":compiler:daemon-common-new"))
|
||||||
compile(projectRuntimeJar(":kotlin-daemon-client"))
|
compile(projectRuntimeJar(":kotlin-daemon-client"))
|
||||||
compileOnly(project(":compiler:util"))
|
compileOnly(project(":compiler:util"))
|
||||||
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
||||||
runtimeOnly(projectRuntimeJar(":kotlin-compiler-embeddable"))
|
runtimeOnly(projectRuntimeJar(":kotlin-compiler-embeddable"))
|
||||||
|
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) { isTransitive = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ messages/**)
|
|||||||
-dontwarn com.intellij.util.io.Decompressor*
|
-dontwarn com.intellij.util.io.Decompressor*
|
||||||
-dontwarn org.w3c.dom.Location
|
-dontwarn org.w3c.dom.Location
|
||||||
-dontwarn org.w3c.dom.Window
|
-dontwarn org.w3c.dom.Window
|
||||||
|
-dontwarn org.slf4j.**
|
||||||
|
|
||||||
|
|
||||||
#-libraryjars '<rtjar>'
|
#-libraryjars '<rtjar>'
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ messages/**)
|
|||||||
-dontwarn org.jetbrains.annotations.Mutable
|
-dontwarn org.jetbrains.annotations.Mutable
|
||||||
-dontwarn com.intellij.util.io.TarUtil
|
-dontwarn com.intellij.util.io.TarUtil
|
||||||
-dontwarn com.intellij.util.io.Compressor$Tar
|
-dontwarn com.intellij.util.io.Compressor$Tar
|
||||||
|
-dontwarn org.slf4j.**
|
||||||
|
|
||||||
# Annotations from intellijCore/annotations.jar that not presented in org.jetbrains.annotations
|
# Annotations from intellijCore/annotations.jar that not presented in org.jetbrains.annotations
|
||||||
-dontwarn org.jetbrains.annotations.Async*
|
-dontwarn org.jetbrains.annotations.Async*
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ messages/**)
|
|||||||
-dontwarn org.jetbrains.annotations.Mutable
|
-dontwarn org.jetbrains.annotations.Mutable
|
||||||
-dontwarn com.intellij.util.io.TarUtil
|
-dontwarn com.intellij.util.io.TarUtil
|
||||||
-dontwarn com.intellij.util.io.Compressor$Tar
|
-dontwarn com.intellij.util.io.Compressor$Tar
|
||||||
|
-dontwarn org.slf4j.**
|
||||||
|
|
||||||
# Annotations from intellijCore/annotations.jar that not presented in org.jetbrains.annotations
|
# Annotations from intellijCore/annotations.jar that not presented in org.jetbrains.annotations
|
||||||
-dontwarn org.jetbrains.annotations.Async*
|
-dontwarn org.jetbrains.annotations.Async*
|
||||||
|
|||||||
@@ -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 {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
id("jps-compatible")
|
id("jps-compatible")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ktorExcludesForDaemon : List<Pair<String, String>> by rootProject.extra
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile(project(":compiler:cli"))
|
compile(project(":compiler:cli"))
|
||||||
compile(project(":compiler:cli-js"))
|
compile(project(":compiler:cli-js"))
|
||||||
compile(project(":compiler:daemon-common"))
|
compile(project(":compiler:daemon-common"))
|
||||||
|
compile(project(":compiler:daemon-common-new"))
|
||||||
compile(project(":compiler:incremental-compilation-impl"))
|
compile(project(":compiler:incremental-compilation-impl"))
|
||||||
compile(project(":kotlin-build-common"))
|
compile(project(":kotlin-build-common"))
|
||||||
compile(commonDep("org.fusesource.jansi", "jansi"))
|
compile(commonDep("org.fusesource.jansi", "jansi"))
|
||||||
compile(commonDep("org.jline", "jline"))
|
compile(commonDep("org.jline", "jline"))
|
||||||
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
||||||
compileOnly(intellijDep()) { includeIntellijCoreJarDependencies(project) }
|
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 {
|
sourceSets {
|
||||||
"main" { projectDefault() }
|
"main" { projectDefault() }
|
||||||
"test" {}
|
"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()
|
||||||
+32
@@ -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)
|
||||||
|
|
||||||
|
}
|
||||||
+104
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
+612
@@ -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
|
||||||
|
}
|
||||||
+79
@@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+49
@@ -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()
|
||||||
+64
@@ -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 {
|
nativePlatformVariants.forEach {
|
||||||
embedded(commonDep("net.rubygrapefruit", "native-platform", "-$it"))
|
embedded(commonDep("net.rubygrapefruit", "native-platform", "-$it"))
|
||||||
}
|
}
|
||||||
|
runtime(project(":kotlin-reflect"))
|
||||||
|
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) {
|
||||||
|
isTransitive = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
|||||||
+105
@@ -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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+27
-27
@@ -19,40 +19,40 @@ package org.jetbrains.kotlin.daemon.client
|
|||||||
import org.jetbrains.kotlin.daemon.common.DaemonReportCategory
|
import org.jetbrains.kotlin.daemon.common.DaemonReportCategory
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
private class NativePlatformLauncherWrapper {
|
class NativePlatformLauncherWrapper {
|
||||||
private val nativeLauncher: net.rubygrapefruit.platform.ProcessLauncher by lazy {
|
private val nativeLauncher: net.rubygrapefruit.platform.ProcessLauncher by lazy {
|
||||||
net.rubygrapefruit.platform.Native.get(net.rubygrapefruit.platform.ProcessLauncher::class.java)
|
net.rubygrapefruit.platform.Native.get(net.rubygrapefruit.platform.ProcessLauncher::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launch(processBuilder: ProcessBuilder): Process =
|
fun launch(processBuilder: ProcessBuilder): Process =
|
||||||
try {
|
try {
|
||||||
nativeLauncher.start(processBuilder)
|
nativeLauncher.start(processBuilder)
|
||||||
}
|
}
|
||||||
catch (e: net.rubygrapefruit.platform.NativeException) {
|
catch (e: net.rubygrapefruit.platform.NativeException) {
|
||||||
throw IOException(e)
|
throw IOException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun launchProcessWithFallback(processBuilder: ProcessBuilder, reportingTargets: DaemonReportingTargets, reportingSource: String = "process launcher"): Process =
|
fun launchProcessWithFallback(processBuilder: ProcessBuilder, reportingTargets: DaemonReportingTargets, reportingSource: String = "process launcher"): Process =
|
||||||
try {
|
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
|
// 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)
|
NativePlatformLauncherWrapper().launch(processBuilder)
|
||||||
}
|
}
|
||||||
catch (e: UnsatisfiedLinkError) {
|
catch (e: UnsatisfiedLinkError) {
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, "Could not start process with native process launcher, falling back to ProcessBuilder#start ($e)", reportingSource)
|
reportingTargets.report(DaemonReportCategory.DEBUG, "Could not start process with native process launcher, falling back to ProcessBuilder#start ($e)", reportingSource)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
catch (e: IOException) {
|
catch (e: IOException) {
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, "Could not start process with native process launcher, falling back to ProcessBuilder#start (${e.cause})", reportingSource)
|
reportingTargets.report(DaemonReportCategory.DEBUG, "Could not start process with native process launcher, falling back to ProcessBuilder#start (${e.cause})", reportingSource)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
catch (e: NoClassDefFoundError) {
|
catch (e: NoClassDefFoundError) {
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, "net.rubygrapefruit.platform library is not in the classpath, falling back to ProcessBuilder#start ($e)", reportingSource)
|
reportingTargets.report(DaemonReportCategory.DEBUG, "net.rubygrapefruit.platform library is not in the classpath, falling back to ProcessBuilder#start ($e)", reportingSource)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
catch (e: ClassNotFoundException) {
|
catch (e: ClassNotFoundException) {
|
||||||
reportingTargets.report(DaemonReportCategory.DEBUG, "net.rubygrapefruit.platform library is not in the classpath, falling back to ProcessBuilder#start ($e)", reportingSource)
|
reportingTargets.report(DaemonReportCategory.DEBUG, "net.rubygrapefruit.platform library is not in the classpath, falling back to ProcessBuilder#start ($e)", reportingSource)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
?: processBuilder.start()
|
?: processBuilder.start()
|
||||||
|
|||||||
+4
-1
@@ -50,7 +50,10 @@ class RemoteReplCompilerStateHistory(private val state: RemoteReplCompilerState)
|
|||||||
override val lock: ReentrantReadWriteLock get() = state.lock
|
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()
|
override val currentGeneration: Int get() = (history as RemoteReplCompilerStateHistory).currentGeneration.get()
|
||||||
|
|
||||||
|
|||||||
+125
@@ -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" {}
|
||||||
|
}
|
||||||
+32
@@ -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
|
||||||
+153
@@ -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) })
|
||||||
|
|
||||||
+42
@@ -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)
|
||||||
|
}
|
||||||
+15
@@ -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
|
||||||
|
}
|
||||||
+393
@@ -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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+234
@@ -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)
|
||||||
+13
@@ -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
|
||||||
|
}
|
||||||
+104
@@ -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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+104
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+30
@@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+26
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -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)
|
||||||
+26
@@ -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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+26
@@ -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)
|
||||||
+14
@@ -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
|
||||||
+137
@@ -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)
|
||||||
|
}
|
||||||
+29
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
+12
@@ -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>
|
||||||
+43
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -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)
|
||||||
+11
@@ -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>
|
||||||
+40
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
+77
@@ -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)
|
||||||
+21
@@ -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
|
||||||
|
)
|
||||||
+260
@@ -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() {}
|
||||||
|
}
|
||||||
+208
@@ -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
|
||||||
|
}
|
||||||
+217
@@ -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 {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
@@ -13,6 +14,9 @@ dependencies {
|
|||||||
compileOnly(project(":js:js.frontend"))
|
compileOnly(project(":js:js.frontend"))
|
||||||
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
||||||
compileOnly(intellijDep()) { includeIntellijCoreJarDependencies(project) }
|
compileOnly(intellijDep()) { includeIntellijCoreJarDependencies(project) }
|
||||||
|
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) {
|
||||||
|
isTransitive = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
|||||||
+5
@@ -29,4 +29,9 @@ enum class CompilationResultCategory(val code: Int) {
|
|||||||
IC_COMPILE_ITERATION(0),
|
IC_COMPILE_ITERATION(0),
|
||||||
BUILD_REPORT_LINES(1),
|
BUILD_REPORT_LINES(1),
|
||||||
VERBOSE_BUILD_REPORT_LINES(2),
|
VERBOSE_BUILD_REPORT_LINES(2),
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CompilationResultsAsync {
|
||||||
|
suspend fun add(compilationResultCategory: Int, value: Serializable)
|
||||||
|
val clientSide: CompilationResultsAsync
|
||||||
}
|
}
|
||||||
+36
@@ -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)
|
||||||
+79
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
+109
@@ -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)
|
||||||
+205
@@ -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)
|
||||||
|
}
|
||||||
+49
@@ -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?
|
||||||
|
}
|
||||||
+25
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
+22
@@ -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)
|
||||||
+21
@@ -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)
|
||||||
+10
@@ -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
|
||||||
|
}
|
||||||
+9
@@ -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 getCounters(): Map<Any?, PerfCounters>
|
||||||
fun getTotalCounters(): 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 {
|
inline fun<R> beginWithMeasureWallTime(perfCounters: PerfCounters) = listOf(System.nanoTime())
|
||||||
val startTime = System.nanoTime()
|
|
||||||
val res = body()
|
inline fun<R> endWithMeasureWallTime(perfCounters: PerfCounters, startState: List<Long>) {
|
||||||
|
val (startTime) = startState
|
||||||
perfCounters.addMeasurement(time = System.nanoTime() - startTime) // TODO: add support for time wrapping
|
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 startTime = System.nanoTime()
|
||||||
val startThreadTime = threadMXBean.threadCpuTime()
|
val startThreadTime = threadMXBean.threadCpuTime()
|
||||||
val startThreadUserTime = threadMXBean.threadUserTime()
|
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
|
// TODO: add support for time wrapping
|
||||||
perfCounters.addMeasurement(time = System.nanoTime() - startTime,
|
perfCounters.addMeasurement(time = System.nanoTime() - startTime,
|
||||||
thread = threadMXBean.threadCpuTime() - startThreadTime,
|
thread = threadMXBean.threadCpuTime() - startThreadTime,
|
||||||
threadUser = threadMXBean.threadUserTime() - startThreadUserTime)
|
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 beginWithMeasureWallAndThreadTimesAndMemory(perfCounters: PerfCounters, withGC: Boolean = false, threadMXBean: ThreadMXBean): List<Long> {
|
||||||
inline fun<R> withMeasureWallAndThreadTimesAndMemory(perfCounters: PerfCounters, withGC: Boolean = false, threadMXBean: ThreadMXBean, body: () -> R): R {
|
|
||||||
val startMem = usedMemory(withGC)
|
val startMem = usedMemory(withGC)
|
||||||
val startTime = System.nanoTime()
|
val startTime = System.nanoTime()
|
||||||
val startThreadTime = threadMXBean.threadCpuTime()
|
val startThreadTime = threadMXBean.threadCpuTime()
|
||||||
val startThreadUserTime = threadMXBean.threadUserTime()
|
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
|
// TODO: add support for time wrapping
|
||||||
perfCounters.addMeasurement(time = System.nanoTime() - startTime,
|
perfCounters.addMeasurement(time = System.nanoTime() - startTime,
|
||||||
thread = threadMXBean.threadCpuTime() - startThreadTime,
|
thread = threadMXBean.threadCpuTime() - startThreadTime,
|
||||||
threadUser = threadMXBean.threadUserTime() - startThreadUserTime,
|
threadUser = threadMXBean.threadUserTime() - startThreadUserTime,
|
||||||
memory = usedMemory(withGC) - startMem)
|
memory = usedMemory(withGC) - startMem)
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun<R> withMeasureWallAndThreadTimesAndMemory(perfCounters: PerfCounters, withGC: Boolean, body: () -> R): R =
|
inline fun<R> beginWithMeasureWallAndThreadTimesAndMemory(perfCounters: PerfCounters, withGC: Boolean) =
|
||||||
withMeasureWallAndThreadTimesAndMemory(perfCounters, withGC, ManagementFactory.getThreadMXBean(), body)
|
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 {
|
class DummyProfiler : Profiler {
|
||||||
override fun getCounters(): Map<Any?, PerfCounters> = mapOf(null to SimplePerfCounters())
|
override fun getCounters(): Map<Any?, PerfCounters> = mapOf(null to SimplePerfCounters())
|
||||||
override fun getTotalCounters(): PerfCounters = 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() {
|
class WallTotalProfiler : TotalProfiler() {
|
||||||
@Suppress("OVERRIDE_BY_INLINE")
|
@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() {
|
class WallAndThreadTotalProfiler : TotalProfiler() {
|
||||||
@Suppress("OVERRIDE_BY_INLINE")
|
@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() {
|
class WallAndThreadAndMemoryTotalProfiler(val withGC: Boolean) : TotalProfiler() {
|
||||||
@Suppress("OVERRIDE_BY_INLINE")
|
@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
|
override fun getCounters(): Map<Any?, PerfCounters> = counters
|
||||||
|
|
||||||
@Suppress("OVERRIDE_BY_INLINE")
|
@Suppress("OVERRIDE_BY_INLINE")
|
||||||
override inline fun <R> withMeasure(obj: Any?, body: () -> R): R =
|
override inline fun beginMeasure(obj: Any?) =
|
||||||
withMeasureWallAndThreadTimes(counters.getOrPut(obj?.javaClass?.name, { SimplePerfCountersWithTotal(total) }), threadMXBean, body)
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
+20
@@ -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>
|
||||||
|
}
|
||||||
+30
@@ -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
|
||||||
|
}
|
||||||
+32
@@ -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
|
||||||
|
}
|
||||||
+10
@@ -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()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
compilerService.startDaemonLife()
|
||||||
|
|
||||||
println(COMPILE_DAEMON_IS_READY_MESSAGE)
|
println(COMPILE_DAEMON_IS_READY_MESSAGE)
|
||||||
log.info("daemon is listening on port: $port")
|
log.info("daemon is listening on port: $port")
|
||||||
|
|||||||
@@ -40,15 +40,11 @@ import java.util.logging.Logger
|
|||||||
import kotlin.concurrent.read
|
import kotlin.concurrent.read
|
||||||
import kotlin.concurrent.write
|
import kotlin.concurrent.write
|
||||||
|
|
||||||
open class KotlinJvmReplService(
|
abstract class KotlinJvmReplServiceBase(
|
||||||
disposable: Disposable,
|
disposable: Disposable,
|
||||||
val portForServers: Int,
|
val compilerId: CompilerId,templateClasspath: List<File>,
|
||||||
val compilerId: CompilerId,
|
templateClassName: String,
|
||||||
templateClasspath: List<File>,
|
protected val messageCollector: MessageCollector
|
||||||
templateClassName: String,
|
|
||||||
protected val messageCollector: MessageCollector,
|
|
||||||
@Deprecated("drop it")
|
|
||||||
protected val operationsTracer: RemoteOperationsTracer?
|
|
||||||
) : ReplCompileAction, ReplCheckAction, CreateReplStageStateAction {
|
) : ReplCompileAction, ReplCheckAction, CreateReplStageStateAction {
|
||||||
|
|
||||||
private val log by lazy { Logger.getLogger("replService") }
|
private val log by lazy { Logger.getLogger("replService") }
|
||||||
@@ -64,7 +60,28 @@ open class KotlinJvmReplService(
|
|||||||
configureScripting(compilerId)
|
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 {
|
try {
|
||||||
val projectEnvironment =
|
val projectEnvironment =
|
||||||
KotlinCoreEnvironment.ProjectEnvironment(
|
KotlinCoreEnvironment.ProjectEnvironment(
|
||||||
@@ -94,35 +111,56 @@ open class KotlinJvmReplService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected val statesLock = ReentrantReadWriteLock()
|
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()
|
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<*> =
|
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 {
|
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult {
|
||||||
operationsTracer?.before("check")
|
before("check")
|
||||||
try {
|
try {
|
||||||
return replCompiler?.check(state, codeLine) ?: ReplCheckResult.Error("Initialization error")
|
return replCompiler?.check(state, codeLine) ?: ReplCheckResult.Error("Initialization error")
|
||||||
}
|
} finally {
|
||||||
finally {
|
after("check")
|
||||||
operationsTracer?.after("check")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult {
|
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult {
|
||||||
operationsTracer?.before("compile")
|
before("compile")
|
||||||
try {
|
try {
|
||||||
return replCompiler?.compile(state, codeLine) ?: ReplCompileResult.Error("Initialization error")
|
return replCompiler?.compile(state, codeLine) ?: ReplCompileResult.Error("Initialization error")
|
||||||
}
|
} finally {
|
||||||
finally {
|
after("compile")
|
||||||
operationsTracer?.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")
|
@Deprecated("Use check(state, line) instead")
|
||||||
fun check(codeLine: ReplCodeLine): ReplCheckResult = check(defaultStateFacade.state, codeLine)
|
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 compile(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>?): ReplCompileResult = compile(defaultStateFacade.state, codeLine)
|
||||||
|
|
||||||
fun createRemoteState(port: Int = portForServers): RemoteReplStateFacadeServer = statesLock.write {
|
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)
|
val stateFacade = RemoteReplStateFacadeServer(id, createState(), port)
|
||||||
states.put(stateFacade, true)
|
states.put(stateFacade, true)
|
||||||
stateFacade
|
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 {
|
states.keys.firstOrNull { it.getId() == stateId }?.let {
|
||||||
CompileService.CallResult.Good(body(it.state))
|
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
|
// fighting hypothetical integer wrapping
|
||||||
var newId = counter.incrementAndGet()
|
var newId = counter.incrementAndGet()
|
||||||
var attemptsLeft = 100
|
var attemptsLeft = 100
|
||||||
@@ -181,7 +219,7 @@ inline internal fun getValidId(counter: AtomicInteger, check: (Int) -> Boolean):
|
|||||||
return newId
|
return newId
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CompilerConfiguration.configureScripting(compilerId: CompilerId) {
|
fun CompilerConfiguration.configureScripting(compilerId: CompilerId) {
|
||||||
val error = try {
|
val error = try {
|
||||||
val componentRegistrars =
|
val componentRegistrars =
|
||||||
(this::class.java.classLoader as? URLClassLoader)?.let {
|
(this::class.java.classLoader as? URLClassLoader)?.let {
|
||||||
|
|||||||
+1
-4
@@ -16,11 +16,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.daemon
|
package org.jetbrains.kotlin.daemon
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.daemon.common.*
|
||||||
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
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 org.jetbrains.kotlin.progress.CompilationCanceledException
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.logging.Logger
|
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.CompilerCallbackServicesFacade
|
||||||
import org.jetbrains.kotlin.daemon.common.DummyProfiler
|
import org.jetbrains.kotlin.daemon.common.DummyProfiler
|
||||||
import org.jetbrains.kotlin.daemon.common.Profiler
|
import org.jetbrains.kotlin.daemon.common.Profiler
|
||||||
|
import org.jetbrains.kotlin.daemon.common.withMeasure
|
||||||
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
|
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
|
||||||
import java.io.File
|
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.CompilerCallbackServicesFacade
|
||||||
import org.jetbrains.kotlin.daemon.common.DummyProfiler
|
import org.jetbrains.kotlin.daemon.common.DummyProfiler
|
||||||
import org.jetbrains.kotlin.daemon.common.Profiler
|
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.IncrementalCache
|
||||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
|
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
|
||||||
import org.jetbrains.kotlin.modules.TargetId
|
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.CompilerCallbackServicesFacade
|
||||||
import org.jetbrains.kotlin.daemon.common.Profiler
|
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.IncrementalDataProvider
|
||||||
import org.jetbrains.kotlin.incremental.js.TranslationResultValue
|
import org.jetbrains.kotlin.incremental.js.TranslationResultValue
|
||||||
import java.io.File
|
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.CompilerCallbackServicesFacade
|
||||||
import org.jetbrains.kotlin.daemon.common.Profiler
|
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.FunctionWithSourceInfo
|
||||||
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer
|
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer
|
||||||
import org.jetbrains.kotlin.incremental.js.JsInlineFunctionHash
|
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) :
|
class RemoteIncrementalResultsConsumer(val facade: CompilerCallbackServicesFacade, eventManager: EventManager, val rpcProfiler: Profiler) :
|
||||||
IncrementalResultsConsumer {
|
IncrementalResultsConsumer {
|
||||||
init {
|
init {
|
||||||
eventManager.onCompilationFinished(this::flush)
|
eventManager.
|
||||||
|
onCompilationFinished(this::flush)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun processHeader(headerMetadata: ByteArray) {
|
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.DummyProfiler
|
||||||
import org.jetbrains.kotlin.daemon.common.Profiler
|
import org.jetbrains.kotlin.daemon.common.Profiler
|
||||||
import org.jetbrains.kotlin.daemon.common.RemoteInputStream
|
import org.jetbrains.kotlin.daemon.common.RemoteInputStream
|
||||||
|
import org.jetbrains.kotlin.daemon.common.withMeasure
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
class RemoteInputStreamClient(val remote: RemoteInputStream, val profiler: Profiler = DummyProfiler()): 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.CompilerCallbackServicesFacade
|
||||||
import org.jetbrains.kotlin.daemon.common.DummyProfiler
|
import org.jetbrains.kotlin.daemon.common.DummyProfiler
|
||||||
import org.jetbrains.kotlin.daemon.common.Profiler
|
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.LookupInfo
|
||||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||||
import org.jetbrains.kotlin.incremental.components.Position
|
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.DummyProfiler
|
||||||
import org.jetbrains.kotlin.daemon.common.Profiler
|
import org.jetbrains.kotlin.daemon.common.Profiler
|
||||||
import org.jetbrains.kotlin.daemon.common.RemoteOutputStream
|
import org.jetbrains.kotlin.daemon.common.RemoteOutputStream
|
||||||
|
import org.jetbrains.kotlin.daemon.common.withMeasure
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
class RemoteOutputStreamClient(val remote: RemoteOutputStream, val profiler: Profiler = DummyProfiler()): OutputStream() {
|
class RemoteOutputStreamClient(val remote: RemoteOutputStream, val profiler: Profiler = DummyProfiler()): OutputStream() {
|
||||||
|
|||||||
+749
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+51
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
+69
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -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) }
|
||||||
|
}
|
||||||
+18
@@ -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)
|
||||||
|
}
|
||||||
+59
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
+73
@@ -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 org.jetbrains.kotlin.daemon.common.*
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
|
|
||||||
internal interface DaemonMessageReporter {
|
interface DaemonMessageReporter {
|
||||||
fun report(severity: ReportSeverity, message: String)
|
fun report(severity: ReportSeverity, message: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun DaemonMessageReporter(
|
fun DaemonMessageReporter(
|
||||||
servicesFacade: CompilerServicesFacadeBase,
|
servicesFacade: CompilerServicesFacadeBase,
|
||||||
compilationOptions: CompilationOptions
|
compilationOptions: CompilationOptions
|
||||||
): DaemonMessageReporter =
|
): DaemonMessageReporter =
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ package org.jetbrains.kotlin.daemon.report
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.incremental.ICReporter
|
import org.jetbrains.kotlin.incremental.ICReporter
|
||||||
|
|
||||||
internal interface RemoteICReporter : ICReporter {
|
interface RemoteICReporter : ICReporter {
|
||||||
fun flush()
|
fun flush()
|
||||||
}
|
}
|
||||||
+60
@@ -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
|
||||||
|
}
|
||||||
+49
@@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
+170
@@ -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.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
internal fun getICReporter(
|
fun getICReporter(
|
||||||
servicesFacade: CompilerServicesFacadeBase,
|
servicesFacade: CompilerServicesFacadeBase,
|
||||||
compilationResults: CompilationResults,
|
compilationResults: CompilationResults,
|
||||||
compilationOptions: IncrementalCompilationOptions
|
compilationOptions: IncrementalCompilationOptions
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ dependencies {
|
|||||||
testCompile(project(":compiler:serialization"))
|
testCompile(project(":compiler:serialization"))
|
||||||
testCompile(project(":kotlin-preloader"))
|
testCompile(project(":kotlin-preloader"))
|
||||||
testCompile(project(":compiler:daemon-common"))
|
testCompile(project(":compiler:daemon-common"))
|
||||||
|
testCompile(project(":compiler:daemon-common-new"))
|
||||||
testCompile(project(":js:js.serializer"))
|
testCompile(project(":js:js.serializer"))
|
||||||
testCompile(project(":js:js.frontend"))
|
testCompile(project(":js:js.frontend"))
|
||||||
testCompile(project(":js:js.translator"))
|
testCompile(project(":js:js.translator"))
|
||||||
|
|||||||
+326
@@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+1436
File diff suppressed because it is too large
Load Diff
+91
@@ -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()
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ dependencies {
|
|||||||
compile(project(":compiler:util"))
|
compile(project(":compiler:util"))
|
||||||
compile(project(":kotlin-build-common"))
|
compile(project(":kotlin-build-common"))
|
||||||
compile(project(":compiler:daemon-common"))
|
compile(project(":compiler:daemon-common"))
|
||||||
|
compile(project(":compiler:daemon-common-new"))
|
||||||
compile(projectRuntimeJar(":kotlin-daemon-client"))
|
compile(projectRuntimeJar(":kotlin-daemon-client"))
|
||||||
compile(project(":kotlin-compiler-runner")) { isTransitive = false }
|
compile(project(":kotlin-compiler-runner")) { isTransitive = false }
|
||||||
compile(project(":compiler:plugin-api"))
|
compile(project(":compiler:plugin-api"))
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ dependencies {
|
|||||||
compile(project(":core:descriptors.jvm"))
|
compile(project(":core:descriptors.jvm"))
|
||||||
compile(project(":kotlin-compiler-runner"))
|
compile(project(":kotlin-compiler-runner"))
|
||||||
compile(project(":compiler:daemon-common"))
|
compile(project(":compiler:daemon-common"))
|
||||||
|
compile(project(":compiler:daemon-common-new"))
|
||||||
compile(projectRuntimeJar(":kotlin-daemon-client"))
|
compile(projectRuntimeJar(":kotlin-daemon-client"))
|
||||||
compile(project(":compiler:frontend.java"))
|
compile(project(":compiler:frontend.java"))
|
||||||
compile(project(":js:js.frontend"))
|
compile(project(":js:js.frontend"))
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@
|
|||||||
of this license document, but changing it is not allowed.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
[This is the first released version of the Lesser GPL. It also counts
|
[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.]
|
the version number 2.1.]
|
||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
@@ -227,7 +227,7 @@ the scope of this License.
|
|||||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
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
|
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
|
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
|
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
|
ordinary GNU General Public License has appeared, then you can specify
|
||||||
that version instead if you wish.) Do not make any other change in
|
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
Reference in New Issue
Block a user