Skip to content

Commit e5fac02

Browse files
Add possibility to use serialization request with only top-level descriptor name, not cellID
1 parent e9aefaa commit e5fac02

File tree

12 files changed

+159
-17
lines changed

12 files changed

+159
-17
lines changed

jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/VariableState.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@ import kotlin.reflect.jvm.isAccessible
55
import java.lang.reflect.Field
66

77
interface VariableState {
8-
// val property: KProperty<*>
98
val property: Field
109
val scriptInstance: Any?
1110
val stringValue: String?
1211
val value: Result<Any?>
1312
}
1413

1514
data class VariableStateImpl(
16-
// override val property: KProperty1<Any, *>,
1715
override val property: Field,
1816
override val scriptInstance: Any,
1917
) : VariableState {

jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/compiler/util/serializedCompiledScript.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ data class SerializedVariablesState(
3030
val fieldDescriptor: MutableMap<String, SerializedVariablesState?> = mutableMapOf()
3131
}
3232

33+
@Serializable
34+
class SerializationReply(
35+
val cellId: Int = 1,
36+
val descriptorsState: Map<String, SerializedVariablesState> = emptyMap()
37+
)
38+
3339
@Serializable
3440
class EvaluatedSnippetMetadata(
3541
val newClasspath: Classpath = emptyList(),

src/main/kotlin/org/jetbrains/kotlinx/jupyter/apiImpl.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ class NotebookImpl(
133133

134134
private val history = arrayListOf<CodeCellImpl>()
135135
private var mainCellCreated = false
136+
private val unchangedVariables: MutableSet<String> = mutableSetOf()
136137

137138
val displays = DisplayContainerImpl()
138139

@@ -149,6 +150,19 @@ class NotebookImpl(
149150
override val jreInfo: JREInfoProvider
150151
get() = JavaRuntime
151152

153+
fun updateVariablesState(evaluator: InternalEvaluator) {
154+
variablesState += evaluator.variablesHolder
155+
currentCellVariables = evaluator.cellVariables
156+
unchangedVariables.clear()
157+
unchangedVariables.addAll(evaluator.getUnchangedVariables())
158+
}
159+
160+
fun updateVariablesState(varsStateUpdate: Map<String, VariableState>) {
161+
variablesState += varsStateUpdate
162+
}
163+
164+
fun unchangedVariables(): Set<String> = unchangedVariables
165+
152166
fun variablesReportAsHTML(): String {
153167
return generateHTMLVarsReport(variablesState)
154168
}

src/main/kotlin/org/jetbrains/kotlinx/jupyter/message_types.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,6 @@ enum class MessageType(val contentClass: KClass<out MessageContent>) {
9393
SERIALIZATION_REQUEST(SerializationRequest::class),
9494
SERIALIZATION_REPLY(SerializationReply::class);
9595

96-
// TODO: add custom commands
97-
// this custom message should be supported on client-side. either JS or Idea Plugin
98-
9996
val type: String
10097
get() = name.lowercase()
10198
}
@@ -562,12 +559,13 @@ class ListErrorsReply(
562559
@Serializable
563560
class SerializationRequest(
564561
val cellId: Int,
565-
val descriptorsState: Map<String, SerializedVariablesState>
562+
val descriptorsState: Map<String, SerializedVariablesState>,
563+
val topLevelDescriptorName: String = ""
566564
) : MessageContent()
567565

568566
@Serializable
569567
class SerializationReply(
570-
val cellId: Int,
568+
val cellId: Int = 1,
571569
val descriptorsState: Map<String, SerializedVariablesState> = emptyMap()
572570
) : MessageContent()
573571

src/main/kotlin/org/jetbrains/kotlinx/jupyter/protocol.kt

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,21 @@ fun JupyterConnection.Socket.shellMessagesHandler(msg: Message, repl: ReplForJup
307307
is CommInfoRequest -> {
308308
sendWrapped(msg, makeReplyMessage(msg, MessageType.COMM_INFO_REPLY, content = CommInfoReply(mapOf())))
309309
}
310+
is CommOpen -> {
311+
if (!content.commId.equals(MessageType.SERIALIZATION_REQUEST.name, ignoreCase = true)) {
312+
send(makeReplyMessage(msg, MessageType.NONE))
313+
return
314+
}
315+
log.debug("Message type in CommOpen: $msg, ${msg.type}")
316+
val data = content.data ?: return sendWrapped(msg, makeReplyMessage(msg, MessageType.SERIALIZATION_REPLY))
317+
318+
val messageContent = getVariablesDescriptorsFromJson(data)
319+
GlobalScope.launch(Dispatchers.Default) {
320+
repl.serializeVariables(messageContent.topLevelDescriptorName, messageContent.descriptorsState) { result ->
321+
sendWrapped(msg, makeReplyMessage(msg, MessageType.COMM_OPEN, content = result))
322+
}
323+
}
324+
}
310325
is CompleteRequest -> {
311326
connection.launchJob {
312327
repl.complete(content.code, content.cursorPos) { result ->
@@ -323,8 +338,14 @@ fun JupyterConnection.Socket.shellMessagesHandler(msg: Message, repl: ReplForJup
323338
}
324339
is SerializationRequest -> {
325340
GlobalScope.launch(Dispatchers.Default) {
326-
repl.serializeVariables(content.cellId, content.descriptorsState) { result ->
327-
sendWrapped(msg, makeReplyMessage(msg, MessageType.SERIALIZATION_REPLY, content = result))
341+
if (content.topLevelDescriptorName.isNotEmpty()) {
342+
repl.serializeVariables(content.topLevelDescriptorName, content.descriptorsState) { result ->
343+
sendWrapped(msg, makeReplyMessage(msg, MessageType.SERIALIZATION_REPLY, content = result))
344+
}
345+
} else {
346+
repl.serializeVariables(content.cellId, content.descriptorsState) { result ->
347+
sendWrapped(msg, makeReplyMessage(msg, MessageType.SERIALIZATION_REPLY, content = result))
348+
}
328349
}
329350
}
330351
}

src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl.kt

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ interface ReplForJupyter {
139139

140140
suspend fun serializeVariables(cellId: Int, descriptorsState: Map<String, SerializedVariablesState>, callback: (SerializationReply) -> Unit)
141141

142+
suspend fun serializeVariables(topLevelVarName: String, descriptorsState: Map<String, SerializedVariablesState>, callback: (SerializationReply) -> Unit)
143+
142144
val homeDir: File?
143145

144146
val currentClasspath: Collection<String>
@@ -562,15 +564,26 @@ class ReplForJupyterImpl(
562564

563565
private val serializationQueue = LockQueue<SerializationReply, SerializationArgs>()
564566
override suspend fun serializeVariables(cellId: Int, descriptorsState: Map<String, SerializedVariablesState>, callback: (SerializationReply) -> Unit) {
565-
doWithLock(SerializationArgs(cellId, descriptorsState, callback), serializationQueue, SerializationReply(cellId, descriptorsState), ::doSerializeVariables)
567+
doWithLock(SerializationArgs(descriptorsState, cellId = cellId, callback = callback), serializationQueue, SerializationReply(cellId, descriptorsState), ::doSerializeVariables)
568+
}
569+
570+
override suspend fun serializeVariables(topLevelVarName: String, descriptorsState: Map<String, SerializedVariablesState>, callback: (SerializationReply) -> Unit) {
571+
doWithLock(SerializationArgs(descriptorsState, topLevelVarName = topLevelVarName, callback = callback), serializationQueue, SerializationReply(), ::doSerializeVariables)
566572
}
567573

568574
private fun doSerializeVariables(args: SerializationArgs): SerializationReply {
569575
val resultMap = mutableMapOf<String, SerializedVariablesState>()
576+
val cellId = if (args.cellId != -1) args.cellId else {
577+
val watcherInfo = internalEvaluator.findVariableCell(args.topLevelVarName) + 1
578+
val finalAns = if (watcherInfo == - 1) 1 else watcherInfo
579+
finalAns
580+
}
570581
args.descriptorsState.forEach { (name, state) ->
571-
resultMap[name] = variablesSerializer.doIncrementalSerialization(args.cellId - 1, name, state)
582+
resultMap[name] = variablesSerializer.doIncrementalSerialization(cellId - 1, name, state)
572583
}
573-
return SerializationReply(args.cellId, resultMap)
584+
log.debug("Serialization cellID: $cellId")
585+
log.debug("Serialization answer: ${resultMap.entries.first().value.fieldDescriptor}")
586+
return SerializationReply(cellId, resultMap)
574587
}
575588

576589

@@ -607,11 +620,13 @@ class ReplForJupyterImpl(
607620
LockQueueArgs<ListErrorsResult>
608621

609622
private data class SerializationArgs(
610-
val cellId: Int,
611623
val descriptorsState: Map<String, SerializedVariablesState>,
624+
var cellId: Int = -1,
625+
val topLevelVarName: String = "",
612626
override val callback: (SerializationReply) -> Unit
613627
) : LockQueueArgs<SerializationReply>
614628

629+
615630
@JvmInline
616631
private value class LockQueue<T, Args : LockQueueArgs<T>>(
617632
private val args: AtomicReference<Args?> = AtomicReference()

src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/InternalEvaluator.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,14 @@ interface InternalEvaluator {
3030
* returns empty data or null
3131
*/
3232
fun popAddedCompiledScripts(): SerializedCompiledScriptsData = SerializedCompiledScriptsData.EMPTY
33+
34+
/**
35+
* Get a cellId where a particular variable is declared
36+
*/
37+
fun findVariableCell(variableName: String): Int
38+
39+
/**
40+
* Returns a set of unaffected variables after execution
41+
*/
42+
fun getUnchangedVariables(): Set<String>
3343
}

src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/InternalEvaluatorImpl.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ internal class InternalEvaluatorImpl(
5252
return SerializedCompiledScriptsData(scripts)
5353
}
5454

55+
override fun findVariableCell(variableName: String): Int {
56+
return variablesWatcher.findDeclarationAddress(variableName) ?: -1
57+
}
58+
5559
override var writeCompiledClasses: Boolean
5660
get() = classWriter != null
5761
set(value) {

src/main/kotlin/org/jetbrains/kotlinx/jupyter/serializationUtils.kt

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package org.jetbrains.kotlinx.jupyter
22

3+
import kotlinx.serialization.Serializable
4+
import kotlinx.serialization.json.Json
5+
import kotlinx.serialization.json.JsonObject
6+
import kotlinx.serialization.json.decodeFromJsonElement
37
import org.jetbrains.kotlinx.jupyter.api.VariableState
48
import org.jetbrains.kotlinx.jupyter.compiler.util.SerializedVariablesState
59
import java.lang.reflect.Field
10+
import kotlin.contracts.ExperimentalContracts
11+
import kotlin.contracts.contract
612
import kotlin.reflect.KClass
713
import kotlin.reflect.KProperty
814
import kotlin.reflect.KProperty1
@@ -23,6 +29,16 @@ enum class PropertiesType {
2329
MIXED
2430
}
2531

32+
@Serializable
33+
data class SerializedCommMessageContent(
34+
val topLevelDescriptorName: String,
35+
val descriptorsState: Map<String, SerializedVariablesState>
36+
)
37+
38+
fun getVariablesDescriptorsFromJson(json: JsonObject): SerializedCommMessageContent {
39+
return Json.decodeFromJsonElement<SerializedCommMessageContent>(json)
40+
}
41+
2642
class ProcessedSerializedVarsState(
2743
val serializedVariablesState: SerializedVariablesState,
2844
val propertiesData: PropertiesData? = null,
@@ -94,9 +110,11 @@ class VariablesSerializer(private val serializationDepth: Int = 2, private val s
94110
} == true
95111
}
96112

97-
val kProperties = if (value != null) value::class.declaredMemberProperties else {
98-
null
99-
}
113+
val kProperties = try {
114+
if (value != null) value::class.declaredMemberProperties else {
115+
null
116+
}
117+
} catch (ex: Exception) {null}
100118
val serializedVersion = SerializedVariablesState(simpleTypeName, getProperString(value), true)
101119
val descriptors = serializedVersion.fieldDescriptor
102120
if (isDescriptorsNeeded) {
@@ -198,6 +216,11 @@ class VariablesSerializer(private val serializationDepth: Int = 2, private val s
198216

199217
private val isSerializationActive: Boolean = System.getProperty(serializationSystemProperty)?.toBooleanStrictOrNull() ?: true
200218

219+
/**
220+
* Cache for not recomputing unchanged variables
221+
*/
222+
val serializedVariablesCache: MutableMap<String, SerializedVariablesState> = mutableMapOf()
223+
201224
fun serializeVariables(cellId: Int, variablesState: Map<String, VariableState>): Map<String, SerializedVariablesState> {
202225
if (!isSerializationActive) return emptyMap()
203226

@@ -375,6 +398,7 @@ class VariablesSerializer(private val serializationDepth: Int = 2, private val s
375398
* Really wanted to use contracts here, but all usages should be provided with this annotation and,
376399
* perhaps, it may be a big overhead
377400
*/
401+
@OptIn(ExperimentalContracts::class)
378402
private fun iterateThrough(
379403
elem: Any,
380404
seenObjectsPerCell: MutableMap<RuntimeObjectWrapper, SerializedVariablesState>?,
@@ -383,6 +407,10 @@ class VariablesSerializer(private val serializationDepth: Int = 2, private val s
383407
instancesPerState: MutableMap<SerializedVariablesState, Any?>,
384408
callInstance: Any
385409
) {
410+
contract {
411+
returns() implies (elem is Field || elem is KProperty1<*, *>)
412+
}
413+
386414
val name = if (elem is Field) elem.name else (elem as KProperty1<Any, *>).name
387415
val value = if (elem is Field) tryGetValueFromProperty(elem, callInstance).toObjectWrapper()
388416
else {
@@ -491,6 +519,8 @@ class VariablesSerializer(private val serializationDepth: Int = 2, private val s
491519
return value
492520
}
493521

522+
// use of Java 9 required
523+
@SuppressWarnings("DEPRECATION")
494524
private fun tryGetValueFromProperty(property: Field, callInstance: Any): Any? {
495525
// some fields may be optimized out like array size. Thus, calling it.isAccessible would return error
496526
val canAccess = try {

src/main/kotlin/org/jetbrains/kotlinx/jupyter/util.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ fun ResultsRenderersProcessor.registerDefaultRenderers() {
8181
* Stores info about where a variable Y was declared and info about what are they at the address X.
8282
* K: key, stands for a way of addressing variables, e.g. address.
8383
* V: value, from Variable, choose any suitable type for your variable reference.
84-
* Default: T=Int, V=String
84+
* Default: K=Int, V=String
8585
*/
8686
class VariablesUsagesPerCellWatcher<K : Any, V : Any> {
8787
val cellVariables = mutableMapOf<K, MutableSet<V>>()
@@ -114,6 +114,8 @@ class VariablesUsagesPerCellWatcher<K : Any, V : Any> {
114114
}
115115
}
116116

117+
fun findDeclarationAddress(variableRef: V) = variablesDeclarationInfo[variableRef]
118+
117119
fun ensureStorageCreation(address: K) = cellVariables.putIfAbsent(address, mutableSetOf())
118120
}
119121

0 commit comments

Comments
 (0)