Report compiler messages from daemon in Gradle thread

#KT-28836 fixed

RMI messages are reported from RMI threads.
When Gradle logger is used from non-Gradle thread,
messages are not grouped in command-line
and they are not shown in build scans.
To fix this, we store all messages from Kotlin daemon
in a buffer, then report them from a Gradle thread.
This commit is contained in:
Alexey Tsvetkov
2018-12-16 03:04:42 +03:00
parent 78d9cd1277
commit c87c5f11aa
3 changed files with 81 additions and 23 deletions
@@ -5,17 +5,20 @@
package org.jetbrains.kotlin.compilerRunner package org.jetbrains.kotlin.compilerRunner
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.daemon.client.reportFromDaemon import org.jetbrains.kotlin.daemon.client.reportFromDaemon
import org.jetbrains.kotlin.daemon.common.* import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
import org.jetbrains.kotlin.gradle.logging.GradleBufferingMessageCollector
import java.io.Serializable import java.io.Serializable
import java.rmi.Remote import java.rmi.Remote
import java.rmi.server.UnicastRemoteObject import java.rmi.server.UnicastRemoteObject
internal open class GradleCompilerServicesFacadeImpl( internal open class GradleCompilerServicesFacadeImpl(
private val log: KotlinLogger, private val log: KotlinLogger,
private val compilerMessageCollector: MessageCollector, // RMI messages are reported from RMI threads.
// Messages reported from non-Gradle threads are not grouped and not shown in build scans.
// To fix this, we store all messages in a buffer, then report them from a Gradle thread
private val compilerMessageCollector: GradleBufferingMessageCollector,
port: Int = SOCKET_ANY_FREE_PORT port: Int = SOCKET_ANY_FREE_PORT
) : UnicastRemoteObject(port, LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.serverLoopbackSocketFactory), ) : UnicastRemoteObject(port, LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.serverLoopbackSocketFactory),
CompilerServicesFacadeBase, CompilerServicesFacadeBase,
@@ -44,7 +47,7 @@ internal open class GradleCompilerServicesFacadeImpl(
internal class GradleIncrementalCompilerServicesFacadeImpl( internal class GradleIncrementalCompilerServicesFacadeImpl(
log: KotlinLogger, log: KotlinLogger,
messageCollector: MessageCollector, messageCollector: GradleBufferingMessageCollector,
port: Int = SOCKET_ANY_FREE_PORT port: Int = SOCKET_ANY_FREE_PORT
) : GradleCompilerServicesFacadeImpl(log, messageCollector, port), ) : GradleCompilerServicesFacadeImpl(log, messageCollector, port),
IncrementalCompilerServicesFacade IncrementalCompilerServicesFacade
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.compilerRunner package org.jetbrains.kotlin.compilerRunner
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.invocation.Gradle
import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
@@ -14,7 +13,9 @@ import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.config.Services import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.daemon.common.* import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.gradle.plugin.TaskLoggers
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
import org.jetbrains.kotlin.gradle.logging.GradleBufferingMessageCollector
import org.jetbrains.kotlin.gradle.tasks.GradleMessageCollector import org.jetbrains.kotlin.gradle.tasks.GradleMessageCollector
import org.jetbrains.kotlin.gradle.tasks.clearLocalStateDirectories import org.jetbrains.kotlin.gradle.tasks.clearLocalStateDirectories
import org.jetbrains.kotlin.gradle.tasks.throwGradleExceptionIfError import org.jetbrains.kotlin.gradle.tasks.throwGradleExceptionIfError
@@ -26,10 +27,8 @@ import java.io.*
import java.net.URLClassLoader import java.net.URLClassLoader
import java.rmi.RemoteException import java.rmi.RemoteException
import java.util.concurrent.Callable import java.util.concurrent.Callable
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
import javax.inject.Inject import javax.inject.Inject
import kotlin.concurrent.thread
internal class ProjectFilesForCompilation( internal class ProjectFilesForCompilation(
val projectRootFile: File, val projectRootFile: File,
@@ -93,7 +92,6 @@ internal class GradleKotlinCompilerWork @Inject constructor(
System.err.println("Could not get logger for '$taskPath'. Falling back to sl4j logger") System.err.println("Could not get logger for '$taskPath'. Falling back to sl4j logger")
SL4JKotlinLogger(LoggerFactory.getLogger("GradleKotlinCompilerWork")) SL4JKotlinLogger(LoggerFactory.getLogger("GradleKotlinCompilerWork"))
} }
private val messageCollector = GradleMessageCollector(log)
private val isIncremental: Boolean private val isIncremental: Boolean
get() = incrementalCompilationEnvironment != null get() = incrementalCompilationEnvironment != null
@@ -103,8 +101,9 @@ internal class GradleKotlinCompilerWork @Inject constructor(
clearLocalStateDirectories(log, localStateDirectories, "IC is disabled") clearLocalStateDirectories(log, localStateDirectories, "IC is disabled")
} }
val messageCollector = GradleMessageCollector(log)
val exitCode = try { val exitCode = try {
compileWithDaemonOrFallbackImpl() compileWithDaemonOrFallbackImpl(messageCollector)
} catch (e: Throwable) { } catch (e: Throwable) {
clearLocalStateDirectories(log, localStateDirectories, "exception when running compiler") clearLocalStateDirectories(log, localStateDirectories, "exception when running compiler")
throw e throw e
@@ -127,7 +126,7 @@ internal class GradleKotlinCompilerWork @Inject constructor(
throwGradleExceptionIfError(exitCode) throwGradleExceptionIfError(exitCode)
} }
private fun compileWithDaemonOrFallbackImpl(): ExitCode { private fun compileWithDaemonOrFallbackImpl(messageCollector: MessageCollector): ExitCode {
with(log) { with(log) {
kotlinDebug { "Kotlin compiler class: ${compilerClassName}" } kotlinDebug { "Kotlin compiler class: ${compilerClassName}" }
kotlinDebug { "Kotlin compiler classpath: ${compilerFullClasspath.joinToString { it.canonicalPath }}" } kotlinDebug { "Kotlin compiler classpath: ${compilerFullClasspath.joinToString { it.canonicalPath }}" }
@@ -136,7 +135,7 @@ internal class GradleKotlinCompilerWork @Inject constructor(
val executionStrategy = kotlinCompilerExecutionStrategy() val executionStrategy = kotlinCompilerExecutionStrategy()
if (executionStrategy == DAEMON_EXECUTION_STRATEGY) { if (executionStrategy == DAEMON_EXECUTION_STRATEGY) {
val daemonExitCode = compileWithDaemon() val daemonExitCode = compileWithDaemon(messageCollector)
if (daemonExitCode != null) { if (daemonExitCode != null) {
return daemonExitCode return daemonExitCode
@@ -147,21 +146,25 @@ internal class GradleKotlinCompilerWork @Inject constructor(
val isGradleDaemonUsed = System.getProperty("org.gradle.daemon")?.let(String::toBoolean) val isGradleDaemonUsed = System.getProperty("org.gradle.daemon")?.let(String::toBoolean)
return if (executionStrategy == IN_PROCESS_EXECUTION_STRATEGY || isGradleDaemonUsed == false) { return if (executionStrategy == IN_PROCESS_EXECUTION_STRATEGY || isGradleDaemonUsed == false) {
compileInProcess() compileInProcess(messageCollector)
} else { } else {
compileOutOfProcess() compileOutOfProcess()
} }
} }
private fun compileWithDaemon(): ExitCode? { private fun compileWithDaemon(messageCollector: MessageCollector): ExitCode? {
val isDebugEnabled = log.isDebugEnabled || System.getProperty("kotlin.daemon.debug.log")?.toBoolean() ?: false
val daemonMessageCollector =
if (isDebugEnabled) messageCollector else MessageCollector.NONE
val connection = val connection =
try { try {
GradleCompilerRunner.getDaemonConnectionImpl( GradleCompilerRunner.getDaemonConnectionImpl(
clientIsAliveFlagFile, clientIsAliveFlagFile,
sessionFlagFile, sessionFlagFile,
compilerFullClasspath, compilerFullClasspath,
messageCollector, daemonMessageCollector,
log.isDebugEnabled isDebugEnabled
) )
} catch (e: Throwable) { } catch (e: Throwable) {
log.error("Caught an exception trying to connect to Kotlin Daemon:") log.error("Caught an exception trying to connect to Kotlin Daemon:")
@@ -184,14 +187,17 @@ internal class GradleKotlinCompilerWork @Inject constructor(
KotlinCompilerClass.METADATA -> CompileService.TargetPlatform.METADATA KotlinCompilerClass.METADATA -> CompileService.TargetPlatform.METADATA
else -> throw IllegalArgumentException("Unknown compiler type $compilerClassName") else -> throw IllegalArgumentException("Unknown compiler type $compilerClassName")
} }
val bufferingMessageCollector = GradleBufferingMessageCollector()
val exitCode = try { val exitCode = try {
val res = if (isIncremental) { val res = if (isIncremental) {
incrementalCompilationWithDaemon(daemon, sessionId, targetPlatform) incrementalCompilationWithDaemon(daemon, sessionId, targetPlatform, bufferingMessageCollector)
} else { } else {
nonIncrementalCompilationWithDaemon(daemon, sessionId, targetPlatform) nonIncrementalCompilationWithDaemon(daemon, sessionId, targetPlatform, bufferingMessageCollector)
} }
bufferingMessageCollector.flush(messageCollector)
exitCodeFromProcessExitCode(log, res.get()) exitCodeFromProcessExitCode(log, res.get())
} catch (e: Throwable) { } catch (e: Throwable) {
bufferingMessageCollector.flush(messageCollector)
log.error("Compilation with Kotlin compile daemon was not successful") log.error("Compilation with Kotlin compile daemon was not successful")
log.error(e.stackTraceAsString()) log.error(e.stackTraceAsString())
null null
@@ -211,7 +217,8 @@ internal class GradleKotlinCompilerWork @Inject constructor(
private fun nonIncrementalCompilationWithDaemon( private fun nonIncrementalCompilationWithDaemon(
daemon: CompileService, daemon: CompileService,
sessionId: Int, sessionId: Int,
targetPlatform: CompileService.TargetPlatform targetPlatform: CompileService.TargetPlatform,
bufferingMessageCollector: GradleBufferingMessageCollector
): CompileService.CallResult<Int> { ): CompileService.CallResult<Int> {
val compilationOptions = CompilationOptions( val compilationOptions = CompilationOptions(
compilerMode = CompilerMode.NON_INCREMENTAL_COMPILER, compilerMode = CompilerMode.NON_INCREMENTAL_COMPILER,
@@ -220,14 +227,15 @@ internal class GradleKotlinCompilerWork @Inject constructor(
reportSeverity = reportSeverity(isVerbose), reportSeverity = reportSeverity(isVerbose),
requestedCompilationResults = emptyArray() requestedCompilationResults = emptyArray()
) )
val servicesFacade = GradleCompilerServicesFacadeImpl(log, messageCollector) val servicesFacade = GradleCompilerServicesFacadeImpl(log, bufferingMessageCollector)
return daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults = null) return daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults = null)
} }
private fun incrementalCompilationWithDaemon( private fun incrementalCompilationWithDaemon(
daemon: CompileService, daemon: CompileService,
sessionId: Int, sessionId: Int,
targetPlatform: CompileService.TargetPlatform targetPlatform: CompileService.TargetPlatform,
bufferingMessageCollector: GradleBufferingMessageCollector
): CompileService.CallResult<Int> { ): CompileService.CallResult<Int> {
val icEnv = incrementalCompilationEnvironment ?: error("incrementalCompilationEnvironment is null!") val icEnv = incrementalCompilationEnvironment ?: error("incrementalCompilationEnvironment is null!")
val knownChangedFiles = icEnv.changedFiles as? ChangedFiles.Known val knownChangedFiles = icEnv.changedFiles as? ChangedFiles.Known
@@ -249,7 +257,7 @@ internal class GradleKotlinCompilerWork @Inject constructor(
) )
log.info("Options for KOTLIN DAEMON: $compilationOptions") log.info("Options for KOTLIN DAEMON: $compilationOptions")
val servicesFacade = GradleIncrementalCompilerServicesFacadeImpl(log, messageCollector) val servicesFacade = GradleIncrementalCompilerServicesFacadeImpl(log, bufferingMessageCollector)
val compilationResults = GradleCompilationResults(log, projectRootFile) val compilationResults = GradleCompilationResults(log, projectRootFile)
return daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults) return daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults)
} }
@@ -257,21 +265,23 @@ internal class GradleKotlinCompilerWork @Inject constructor(
private fun compileOutOfProcess(): ExitCode = private fun compileOutOfProcess(): ExitCode =
runToolInSeparateProcess(compilerArgs, compilerClassName, compilerFullClasspath, log, loggingMessageCollector) runToolInSeparateProcess(compilerArgs, compilerClassName, compilerFullClasspath, log, loggingMessageCollector)
private fun compileInProcess(): ExitCode { private fun compileInProcess(messageCollector: MessageCollector): ExitCode {
// in-process compiler should always be run in a different thread // in-process compiler should always be run in a different thread
// to avoid leaking thread locals from compiler (see KT-28037) // to avoid leaking thread locals from compiler (see KT-28037)
val threadPool = Executors.newSingleThreadExecutor() val threadPool = Executors.newSingleThreadExecutor()
val bufferingMessageCollector = GradleBufferingMessageCollector()
try { try {
val future = threadPool.submit(Callable { val future = threadPool.submit(Callable {
compileInProcessImpl() compileInProcessImpl(bufferingMessageCollector)
}) })
return future.get() return future.get()
} finally { } finally {
bufferingMessageCollector.flush(messageCollector)
threadPool.shutdown() threadPool.shutdown()
} }
} }
private fun compileInProcessImpl(): ExitCode { private fun compileInProcessImpl(messageCollector: MessageCollector): ExitCode {
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
val out = PrintStream(stream) val out = PrintStream(stream)
// todo: cache classloader? // todo: cache classloader?
@@ -0,0 +1,45 @@
/*
* 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.gradle.logging
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 java.util.ArrayList
internal class GradleBufferingMessageCollector : MessageCollector {
private class MessageData(
val severity: CompilerMessageSeverity,
val message: String,
val location: CompilerMessageLocation?
)
private val messages = ArrayList<MessageData>()
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
synchronized(messages) {
messages.add(MessageData(severity, message, location))
}
}
override fun hasErrors() =
synchronized(messages) {
messages.any { it.severity.isError }
}
override fun clear() {
synchronized(messages) {
messages.clear()
}
}
fun flush(delegate: MessageCollector) {
messages.forEach {
delegate.report(it.severity, it.message, it.location)
}
clear()
}
}