Implementing new logic of choosing the daemon, startup and shutdown, some related refactorings and fixes
This commit is contained in:
+24
-128
@@ -20,9 +20,9 @@ import net.rubygrapefruit.platform.ProcessLauncher
|
||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
|
||||
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
||||
import org.jetbrains.kotlin.rmi.*
|
||||
import java.io.*
|
||||
import java.rmi.ConnectException
|
||||
import java.rmi.registry.LocateRegistry
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
import java.io.PrintStream
|
||||
import java.util.concurrent.Semaphore
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.concurrent.thread
|
||||
@@ -61,7 +61,7 @@ public object KotlinCompilerClient {
|
||||
?.let { File(it) }
|
||||
?.ifOrNull { exists() }
|
||||
?: newFlagFile()
|
||||
return connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, reportingTargets, autostart, checkId)
|
||||
return connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, reportingTargets, autostart)
|
||||
}
|
||||
|
||||
public fun connectToCompileService(compilerId: CompilerId,
|
||||
@@ -69,55 +69,31 @@ public object KotlinCompilerClient {
|
||||
daemonJVMOptions: DaemonJVMOptions,
|
||||
daemonOptions: DaemonOptions,
|
||||
reportingTargets: DaemonReportingTargets,
|
||||
autostart: Boolean = true,
|
||||
checkId: Boolean = true
|
||||
autostart: Boolean = true
|
||||
): CompileService? {
|
||||
|
||||
var attempts = 0
|
||||
var fileLock: FileBasedLock? = null
|
||||
var shutdonwnPerformed = false
|
||||
try {
|
||||
while (attempts++ < DAEMON_CONNECT_CYCLE_ATTEMPTS) {
|
||||
val service = tryFindDaemon(File(daemonOptions.runFilesPath), compilerId, reportingTargets)
|
||||
val (service, newJVMOptions) = tryFindSuitableDaemonOrNewOpts(File(daemonOptions.runFilesPath), compilerId, daemonJVMOptions, { cat, msg -> reportingTargets.report(cat, msg) })
|
||||
if (service != null) {
|
||||
if (!checkId || service.checkCompilerId(compilerId)) {
|
||||
service.registerClient(clientAliveFlagFile.absolutePath)
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "connected to the daemon")
|
||||
return service
|
||||
}
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "compiler identity don't match: " + compilerId.mappers.flatMap { it.toArgs("") }.joinToString(" "))
|
||||
if (!autostart) return null
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "shutdown the daemon")
|
||||
service.shutdown()
|
||||
// TODO: find more reliable way
|
||||
Thread.sleep(1000)
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "daemon shut down correctly")
|
||||
shutdonwnPerformed = true
|
||||
// the newJVMOptions could be checked here for additional parameters, if needed
|
||||
service.registerClient(clientAliveFlagFile.absolutePath)
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "connected to the daemon")
|
||||
return service
|
||||
}
|
||||
else {
|
||||
if (!autostart) return null
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, if (shutdonwnPerformed) "starting a new daemon" else "no suitable daemon found, starting a new one")
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "no suitable daemon found, starting a new one")
|
||||
}
|
||||
|
||||
if (fileLock == null || !fileLock.isLocked()) {
|
||||
File(daemonOptions.runFilesPath).mkdirs()
|
||||
fileLock = FileBasedLock(compilerId, daemonOptions)
|
||||
// need to check the daemons again here, because of possible racing conditions
|
||||
// note: the algorithm could be simpler if we'll acquire lock right from the beginning, but it may be costly
|
||||
attempts--
|
||||
}
|
||||
else {
|
||||
startDaemon(compilerId, daemonJVMOptions, daemonOptions, reportingTargets)
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "daemon started, trying to find it")
|
||||
}
|
||||
startDaemon(compilerId, newJVMOptions, daemonOptions, reportingTargets)
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "daemon started, trying to find it")
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
reportingTargets.report(DaemonReportCategory.EXCEPTION, e.toString())
|
||||
}
|
||||
finally {
|
||||
fileLock?.release()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -270,60 +246,22 @@ public object KotlinCompilerClient {
|
||||
|
||||
// --- Implementation ---------------------------------------
|
||||
|
||||
fun DaemonReportingTargets.report(category: DaemonReportCategory, message: String, source: String = "daemon client") {
|
||||
private fun DaemonReportingTargets.report(category: DaemonReportCategory, message: String, source: String = "daemon client") {
|
||||
if (category == DaemonReportCategory.DEBUG && !verboseReporting) return
|
||||
out?.println("[$source] ${category.name()}: $message")
|
||||
out?.println("[$source] ${category.name}: $message")
|
||||
messages?.add(DaemonReportMessage(category, "[$source] $message"))
|
||||
}
|
||||
|
||||
|
||||
private fun String.extractPortFromRunFilename(digest: String): Int =
|
||||
makeRunFilenameString(timestamp = "[0-9TZ:\\.\\+-]+", digest = digest, port = "(\\d+)", escapeSequence = "\\").toRegex()
|
||||
.find(this)
|
||||
?.groups?.get(1)
|
||||
?.value?.toInt()
|
||||
?: 0
|
||||
|
||||
|
||||
private fun tryFindDaemon(registryDir: File, compilerId: CompilerId, reportingTargets: DaemonReportingTargets): CompileService? {
|
||||
val classPathDigest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest().toHexString()
|
||||
val daemons = registryDir.walk()
|
||||
.map { Pair(it, it.name.extractPortFromRunFilename(classPathDigest)) }
|
||||
.filter { it.second != 0 }
|
||||
.mapNotNull {
|
||||
assert(it.second > 0 && it.second < 0xffff)
|
||||
reportingTargets.report(DaemonReportCategory.DEBUG, "found suitable daemon on port ${it.second}, trying to connect")
|
||||
val daemon = tryConnectToDaemon(it.second, reportingTargets)
|
||||
// cleaning orphaned file; note: daemon should shut itself down if it detects that the run file is deleted
|
||||
if (daemon == null && !it.first.delete()) {
|
||||
reportingTargets.report(DaemonReportCategory.INFO, "WARNING: unable to delete seemingly orphaned file '${it.first.absolutePath}', cleanup recommended")
|
||||
}
|
||||
daemon
|
||||
}
|
||||
.toList()
|
||||
return when (daemons.size) {
|
||||
0 -> null
|
||||
1 -> daemons.first()
|
||||
else -> throw IllegalStateException("Multiple daemons serving the same compiler, reset with the cleanup required")
|
||||
// TODO: consider implementing automatic recovery instead, e.g. getting the youngest or least used daemon and shut down others
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun tryConnectToDaemon(port: Int, reportingTargets: DaemonReportingTargets): CompileService? {
|
||||
try {
|
||||
val daemon = LocateRegistry.getRegistry(LoopbackNetworkInterface.loopbackInetAddressName, port)
|
||||
?.lookup(COMPILER_SERVICE_RMI_NAME)
|
||||
if (daemon != null)
|
||||
return daemon as? CompileService ?:
|
||||
throw ClassCastException("Unable to cast compiler service, actual class received: ${daemon.javaClass}")
|
||||
reportingTargets.report(DaemonReportCategory.EXCEPTION, "daemon not found")
|
||||
}
|
||||
catch (e: ConnectException) {
|
||||
reportingTargets.report(DaemonReportCategory.EXCEPTION, "cannot connect to registry: " + (e.getCause()?.getMessage() ?: e.getMessage() ?: "unknown exception"))
|
||||
// ignoring it - processing below
|
||||
}
|
||||
return null
|
||||
private fun tryFindSuitableDaemonOrNewOpts(registryDir: File, compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, report: (DaemonReportCategory, String) -> Unit): Pair<CompileService?, DaemonJVMOptions> {
|
||||
val aliveWithOpts = walkDaemons(registryDir, compilerId, report)
|
||||
.map { Pair(it, it.getDaemonJVMOptions()) }
|
||||
.filter { it.second.isGood }
|
||||
// TODO: consider to sort the found daemons by memory settings and take the largest (rather that the first found), but carefully analyze possible situations first
|
||||
val opts = daemonJVMOptions.copy()
|
||||
return aliveWithOpts.firstOrNull { daemonJVMOptions memorywiseFitsInto it.second.get() }
|
||||
?.let { Pair(it.first, opts.updateMemoryUpperBounds(it.second.get())) }
|
||||
?: Pair(null, aliveWithOpts.fold(daemonJVMOptions, { opts, d -> opts.updateMemoryUpperBounds(d.second.get()) }))
|
||||
}
|
||||
|
||||
|
||||
@@ -396,51 +334,9 @@ public object KotlinCompilerClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FileBasedLock(compilerId: CompilerId, daemonOptions: DaemonOptions) {
|
||||
|
||||
private val lockFile: File =
|
||||
File(daemonOptions.runFilesPath,
|
||||
makeRunFilenameString(timestamp = "lock",
|
||||
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest().toHexString(),
|
||||
port = "0"))
|
||||
@Volatile private var locked = acquireLockFile(lockFile)
|
||||
private val channel = if (locked) RandomAccessFile(lockFile, "rw").channel else null
|
||||
private val lock = channel?.lock()
|
||||
|
||||
public fun isLocked(): Boolean = locked
|
||||
|
||||
@Synchronized public fun release(): Unit {
|
||||
if (locked) {
|
||||
lock?.release()
|
||||
channel?.close()
|
||||
lockFile.delete()
|
||||
locked = false
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized private fun acquireLockFile(lockFile: File): Boolean {
|
||||
val maxAttempts = COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_MS / COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_CHECK_MS + 1
|
||||
var attempt = 0L
|
||||
while (true) {
|
||||
if (lockFile.createNewFile()) break
|
||||
// attempt to delete if file is orphaned
|
||||
if (lockFile.delete() && lockFile.createNewFile()) break
|
||||
if (lockFile.exists() && ++attempt >= maxAttempts)
|
||||
throw IOException("Timeout waiting the release of the lock file '${lockFile.absolutePath}")
|
||||
Thread.sleep(COMPILE_DAEMON_STARTUP_LOCK_TIMEOUT_CHECK_MS)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum class DaemonReportCategory {
|
||||
DEBUG, INFO, EXCEPTION;
|
||||
}
|
||||
|
||||
public data class DaemonReportMessage(public val category: DaemonReportCategory, public val message: String)
|
||||
|
||||
public class DaemonReportingTargets(public val out: PrintStream? = null, public val messages: MutableCollection<DaemonReportMessage>? = null)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.rmi
|
||||
|
||||
import java.io.File
|
||||
import java.rmi.ConnectException
|
||||
import java.rmi.registry.LocateRegistry
|
||||
|
||||
|
||||
internal val MAX_PORT_NUMBER = 0xffff
|
||||
|
||||
|
||||
enum class DaemonReportCategory {
|
||||
DEBUG, INFO, EXCEPTION;
|
||||
}
|
||||
|
||||
|
||||
fun makeRunFilenameString(timestamp: String, digest: String, port: String, escapeSequence: String = ""): String = "$COMPILE_DAEMON_DEFAULT_FILES_PREFIX$escapeSequence.$timestamp$escapeSequence.$digest$escapeSequence.$port$escapeSequence.run"
|
||||
|
||||
|
||||
fun String.extractPortFromRunFilename(digest: String): Int =
|
||||
makeRunFilenameString(timestamp = "[0-9TZ:\\.\\+-]+", digest = digest, port = "(\\d+)", escapeSequence = "\\").toRegex()
|
||||
.find(this)
|
||||
?.groups?.get(1)
|
||||
?.value?.toInt()
|
||||
?: 0
|
||||
|
||||
|
||||
fun walkDaemons(registryDir: File, compilerId: CompilerId, report: (DaemonReportCategory, String) -> Unit): Sequence<CompileService> {
|
||||
val classPathDigest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest().toHexString()
|
||||
return registryDir.walk()
|
||||
.map { Pair(it, it.name.extractPortFromRunFilename(classPathDigest)) }
|
||||
.filter { it.second != 0 }
|
||||
.map {
|
||||
assert(it.second > 0 && it.second < MAX_PORT_NUMBER)
|
||||
report(DaemonReportCategory.DEBUG, "found daemon on port ${it.second}, trying to connect")
|
||||
val daemon = tryConnectToDaemon(it.second, report)
|
||||
// cleaning orphaned file; note: daemon should shut itself down if it detects that the run file is deleted
|
||||
if (daemon == null && !it.first.delete()) {
|
||||
report(DaemonReportCategory.INFO, "WARNING: unable to delete seemingly orphaned file '${it.first.absolutePath}', cleanup recommended")
|
||||
}
|
||||
daemon
|
||||
}
|
||||
.filterNotNull()
|
||||
}
|
||||
|
||||
|
||||
private inline fun tryConnectToDaemon(port: Int, report: (DaemonReportCategory, String) -> Unit): CompileService? {
|
||||
try {
|
||||
val daemon = LocateRegistry.getRegistry(LoopbackNetworkInterface.loopbackInetAddressName, port)
|
||||
?.lookup(COMPILER_SERVICE_RMI_NAME)
|
||||
if (daemon != null)
|
||||
return daemon as? CompileService ?:
|
||||
throw ClassCastException("Unable to cast compiler service, actual class received: ${daemon.javaClass}")
|
||||
report(DaemonReportCategory.EXCEPTION, "daemon not found")
|
||||
}
|
||||
catch (e: ConnectException) {
|
||||
report(DaemonReportCategory.EXCEPTION, "cannot connect to registry: " + (e.cause?.message ?: e.message ?: "unknown exception"))
|
||||
// ignoring it - processing below
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -36,11 +36,14 @@ public interface CompileService : Remote {
|
||||
public val NO_SESSION: Int = 0
|
||||
}
|
||||
|
||||
public sealed class CallResult<R> : Serializable {
|
||||
public sealed class CallResult<out R> : Serializable {
|
||||
class Good<R>(val result: R) : CallResult<R>()
|
||||
class Ok : CallResult<Nothing>()
|
||||
class Dying : CallResult<Nothing>()
|
||||
class Error(val message: String) : CallResult<Nothing>()
|
||||
|
||||
val isGood: Boolean get() = this is Good<*>
|
||||
|
||||
fun get(): R = when (this) {
|
||||
is Good<R> -> this.result
|
||||
is Dying -> throw IllegalStateException("Service is dying")
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.io.Serializable
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.security.MessageDigest
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.text.RegexOption
|
||||
|
||||
|
||||
public val COMPILER_JAR_NAME: String = "kotlin-compiler.jar"
|
||||
@@ -55,8 +56,6 @@ public val COMPILE_DAEMON_DEFAULT_RUN_DIR_PATH: String get() =
|
||||
|
||||
val CLASSPATH_ID_DIGEST = "MD5"
|
||||
|
||||
public fun makeRunFilenameString(timestamp: String, digest: String, port: String, escapeSequence: String = ""): String = "$COMPILE_DAEMON_DEFAULT_FILES_PREFIX$escapeSequence.$timestamp$escapeSequence.$digest$escapeSequence.$port$escapeSequence.run"
|
||||
|
||||
|
||||
open class PropMapper<C, V, P : KMutableProperty1<C, V>>(val dest: C,
|
||||
val prop: P,
|
||||
@@ -307,3 +306,35 @@ public fun configureDaemonOptions(opts: DaemonOptions): DaemonOptions {
|
||||
|
||||
|
||||
public fun configureDaemonOptions(): DaemonOptions = configureDaemonOptions(DaemonOptions())
|
||||
|
||||
|
||||
fun String.memToBytes(): Long? =
|
||||
"(\\d+)([kmg]?)".toRegex()
|
||||
.matchEntire(this.trim().toLowerCase())
|
||||
?.groups?.let { match ->
|
||||
match.get(1)?.value?.let {
|
||||
it.toLong() *
|
||||
when (match.get(2)?.value) {
|
||||
"k" -> 1 shl 10
|
||||
"m" -> 1 shl 20
|
||||
"g" -> 1 shl 30
|
||||
else -> 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun daemonJVMOptionsMemoryProps() =
|
||||
listOf(DaemonJVMOptions::maxMemory, DaemonJVMOptions::maxPermSize, DaemonJVMOptions::reservedCodeCacheSize)
|
||||
|
||||
|
||||
infix fun DaemonJVMOptions.memorywiseFitsInto(other: DaemonJVMOptions): Boolean =
|
||||
daemonJVMOptionsMemoryProps()
|
||||
.all { (it.get(this).memToBytes() ?: 0) <= (it.get(other).memToBytes() ?: 0) }
|
||||
|
||||
|
||||
fun DaemonJVMOptions.updateMemoryUpperBounds(other: DaemonJVMOptions): DaemonJVMOptions {
|
||||
daemonJVMOptionsMemoryProps()
|
||||
.forEach { if ((it.get(this).memToBytes() ?: 0) < (it.get(other).memToBytes() ?: 0)) it.set(this, it.get(other)) }
|
||||
return this
|
||||
}
|
||||
|
||||
+45
-16
@@ -30,6 +30,7 @@ import java.rmi.registry.Registry
|
||||
import java.rmi.server.UnicastRemoteObject
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import java.util.logging.Level
|
||||
@@ -81,12 +82,14 @@ class CompileServiceImpl(
|
||||
private val clientProxies: MutableSet<ClientOrSessionProxy> = hashSetOf()
|
||||
private val sessions: MutableMap<Int, ClientOrSessionProxy> = hashMapOf()
|
||||
|
||||
private val sessionsIdCounter: AtomicInteger = AtomicInteger(0)
|
||||
private val sessionsIdCounter = AtomicInteger(0)
|
||||
private val internalRng = Random()
|
||||
|
||||
private val classpathWatcher = LazyClasspathWatcher(compilerId.compilerClasspath)
|
||||
|
||||
private val compilationsCounter: AtomicInteger = AtomicInteger(0)
|
||||
private val compilationsCounter = AtomicInteger(0)
|
||||
private val shutdownQueued = AtomicBoolean(false)
|
||||
|
||||
@Volatile private var _lastUsedSeconds = nowSeconds()
|
||||
public val lastUsedSeconds: Long get() = if (rwlock.isWriteLocked || rwlock.readLockCount - rwlock.readHoldCount > 0) nowSeconds() else _lastUsedSeconds
|
||||
|
||||
@@ -138,7 +141,7 @@ class CompileServiceImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
// assuming wrap, jumping to random number to reduce probability of further ckashes
|
||||
// assuming wrap, jumping to random number to reduce probability of further clashes
|
||||
newId = sessionsIdCounter.addAndGet(internalRng.nextInt())
|
||||
}
|
||||
throw Exception("Invalid state or algorithm error")
|
||||
@@ -163,7 +166,7 @@ class CompileServiceImpl(
|
||||
override fun getUsedMemory(): CompileService.CallResult<Long> = ifAlive { usedMemory(withGC = true) }
|
||||
|
||||
override fun shutdown() {
|
||||
ifAliveExclusive {
|
||||
ifAliveExclusive(ignoreCompilerChanged = true) {
|
||||
log.info("Shutdown started")
|
||||
alive = false
|
||||
UnicastRemoteObject.unexportObject(this, true)
|
||||
@@ -261,7 +264,8 @@ class CompileServiceImpl(
|
||||
|
||||
// 3. clean dead clients, then check if any left - conditional shutdown (with small delay)
|
||||
synchronized(clientProxies) { clientProxies.removeAll( clientProxies.filter{ !it.isAlive }) }
|
||||
if (clientProxies.none()) {
|
||||
if (clientProxies.none() && compilationsCounter.get() > 0 && !shutdownQueued.get()) {
|
||||
log.info("No more clients left, delayed shutdown in ${daemonOptions.shutdownDelayMilliseconds}ms")
|
||||
shutdownWithDelay()
|
||||
}
|
||||
// 4. check idle timeout - shutdown
|
||||
@@ -279,11 +283,17 @@ class CompileServiceImpl(
|
||||
}
|
||||
|
||||
private fun shutdownWithDelay() {
|
||||
shutdownQueued.set(true)
|
||||
val currentCompilationsCount = compilationsCounter.get()
|
||||
timer.schedule(daemonOptions.shutdownDelayMilliseconds) {
|
||||
shutdownQueued.set(false)
|
||||
if (currentCompilationsCount == compilationsCounter.get()) {
|
||||
log.fine("Execute delayed shutdown")
|
||||
shutdown()
|
||||
}
|
||||
else {
|
||||
log.info("Cancel delayed shutdown due to new client")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +346,7 @@ class CompileServiceImpl(
|
||||
}
|
||||
|
||||
|
||||
fun<R> checkedCompile(args: Array<out String>, serviceOut: PrintStream, rpcProfiler: Profiler, body: () -> R): R {
|
||||
private fun<R> checkedCompile(args: Array<out String>, serviceOut: PrintStream, rpcProfiler: Profiler, body: () -> R): R {
|
||||
try {
|
||||
if (args.none())
|
||||
throw IllegalArgumentException("Error: empty arguments list.")
|
||||
@@ -378,20 +388,39 @@ class CompileServiceImpl(
|
||||
}
|
||||
}
|
||||
|
||||
fun<R> ifAlive(body: () -> R): CompileService.CallResult<R> = rwlock.read {
|
||||
if (!alive) CompileService.CallResult.Dying()
|
||||
CompileService.CallResult.Good( body() )
|
||||
private fun<R> ifAlive(ignoreCompilerChanged: Boolean = false, body: () -> R): CompileService.CallResult<R> = rwlock.read {
|
||||
ifAliveChecksImpl(ignoreCompilerChanged) { CompileService.CallResult.Good(body()) }
|
||||
}
|
||||
|
||||
// TODO: find how to implement it without using unique name for this variant; making name deliberately ugly meanwhile
|
||||
fun ifAlive_Nothing(body: () -> Unit): CompileService.CallResult<Nothing> = rwlock.read {
|
||||
if (!alive) CompileService.CallResult.Dying()
|
||||
body()
|
||||
CompileService.CallResult.Ok()
|
||||
private fun ifAlive_Nothing(ignoreCompilerChanged: Boolean = false, body: () -> Unit): CompileService.CallResult<Nothing> = rwlock.read {
|
||||
ifAliveChecksImpl(ignoreCompilerChanged) {
|
||||
body()
|
||||
CompileService.CallResult.Ok()
|
||||
}
|
||||
}
|
||||
|
||||
fun<R> ifAliveExclusive(body: () -> R): CompileService.CallResult<R> = rwlock.write {
|
||||
if (!alive) CompileService.CallResult.Dying()
|
||||
CompileService.CallResult.Good( body() )
|
||||
private fun<R> ifAliveExclusive(ignoreCompilerChanged: Boolean = false, body: () -> R): CompileService.CallResult<R> = rwlock.write {
|
||||
ifAliveChecksImpl(ignoreCompilerChanged) { CompileService.CallResult.Good(body()) }
|
||||
}
|
||||
|
||||
inline private fun<R> ifAliveChecksImpl(ignoreCompilerChanged: Boolean = false, body: () -> CompileService.CallResult<R>): CompileService.CallResult<R> =
|
||||
when {
|
||||
!alive -> CompileService.CallResult.Dying()
|
||||
!ignoreCompilerChanged && classpathWatcher.isChanged -> {
|
||||
log.info("Compiler changed, scheduling shutdown")
|
||||
timer.schedule(1) { shutdown() }
|
||||
CompileService.CallResult.Dying()
|
||||
}
|
||||
else -> {
|
||||
try {
|
||||
body()
|
||||
}
|
||||
catch (e: Exception) {
|
||||
log.log(Level.SEVERE, "Exception", e)
|
||||
CompileService.CallResult.Error(e.message ?: "unknown")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
val compilerId by lazy(LazyThreadSafetyMode.NONE) { CompilerId.makeCompilerId(compilerClassPath) }
|
||||
|
||||
private fun compileOnDaemon(clientAliveFile: File, compilerId: CompilerId, daemonJVMOptions: DaemonJVMOptions, daemonOptions: DaemonOptions, vararg args: String): CompilerResults {
|
||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, clientAliveFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true, checkId = true)
|
||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, clientAliveFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
|
||||
TestCase.assertNotNull("failed to connect daemon", daemon)
|
||||
daemon?.registerClient(clientAliveFile.absolutePath)
|
||||
val strm = ByteArrayOutputStream()
|
||||
@@ -264,7 +264,7 @@ public class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
||||
try {
|
||||
val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath)
|
||||
val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false)
|
||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true, checkId = true)
|
||||
val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true)
|
||||
TestCase.assertNotNull("failed to connect daemon", daemon)
|
||||
|
||||
val (registry, port) = findPortAndCreateRegistry(10, 16384, 65535)
|
||||
|
||||
Reference in New Issue
Block a user