diff --git a/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinRemoteReplClient.kt b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinRemoteReplClient.kt index ddefdb28325..22ae017f481 100644 --- a/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinRemoteReplClient.kt +++ b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/KotlinRemoteReplClient.kt @@ -16,45 +16,42 @@ package org.jetbrains.kotlin.daemon.client +import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.repl.* -import org.jetbrains.kotlin.daemon.common.CompileService -import org.jetbrains.kotlin.daemon.common.RemoteOperationsTracer -import org.jetbrains.kotlin.daemon.common.SOCKET_ANY_FREE_PORT +import org.jetbrains.kotlin.daemon.common.* import java.io.File import java.io.InputStream import java.io.OutputStream +import java.util.concurrent.locks.ReentrantReadWriteLock // TODO: reduce number of ports used then SOCKET_ANY_FREE_PORT is passed (same problem with other calls) -open class KotlinRemoteReplClientBase( +open class KotlinRemoteReplClient( protected val compileService: CompileService, clientAliveFlagFile: File?, targetPlatform: CompileService.TargetPlatform, + args: Array, + messageCollector: MessageCollector, templateClasspath: List, templateClassName: String, - scriptArgs: Array? = null, - scriptArgsTypes: Array>? = null, - compilerMessagesOutputStream: OutputStream = System.err, - evalOutputStream: OutputStream? = null, - evalErrorStream: OutputStream? = null, - evalInputStream: InputStream? = null, - port: Int = SOCKET_ANY_FREE_PORT, - operationsTracer: RemoteOperationsTracer? = null -) { + scriptArgsWithTypes: ScriptArgsWithTypes, + port: Int = SOCKET_ANY_FREE_PORT +) : ReplCompiler { + val services = BasicCompilerServicesWithResultsFacadeServer(messageCollector, null, port) val sessionId = compileService.leaseReplSession( clientAliveFlagFile?.absolutePath, - targetPlatform, - CompilerCallbackServicesFacadeServer(port = port), + 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, templateClasspath, templateClassName, - scriptArgs, - scriptArgsTypes, - RemoteOutputStreamServer(compilerMessagesOutputStream, port), - evalOutputStream?.let { RemoteOutputStreamServer(it, port) }, - evalErrorStream?.let { RemoteOutputStreamServer(it, port) }, - evalInputStream?.let { RemoteInputStreamServer(it, port) }, - operationsTracer + scriptArgsWithTypes ).get() // dispose should be called at the end of the repl lifetime to free daemon repl session and appropriate resources @@ -66,89 +63,13 @@ open class KotlinRemoteReplClientBase( // assuming that communication failed and daemon most likely is already down } } -} - -class KotlinRemoteReplCompiler( - compileService: CompileService, - clientAliveFlagFile: File?, - targetPlatform: CompileService.TargetPlatform, - templateClasspath: List, - templateClassName: String, - compilerMessagesOutputStream: OutputStream, - port: Int = SOCKET_ANY_FREE_PORT, - operationsTracer: RemoteOperationsTracer? = null -) : KotlinRemoteReplClientBase( - compileService = compileService, - clientAliveFlagFile = clientAliveFlagFile, - targetPlatform = targetPlatform, - templateClasspath = templateClasspath, - templateClassName = templateClassName, - compilerMessagesOutputStream = compilerMessagesOutputStream, - evalOutputStream = null, - evalErrorStream = null, - evalInputStream = null, - port = port, - operationsTracer = operationsTracer -), ReplCompiler, ReplCheckAction { - override fun resetToLine(lineNumber: Int): List { - return emptyList() // TODO: not implemented, no current need - } - - override val history: List - get() = emptyList() // TODO: not implemented, no current need - - override fun check(codeLine: ReplCodeLine): ReplCheckResult { - return compileService.remoteReplLineCheck(sessionId, codeLine).get() - } - - override fun compile(codeLine: ReplCodeLine, verifyHistory: List?): ReplCompileResult { - return compileService.remoteReplLineCompile(sessionId, codeLine, verifyHistory).get() - } -} - -// TODO: consider removing daemon eval completely - it is not required now and has questionable security. This will simplify daemon interface as well -class KotlinRemoteReplEvaluator( - compileService: CompileService, - clientAliveFlagFile: File?, - targetPlatform: CompileService.TargetPlatform, - templateClasspath: List, - templateClassName: String, - scriptArgs: Array? = null, - scriptArgsTypes: Array>? = null, - compilerMessagesOutputStream: OutputStream, - evalOutputStream: OutputStream?, - evalErrorStream: OutputStream?, - evalInputStream: InputStream?, - port: Int = SOCKET_ANY_FREE_PORT, - operationsTracer: RemoteOperationsTracer? = null -) : KotlinRemoteReplClientBase( - compileService = compileService, - clientAliveFlagFile = clientAliveFlagFile, - targetPlatform = targetPlatform, - templateClasspath = templateClasspath, - templateClassName = templateClassName, - scriptArgs = scriptArgs, - scriptArgsTypes = scriptArgsTypes, - compilerMessagesOutputStream = compilerMessagesOutputStream, - evalOutputStream = evalOutputStream, - evalErrorStream = evalErrorStream, - evalInputStream = evalInputStream, - port = port, - operationsTracer = operationsTracer -), ReplAtomicEvalAction, ReplEvaluatorExposedInternalHistory, ReplCheckAction { - - override val lastEvaluatedScripts: List = emptyList() // not implemented, no need so far - - // TODO: invokeWrapper is ignored here, and in the daemon the session wrapper is used instead; So consider to make it per call (avoid performance penalties though) - // TODO: scriptArgs are ignored here, they should be passed through - override fun compileAndEval(codeLine: ReplCodeLine, - scriptArgs: ScriptArgsWithTypes?, - verifyHistory: List?, - invokeWrapper: InvokeWrapper?): ReplEvalResult { - return compileService.remoteReplLineEval(sessionId, codeLine, verifyHistory).get() - } - - override fun check(codeLine: ReplCodeLine): ReplCheckResult { - return compileService.remoteReplLineCheck(sessionId, codeLine).get() - } + + override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = + RemoteReplCompilerState(compileService.replCreateState(sessionId).get(), lock) + + override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = + compileService.replCheck(sessionId, state.asState().replStateFacade.id, codeLine).get() + + override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult = + compileService.replCompile(sessionId, state.asState().replStateFacade.id, codeLine).get() } diff --git a/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/RemoteReplCompilerState.kt b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/RemoteReplCompilerState.kt new file mode 100644 index 00000000000..f297dfa70ff --- /dev/null +++ b/compiler/daemon/daemon-client/src/org/jetbrains/kotlin/daemon/client/RemoteReplCompilerState.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2017 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.client + +import org.jetbrains.kotlin.cli.common.repl.ILineId +import org.jetbrains.kotlin.cli.common.repl.IReplStageHistory +import org.jetbrains.kotlin.cli.common.repl.IReplStageState +import org.jetbrains.kotlin.cli.common.repl.ReplHistoryRecord +import org.jetbrains.kotlin.daemon.common.ReplStateFacade +import java.util.concurrent.locks.ReentrantReadWriteLock + +// NOTE: the lock is local +// TODO: verify that locla lock doesn't lead to any synch problems +class RemoteReplCompilerStateHistory(private val state: RemoteReplCompilerState) : IReplStageHistory, AbstractList>() { + override val size: Int + get() = state.replStateFacade.historySize + + override fun get(index: Int): ReplHistoryRecord = 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? { + throw NotImplementedError("pop from remote history is not supported") + } + + override fun resetTo(id: ILineId): Iterable = state.replStateFacade.historyResetTo(id) + + override val lock: ReentrantReadWriteLock get() = state.lock +} + +class RemoteReplCompilerState(internal val replStateFacade: ReplStateFacade, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : IReplStageState { + + override val history: IReplStageHistory = RemoteReplCompilerStateHistory(this) +} \ No newline at end of file diff --git a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompileService.kt b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompileService.kt index aa7715d43af..8a667b0cc8c 100644 --- a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompileService.kt +++ b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompileService.kt @@ -16,10 +16,7 @@ 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 org.jetbrains.kotlin.cli.common.repl.ReplEvalResult +import org.jetbrains.kotlin.cli.common.repl.* import java.io.File import java.io.Serializable import java.rmi.Remote @@ -38,8 +35,6 @@ interface CompileService : Remote { METADATA } - - companion object { val NO_SESSION: Int = 0 } @@ -107,6 +102,7 @@ interface CompileService : Remote { // TODO: consider adding async version of shutdown and release + @Deprecated("The usages should be replaced with `compile` method", ReplaceWith("compile")) @Throws(RemoteException::class) fun remoteCompile( sessionId: Int, @@ -119,6 +115,7 @@ interface CompileService : Remote { operationsTracer: RemoteOperationsTracer? ): CallResult + @Deprecated("The usages should be replaced with `compile` method", ReplaceWith("compile")) @Throws(RemoteException::class) fun remoteIncrementalCompile( sessionId: Int, @@ -143,6 +140,7 @@ interface CompileService : Remote { @Throws(RemoteException::class) fun clearJarCache() + @Deprecated("The usages should be replaced with other `leaseReplSession` method", ReplaceWith("leaseReplSession")) @Throws(RemoteException::class) fun leaseReplSession( aliveFlagPath: String?, @@ -162,12 +160,14 @@ interface CompileService : Remote { @Throws(RemoteException::class) fun releaseReplSession(sessionId: Int): CallResult + @Deprecated("The usages should be replaced with `replCheck` method", ReplaceWith("replCheck")) @Throws(RemoteException::class) fun remoteReplLineCheck( sessionId: Int, codeLine: ReplCodeLine ): CallResult + @Deprecated("The usages should be replaced with `replCompile` method", ReplaceWith("replCompile")) @Throws(RemoteException::class) fun remoteReplLineCompile( sessionId: Int, @@ -175,10 +175,39 @@ interface CompileService : Remote { history: List? ): CallResult + @Deprecated("Evaluation on daemon is not supported") @Throws(RemoteException::class) fun remoteReplLineEval( sessionId: Int, codeLine: ReplCodeLine, history: List? ): CallResult + + @Throws(RemoteException::class) + fun leaseReplSession( + aliveFlagPath: String?, + compilerArguments: Array, + compilationOptions: CompilationOptions, + servicesFacade: CompilerServicesFacadeBase, + templateClasspath: List, + templateClassName: String, + scriptArgsWithTypes: ScriptArgsWithTypes? + ): CallResult + + @Throws(RemoteException::class) + fun replCreateState(sessionId: Int): CallResult + + @Throws(RemoteException::class) + fun replCheck( + sessionId: Int, + replStateId: Int, + codeLine: ReplCodeLine + ): CallResult + + @Throws(RemoteException::class) + fun replCompile( + sessionId: Int, + replStateId: Int, + codeLine: ReplCodeLine + ): CallResult } diff --git a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilerCallbackServicesFacade.kt b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilerCallbackServicesFacade.kt index 4afdf2e9b26..bb655e0f6a4 100644 --- a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilerCallbackServicesFacade.kt +++ b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilerCallbackServicesFacade.kt @@ -29,6 +29,7 @@ import java.rmi.RemoteException * the reason for having common facade is attempt to reduce number of connections between client and daemon * Note: non-standard naming convention used to denote combining several entities in one facade - prefix _ is used for every function belonging to the entity */ +@Deprecated("The usages should be replaced with `compile` method and `CompilerServicesFacadeBase` implementations", ReplaceWith("CompilerServicesFacadeBase")) interface CompilerCallbackServicesFacade : Remote { @Throws(RemoteException::class) diff --git a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/ReplStateFacade.kt b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/ReplStateFacade.kt new file mode 100644 index 00000000000..c4509bfc315 --- /dev/null +++ b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/ReplStateFacade.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.daemon.common + +import org.jetbrains.kotlin.cli.common.repl.ILineId +import java.rmi.Remote + +interface ReplStateFacade : Remote { + val id: Int + + val historySize: Int + + fun historyGet(index: Int): ILineId + + fun historyResetTo(id: ILineId): List +} \ No newline at end of file diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt index e81fccf2d38..da19e0f4df3 100644 --- a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt @@ -63,7 +63,6 @@ import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.logging.Level import java.util.logging.Logger -import kotlin.comparisons.compareByDescending import kotlin.concurrent.read import kotlin.concurrent.schedule import kotlin.concurrent.write @@ -102,6 +101,7 @@ class CompileServiceImpl( val timer: Timer, val onShutdown: () -> Unit ) : CompileService { + init { System.setProperty(KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY, "true") } @@ -133,24 +133,13 @@ class CompileServiceImpl( private val lock = ReentrantReadWriteLock() private val sessions: MutableMap> = hashMapOf() private val sessionsIdCounter = AtomicInteger(0) - private val internalRng = Random() - fun leaseSession(session: ClientOrSessionProxy): Int { - // fighting hypothetical integer wrapping - var newId = sessionsIdCounter.incrementAndGet() - for (attempt in 1..100) { - if (newId != CompileService.NO_SESSION) { - lock.write { - if (!sessions.containsKey(newId)) { - sessions.put(newId, session) - return newId - } - } - } - // assuming wrap, jumping to random number to reduce probability of further clashes - newId = sessionsIdCounter.addAndGet(internalRng.nextInt()) + fun leaseSession(session: ClientOrSessionProxy): Int = lock.write { + val newId = getValidId(sessionsIdCounter) { + it != CompileService.NO_SESSION && !sessions.containsKey(it) } - throw IllegalStateException("Invalid state or algorithm error") + sessions.put(newId, session) + newId } fun isEmpty(): Boolean = lock.read { sessions.isEmpty() } @@ -463,9 +452,11 @@ class CompileServiceImpl( CompileService.CallResult.Error("Sorry, only JVM target platform is supported now") else { val disposable = Disposer.newDisposable() - val repl = KotlinJvmReplService(disposable, templateClasspath, templateClassName, + val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerMessagesOutputStream, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE)) + val messageCollector = KeepFirstErrorMessageCollector(compilerMessagesStream) + val repl = KotlinJvmReplService(disposable, port, templateClasspath, templateClassName, scriptArgs?.let { ScriptArgsWithTypes(it, scriptArgsTypes?.map { it.kotlin }?.toTypedArray() ?: emptyArray()) }, - compilerMessagesOutputStream, operationsTracer) + messageCollector, operationsTracer) val sessionId = state.sessions.leaseSession(ClientOrSessionProxy(aliveFlagPath, repl, disposable)) CompileService.CallResult.Good(sessionId) @@ -478,14 +469,14 @@ class CompileServiceImpl( override fun remoteReplLineCheck(sessionId: Int, codeLine: ReplCodeLine): CompileService.CallResult = ifAlive(minAliveness = Aliveness.Alive) { withValidRepl(sessionId) { - check(codeLine) + CompileService.CallResult.Good(check(codeLine)) } } override fun remoteReplLineCompile(sessionId: Int, codeLine: ReplCodeLine, history: List?): CompileService.CallResult = ifAlive(minAliveness = Aliveness.Alive) { withValidRepl(sessionId) { - compile(codeLine, history) + CompileService.CallResult.Good(compile(codeLine, history)) } } @@ -496,10 +487,57 @@ class CompileServiceImpl( ): CompileService.CallResult = ifAlive(minAliveness = Aliveness.Alive) { withValidRepl(sessionId) { - compileAndEval(codeLine, verifyHistory = history) + CompileService.CallResult.Good(compileAndEval(codeLine, verifyHistory = history)) } } + override fun leaseReplSession(aliveFlagPath: String?, + compilerArguments: Array, + compilationOptions: CompilationOptions, + servicesFacade: CompilerServicesFacadeBase, + templateClasspath: List, + templateClassName: String, + scriptArgsWithTypes: ScriptArgsWithTypes? + ): CompileService.CallResult = 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 = KotlinJvmReplService(disposable, port, templateClasspath, templateClassName, + scriptArgsWithTypes, messageCollector, null) + val sessionId = state.sessions.leaseSession(ClientOrSessionProxy(aliveFlagPath, repl, disposable)) + + CompileService.CallResult.Good(sessionId) + } + } + + override fun replCreateState(sessionId: Int): CompileService.CallResult = + ifAlive(minAliveness = Aliveness.Alive) { + withValidRepl(sessionId) { + CompileService.CallResult.Good(createRemoteState(port)) + } + } + + override fun replCheck(sessionId: Int, replStateId: Int, codeLine: ReplCodeLine): CompileService.CallResult = + ifAlive(minAliveness = Aliveness.Alive) { + withValidRepl(sessionId) { + withValidReplState(replStateId) { state -> + check(state, codeLine) + } + } + } + + override fun replCompile(sessionId: Int, replStateId: Int, codeLine: ReplCodeLine): CompileService.CallResult = + ifAlive(minAliveness = Aliveness.Alive) { + withValidRepl(sessionId) { + withValidReplState(replStateId) { state -> + compile(state, codeLine) + } + } + } + + // ----------------------------------------------------------------------- // internal implementation stuff // TODO: consider matching compilerId coming from outside with actual one @@ -852,7 +890,6 @@ class CompileServiceImpl( finally { _lastUsedSeconds = nowSeconds() } - } private inline fun withValidRepl(sessionId: Int, body: KotlinJvmReplService.() -> R): CompileService.CallResult = @@ -861,4 +898,12 @@ class CompileServiceImpl( CompileService.CallResult.Good(it.body()) } ?: CompileService.CallResult.Error("Not a REPL session $sessionId") } + + @JvmName("withValidRepl1") + private inline fun withValidRepl(sessionId: Int, body: KotlinJvmReplService.() -> CompileService.CallResult): CompileService.CallResult = + withValidClientOrSessionProxy(sessionId) { session -> + (session?.data as? KotlinJvmReplService?)?.let { + it.body() + } ?: CompileService.CallResult.Error("Not a REPL session $sessionId") + } } diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/KotlinRemoteReplService.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/KotlinRemoteReplService.kt index 37d3bb2fa91..1df1f952a4c 100644 --- a/compiler/daemon/src/org/jetbrains/kotlin/daemon/KotlinRemoteReplService.kt +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/KotlinRemoteReplService.kt @@ -22,50 +22,33 @@ import org.jetbrains.kotlin.cli.common.repl.* import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots import org.jetbrains.kotlin.cli.jvm.repl.GenericReplCompiler +import org.jetbrains.kotlin.cli.jvm.repl.GenericReplCompilerState import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration -import org.jetbrains.kotlin.daemon.common.DummyProfiler +import org.jetbrains.kotlin.daemon.common.CompileService import org.jetbrains.kotlin.daemon.common.RemoteOperationsTracer -import org.jetbrains.kotlin.daemon.common.RemoteOutputStream import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate import org.jetbrains.kotlin.utils.PathUtil -import java.io.BufferedOutputStream import java.io.File import java.io.PrintStream import java.net.URLClassLoader +import java.util.* +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write open class KotlinJvmReplService( disposable: Disposable, + val portForServers: Int, templateClasspath: List, templateClassName: String, protected val fallbackScriptArgs: ScriptArgsWithTypes?, - compilerOutputStreamProxy: RemoteOutputStream, + protected val messageCollector: MessageCollector, + @Deprecated("drop it") protected val operationsTracer: RemoteOperationsTracer? -) : ReplCompileAction, ReplAtomicEvalAction, ReplCheckAction, ReplEvaluatorExposedInternalHistory { - protected val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerOutputStreamProxy, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE)) - - protected class KeepFirstErrorMessageCollector(compilerMessagesStream: PrintStream) : MessageCollector { - - private val innerCollector = PrintingMessageCollector(compilerMessagesStream, MessageRenderer.WITHOUT_PATHS, false) - - internal var firstErrorMessage: String? = null - internal var firstErrorLocation: CompilerMessageLocation? = null - - override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) { - if (firstErrorMessage == null && severity.isError) { - firstErrorMessage = message - firstErrorLocation = location - } - innerCollector.report(severity, message, location) - } - - override fun hasErrors(): Boolean = innerCollector.hasErrors() - override fun clear() { - innerCollector.clear() - } - } - protected val messageCollector = KeepFirstErrorMessageCollector(compilerMessagesStream) +) : ReplCompileAction, ReplAtomicEvalAction, ReplCheckAction, CreateReplStageStateAction { protected val configuration = CompilerConfiguration().apply { addJvmClasspathRoots(PathUtil.getJdkClassesRoots()) @@ -113,43 +96,107 @@ open class KotlinJvmReplService( } } - override val lastEvaluatedScripts: List get() = replEvaluator?.lastEvaluatedScripts ?: emptyList() + protected val statesLock = ReentrantReadWriteLock() + // TODO: consider using values here for session cleanup + protected val states = WeakHashMap() // used as (missing) WeakHashSet + protected val stateIdCounter = AtomicInteger() + @Deprecated("remove after removal state-less check/compile/eval methods") + protected val defaultStateFacade: RemoteReplStateFacadeServer by lazy { createRemoteState() } - override fun check(codeLine: ReplCodeLine): ReplCheckResult { + override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = + replCompiler?.createState(lock) ?: throw IllegalStateException("repl compiler is not initialized properly") + + override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult { operationsTracer?.before("check") try { - return replCompiler?.check(codeLine) - ?: ReplCheckResult.Error(messageCollector.firstErrorMessage ?: "Unknown error", - messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION) + return replCompiler?.check(state, codeLine) + ?: ReplCheckResult.Error("Initialization error", CompilerMessageLocation.NO_LOCATION) } finally { operationsTracer?.after("check") } } - override fun compile(codeLine: ReplCodeLine, verifyHistory: List?): ReplCompileResult { + override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult { operationsTracer?.before("compile") try { - return replCompiler?.compile(codeLine, verifyHistory) - ?: ReplCompileResult.Error(verifyHistory ?: emptyList(), - messageCollector.firstErrorMessage ?: "Unknown error", - messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION) + return replCompiler?.compile(state, codeLine) + ?: ReplCompileResult.Error("Initialization error", CompilerMessageLocation.NO_LOCATION) } finally { operationsTracer?.after("compile") } } - override fun compileAndEval(codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, verifyHistory: List?, invokeWrapper: InvokeWrapper?): ReplEvalResult { + @Deprecated("eval is not supported on daemon") + override fun compileAndEval(state: IReplStageState<*>, codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult { operationsTracer?.before("eval") try { - return replEvaluator?.compileAndEval(codeLine, scriptArgs ?: fallbackScriptArgs, verifyHistory, invokeWrapper) - ?: ReplEvalResult.Error.CompileTime(verifyHistory ?: replEvaluator?.history ?: emptyList(), - messageCollector.firstErrorMessage ?: "Unknown error", - messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION) + return replEvaluator?.compileAndEval(state, codeLine, scriptArgs ?: fallbackScriptArgs, invokeWrapper) + ?: ReplEvalResult.Error.Runtime("Initialization error") } finally { operationsTracer?.after("eval") } } + + @Deprecated("Use check(state, line) instead") + fun check(codeLine: ReplCodeLine): ReplCheckResult = check(defaultStateFacade.state, codeLine) + + @Deprecated("Use compile(state, line) instead") + fun compile(codeLine: ReplCodeLine, verifyHistory: List?): ReplCompileResult = compile(defaultStateFacade.state, codeLine) + + @Deprecated("eval is not supported on daemon") + fun compileAndEval(codeLine: ReplCodeLine, verifyHistory: List?): ReplEvalResult = ReplEvalResult.Error.Runtime("Eval is not supported on daemon") + + fun createRemoteState(port: Int = portForServers): RemoteReplStateFacadeServer = statesLock.write { + val id = getValidId(stateIdCounter) { id -> states.none { it.key.id == id} } + val stateFacade = RemoteReplStateFacadeServer(id, createState().asState(), port) + states.put(stateFacade, true) + stateFacade + } + + fun withValidReplState(stateId: Int, body: (IReplStageState<*>) -> R): CompileService.CallResult = statesLock.read { + states.keys.firstOrNull { it.id == stateId }?.let { + CompileService.CallResult.Good(body(it.state)) + } + ?: CompileService.CallResult.Error("No REPL state with id $stateId found") + } +} + +internal class KeepFirstErrorMessageCollector(compilerMessagesStream: PrintStream) : MessageCollector { + + private val innerCollector = PrintingMessageCollector(compilerMessagesStream, MessageRenderer.WITHOUT_PATHS, false) + + internal var firstErrorMessage: String? = null + internal var firstErrorLocation: CompilerMessageLocation? = null + + override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) { + if (firstErrorMessage == null && severity.isError) { + firstErrorMessage = message + firstErrorLocation = location + } + innerCollector.report(severity, message, location) + } + + override fun hasErrors(): Boolean = innerCollector.hasErrors() + override fun clear() { + innerCollector.clear() + } +} + +internal val internalRng = Random() + +inline internal fun getValidId(counter: AtomicInteger, check: (Int) -> Boolean): Int { + // fighting hypothetical integer wrapping + var newId = counter.incrementAndGet() + var attemptsLeft = 100 + while (!check(newId)) { + attemptsLeft -= 1 + if (attemptsLeft <= 0) + throw IllegalStateException("Invalid state or algorithm error") + // assuming wrap, jumping to random number to reduce probability of further clashes + newId = counter.addAndGet(internalRng.nextInt()) + } + return newId } diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/RemoteReplStateFacadeImpl.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/RemoteReplStateFacadeImpl.kt new file mode 100644 index 00000000000..3ae04737d88 --- /dev/null +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/RemoteReplStateFacadeImpl.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2017 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 + +import org.jetbrains.kotlin.cli.common.repl.ILineId +import org.jetbrains.kotlin.cli.jvm.repl.GenericReplCompilerState +import org.jetbrains.kotlin.daemon.common.LoopbackNetworkInterface +import org.jetbrains.kotlin.daemon.common.ReplStateFacade +import org.jetbrains.kotlin.daemon.common.SOCKET_ANY_FREE_PORT +import java.rmi.server.UnicastRemoteObject + +class RemoteReplStateFacadeServer(override val id: Int, + val state: GenericReplCompilerState, + port: Int = SOCKET_ANY_FREE_PORT +) : ReplStateFacade, + UnicastRemoteObject(port, LoopbackNetworkInterface.clientLoopbackSocketFactory, LoopbackNetworkInterface.serverLoopbackSocketFactory) +{ + override val historySize: Int = state.history.size + + override fun historyGet(index: Int): ILineId = state.history[index].id + + override fun historyResetTo(id: ILineId): List = state.history.resetTo(id).toList() +} diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt index a1c5312324d..4224603e8b4 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt @@ -16,11 +16,14 @@ package org.jetbrains.kotlin.jsr223 +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.repl.* import org.jetbrains.kotlin.daemon.client.DaemonReportMessage import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets import org.jetbrains.kotlin.daemon.client.KotlinCompilerClient -import org.jetbrains.kotlin.daemon.client.KotlinRemoteReplCompiler +import org.jetbrains.kotlin.daemon.client.KotlinRemoteReplClient import org.jetbrains.kotlin.daemon.common.* import org.jetbrains.kotlin.utils.PathUtil import java.io.File @@ -52,14 +55,18 @@ class KotlinJsr223JvmScriptEngine4Idea( ?: throw ScriptException("Unable to connect to repl server:" + daemonReportMessages.joinToString("\n ", prefix = "\n ") { "${it.category.name} ${it.message}" }) } + private val messageCollector = MyMessageCollector() + override val replCompiler: ReplCompiler by lazy { daemon.let { - KotlinRemoteReplCompiler(it, - makeAutodeletingFlagFile("idea-jsr223-repl-session"), - CompileService.TargetPlatform.JVM, - templateClasspath, - templateClassName, - System.out) + KotlinRemoteReplClient(it, + makeAutodeletingFlagFile("idea-jsr223-repl-session"), + CompileService.TargetPlatform.JVM, + emptyArray(), + messageCollector, + templateClasspath, + templateClassName, + ScriptArgsWithTypes(emptyArray(), emptyArray())) } } @@ -69,4 +76,20 @@ class KotlinJsr223JvmScriptEngine4Idea( val localEvaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(replCompiler, templateClasspath, Thread.currentThread().contextClassLoader) } override val replEvaluator: ReplFullEvaluator get() = localEvaluator + + private class MyMessageCollector : MessageCollector { + + var _hasErrors: Boolean = false + + override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) { + System.err.println(message) // TODO: proper location printing + if (!_hasErrors) { + _hasErrors = severity == CompilerMessageSeverity.EXCEPTION || severity == CompilerMessageSeverity.ERROR + } + } + + override fun clear() {} + + override fun hasErrors(): Boolean = _hasErrors + } }