Implement remote part of the new repl state handling
This commit is contained in:
+28
-107
@@ -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<out String>,
|
||||
messageCollector: MessageCollector,
|
||||
templateClasspath: List<File>,
|
||||
templateClassName: String,
|
||||
scriptArgs: Array<out Any?>? = null,
|
||||
scriptArgsTypes: Array<out Class<out Any>>? = 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<File>,
|
||||
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<ReplCodeLine> {
|
||||
return emptyList() // TODO: not implemented, no current need
|
||||
}
|
||||
|
||||
override val history: List<ReplCodeLine>
|
||||
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<ReplCodeLine>?): 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<File>,
|
||||
templateClassName: String,
|
||||
scriptArgs: Array<out Any?>? = null,
|
||||
scriptArgsTypes: Array<out Class<out Any>>? = 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<EvalHistoryType> = 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<ReplCodeLine>?,
|
||||
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<RemoteReplCompilerState>().replStateFacade.id, codeLine).get()
|
||||
|
||||
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult =
|
||||
compileService.replCompile(sessionId, state.asState<RemoteReplCompilerState>().replStateFacade.id, codeLine).get()
|
||||
}
|
||||
|
||||
+50
@@ -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<Unit>, AbstractList<ReplHistoryRecord<Unit>>() {
|
||||
override val size: Int
|
||||
get() = state.replStateFacade.historySize
|
||||
|
||||
override fun get(index: Int): ReplHistoryRecord<Unit> = 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 resetTo(id: ILineId): Iterable<ILineId> = state.replStateFacade.historyResetTo(id)
|
||||
|
||||
override val lock: ReentrantReadWriteLock get() = state.lock
|
||||
}
|
||||
|
||||
class RemoteReplCompilerState(internal val replStateFacade: ReplStateFacade, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : IReplStageState<Unit> {
|
||||
|
||||
override val history: IReplStageHistory<Unit> = RemoteReplCompilerStateHistory(this)
|
||||
}
|
||||
+35
-6
@@ -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<Int>
|
||||
|
||||
@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<Nothing>
|
||||
|
||||
@Deprecated("The usages should be replaced with `replCheck` method", ReplaceWith("replCheck"))
|
||||
@Throws(RemoteException::class)
|
||||
fun remoteReplLineCheck(
|
||||
sessionId: Int,
|
||||
codeLine: ReplCodeLine
|
||||
): CallResult<ReplCheckResult>
|
||||
|
||||
@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<ReplCodeLine>?
|
||||
): CallResult<ReplCompileResult>
|
||||
|
||||
@Deprecated("Evaluation on daemon is not supported")
|
||||
@Throws(RemoteException::class)
|
||||
fun remoteReplLineEval(
|
||||
sessionId: Int,
|
||||
codeLine: ReplCodeLine,
|
||||
history: List<ReplCodeLine>?
|
||||
): CallResult<ReplEvalResult>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun leaseReplSession(
|
||||
aliveFlagPath: String?,
|
||||
compilerArguments: Array<out String>,
|
||||
compilationOptions: CompilationOptions,
|
||||
servicesFacade: CompilerServicesFacadeBase,
|
||||
templateClasspath: List<File>,
|
||||
templateClassName: String,
|
||||
scriptArgsWithTypes: ScriptArgsWithTypes?
|
||||
): CallResult<Int>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun replCreateState(sessionId: Int): CallResult<ReplStateFacade>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun replCheck(
|
||||
sessionId: Int,
|
||||
replStateId: Int,
|
||||
codeLine: ReplCodeLine
|
||||
): CallResult<ReplCheckResult>
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
fun replCompile(
|
||||
sessionId: Int,
|
||||
replStateId: Int,
|
||||
codeLine: ReplCodeLine
|
||||
): CallResult<ReplCompileResult>
|
||||
}
|
||||
|
||||
+1
@@ -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 <entityName>_ 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)
|
||||
|
||||
+30
@@ -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<ILineId>
|
||||
}
|
||||
@@ -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<Int, ClientOrSessionProxy<Any>> = hashMapOf()
|
||||
private val sessionsIdCounter = AtomicInteger(0)
|
||||
private val internalRng = Random()
|
||||
|
||||
fun<T: Any> leaseSession(session: ClientOrSessionProxy<T>): 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<T: Any> leaseSession(session: ClientOrSessionProxy<T>): 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<ReplCheckResult> =
|
||||
ifAlive(minAliveness = Aliveness.Alive) {
|
||||
withValidRepl(sessionId) {
|
||||
check(codeLine)
|
||||
CompileService.CallResult.Good(check(codeLine))
|
||||
}
|
||||
}
|
||||
|
||||
override fun remoteReplLineCompile(sessionId: Int, codeLine: ReplCodeLine, history: List<ReplCodeLine>?): CompileService.CallResult<ReplCompileResult> =
|
||||
ifAlive(minAliveness = Aliveness.Alive) {
|
||||
withValidRepl(sessionId) {
|
||||
compile(codeLine, history)
|
||||
CompileService.CallResult.Good(compile(codeLine, history))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,10 +487,57 @@ class CompileServiceImpl(
|
||||
): CompileService.CallResult<ReplEvalResult> =
|
||||
ifAlive(minAliveness = Aliveness.Alive) {
|
||||
withValidRepl(sessionId) {
|
||||
compileAndEval(codeLine, verifyHistory = history)
|
||||
CompileService.CallResult.Good(compileAndEval(codeLine, verifyHistory = history))
|
||||
}
|
||||
}
|
||||
|
||||
override fun leaseReplSession(aliveFlagPath: String?,
|
||||
compilerArguments: Array<out String>,
|
||||
compilationOptions: CompilationOptions,
|
||||
servicesFacade: CompilerServicesFacadeBase,
|
||||
templateClasspath: List<File>,
|
||||
templateClassName: String,
|
||||
scriptArgsWithTypes: ScriptArgsWithTypes?
|
||||
): 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 = 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<ReplStateFacade> =
|
||||
ifAlive(minAliveness = Aliveness.Alive) {
|
||||
withValidRepl(sessionId) {
|
||||
CompileService.CallResult.Good(createRemoteState(port))
|
||||
}
|
||||
}
|
||||
|
||||
override fun replCheck(sessionId: Int, replStateId: Int, codeLine: ReplCodeLine): CompileService.CallResult<ReplCheckResult> =
|
||||
ifAlive(minAliveness = Aliveness.Alive) {
|
||||
withValidRepl(sessionId) {
|
||||
withValidReplState(replStateId) { state ->
|
||||
check(state, codeLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun replCompile(sessionId: Int, replStateId: Int, codeLine: ReplCodeLine): CompileService.CallResult<ReplCompileResult> =
|
||||
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<R> withValidRepl(sessionId: Int, body: KotlinJvmReplService.() -> R): CompileService.CallResult<R> =
|
||||
@@ -861,4 +898,12 @@ class CompileServiceImpl(
|
||||
CompileService.CallResult.Good(it.body())
|
||||
} ?: CompileService.CallResult.Error("Not a REPL session $sessionId")
|
||||
}
|
||||
|
||||
@JvmName("withValidRepl1")
|
||||
private inline fun<R> withValidRepl(sessionId: Int, body: KotlinJvmReplService.() -> CompileService.CallResult<R>): CompileService.CallResult<R> =
|
||||
withValidClientOrSessionProxy(sessionId) { session ->
|
||||
(session?.data as? KotlinJvmReplService?)?.let {
|
||||
it.body()
|
||||
} ?: CompileService.CallResult.Error("Not a REPL session $sessionId")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<File>,
|
||||
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<EvalHistoryType> get() = replEvaluator?.lastEvaluatedScripts ?: emptyList()
|
||||
protected val statesLock = ReentrantReadWriteLock()
|
||||
// TODO: consider using values here for session cleanup
|
||||
protected val states = WeakHashMap<RemoteReplStateFacadeServer, Boolean>() // used as (missing) WeakHashSet
|
||||
protected val stateIdCounter = AtomicInteger()
|
||||
@Deprecated("remove after removal state-less check/compile/eval methods")
|
||||
protected val defaultStateFacade: RemoteReplStateFacadeServer by lazy { createRemoteState() }
|
||||
|
||||
override fun 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<ReplCodeLine>?): 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<ReplCodeLine>?, 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<ReplCodeLine>?): ReplCompileResult = compile(defaultStateFacade.state, codeLine)
|
||||
|
||||
@Deprecated("eval is not supported on daemon")
|
||||
fun compileAndEval(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>?): 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<GenericReplCompilerState>(), port)
|
||||
states.put(stateFacade, true)
|
||||
stateFacade
|
||||
}
|
||||
|
||||
fun<R> withValidReplState(stateId: Int, body: (IReplStageState<*>) -> R): CompileService.CallResult<R> = 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
|
||||
}
|
||||
|
||||
@@ -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<ILineId> = state.history.resetTo(id).toList()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user