Skip to content

Commit 4541088

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

File tree

11 files changed

+124
-14
lines changed

11 files changed

+124
-14
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@ package org.jetbrains.kotlinx.jupyter.api
33
import java.lang.reflect.Field
44

55
interface VariableState {
6-
// val property: KProperty<*>
76
val property: Field
87
val scriptInstance: Any?
98
val stringValue: String?
109
val value: Any?
1110
}
1211

1312
data class VariableStateImpl(
14-
// override val property: KProperty1<Any, *>,
1513
override val property: Field,
1614
override val scriptInstance: Any,
1715
) : VariableState {
1816
private var cachedValue: Any? = null
1917

18+
// use of Java 9 required
19+
@SuppressWarnings("DEPRECATION")
2020
fun update() {
2121
val wasAccessible = property.isAccessible
2222
property.isAccessible = true

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/message_types.kt

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

95-
// TODO: add custom commands
96-
// this custom message should be supported on client-side. either JS or Idea Plugin
97-
9895
val type: String
9996
get() = name.lowercase()
10097
}
@@ -580,12 +577,13 @@ class ListErrorsReply(
580577
@Serializable
581578
class SerializationRequest(
582579
val cellId: Int,
583-
val descriptorsState: Map<String, SerializedVariablesState>
580+
val descriptorsState: Map<String, SerializedVariablesState>,
581+
val topLevelDescriptorName: String = ""
584582
) : MessageContent()
585583

586584
@Serializable
587585
class SerializationReply(
588-
val cellId: Int,
586+
val cellId: Int = 1,
589587
val descriptorsState: Map<String, SerializedVariablesState> = emptyMap()
590588
) : MessageContent()
591589

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,21 @@ fun JupyterConnection.Socket.shellMessagesHandler(msg: Message, repl: ReplForJup
305305
is CommInfoRequest -> {
306306
sendWrapped(msg, makeReplyMessage(msg, MessageType.COMM_INFO_REPLY, content = CommInfoReply(mapOf())))
307307
}
308+
is CommOpen -> {
309+
if (!content.commId.equals(MessageType.SERIALIZATION_REQUEST.name, ignoreCase = true)) {
310+
send(makeReplyMessage(msg, MessageType.NONE))
311+
return
312+
}
313+
log.debug("Message type in CommOpen: $msg, ${msg.type}")
314+
val data = content.data ?: return sendWrapped(msg, makeReplyMessage(msg, MessageType.SERIALIZATION_REPLY))
315+
316+
val messageContent = getVariablesDescriptorsFromJson(data)
317+
GlobalScope.launch(Dispatchers.Default) {
318+
repl.serializeVariables(messageContent.topLevelDescriptorName, messageContent.descriptorsState) { result ->
319+
sendWrapped(msg, makeReplyMessage(msg, MessageType.COMM_OPEN, content = result))
320+
}
321+
}
322+
}
308323
is CompleteRequest -> {
309324
GlobalScope.launch(Dispatchers.Default) {
310325
repl.complete(content.code, content.cursorPos) { result ->
@@ -321,8 +336,14 @@ fun JupyterConnection.Socket.shellMessagesHandler(msg: Message, repl: ReplForJup
321336
}
322337
is SerializationRequest -> {
323338
GlobalScope.launch(Dispatchers.Default) {
324-
repl.serializeVariables(content.cellId, content.descriptorsState) { result ->
325-
sendWrapped(msg, makeReplyMessage(msg, MessageType.SERIALIZATION_REPLY, content = result))
339+
if (content.topLevelDescriptorName.isNotEmpty()) {
340+
repl.serializeVariables(content.topLevelDescriptorName, content.descriptorsState) { result ->
341+
sendWrapped(msg, makeReplyMessage(msg, MessageType.SERIALIZATION_REPLY, content = result))
342+
}
343+
} else {
344+
repl.serializeVariables(content.cellId, content.descriptorsState) { result ->
345+
sendWrapped(msg, makeReplyMessage(msg, MessageType.SERIALIZATION_REPLY, content = result))
346+
}
326347
}
327348
}
328349
}

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ interface ReplForJupyter {
120120

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

123+
suspend fun serializeVariables(topLevelVarName: String, descriptorsState: Map<String, SerializedVariablesState>, callback: (SerializationReply) -> Unit)
124+
123125
val homeDir: File?
124126

125127
val currentClasspath: Collection<String>
@@ -520,15 +522,26 @@ class ReplForJupyterImpl(
520522

521523
private val serializationQueue = LockQueue<SerializationReply, SerializationArgs>()
522524
override suspend fun serializeVariables(cellId: Int, descriptorsState: Map<String, SerializedVariablesState>, callback: (SerializationReply) -> Unit) {
523-
doWithLock(SerializationArgs(cellId, descriptorsState, callback), serializationQueue, SerializationReply(cellId, descriptorsState), ::doSerializeVariables)
525+
doWithLock(SerializationArgs(descriptorsState, cellId = cellId, callback = callback), serializationQueue, SerializationReply(cellId, descriptorsState), ::doSerializeVariables)
526+
}
527+
528+
override suspend fun serializeVariables(topLevelVarName: String, descriptorsState: Map<String, SerializedVariablesState>, callback: (SerializationReply) -> Unit) {
529+
doWithLock(SerializationArgs(descriptorsState, topLevelVarName = topLevelVarName, callback = callback), serializationQueue, SerializationReply(), ::doSerializeVariables)
524530
}
525531

526532
private fun doSerializeVariables(args: SerializationArgs): SerializationReply {
527533
val resultMap = mutableMapOf<String, SerializedVariablesState>()
534+
val cellId = if (args.cellId != -1) args.cellId else {
535+
val watcherInfo = internalEvaluator.findVariableCell(args.topLevelVarName) + 1
536+
val finalAns = if (watcherInfo == - 1) 1 else watcherInfo
537+
finalAns
538+
}
528539
args.descriptorsState.forEach { (name, state) ->
529-
resultMap[name] = variablesSerializer.doIncrementalSerialization(args.cellId - 1, name, state)
540+
resultMap[name] = variablesSerializer.doIncrementalSerialization(cellId - 1, name, state)
530541
}
531-
return SerializationReply(args.cellId, resultMap)
542+
log.debug("Serialization cellID: $cellId")
543+
log.debug("Serialization answer: ${resultMap.entries.first().value.fieldDescriptor}")
544+
return SerializationReply(cellId, resultMap)
532545
}
533546

534547

@@ -565,8 +578,9 @@ class ReplForJupyterImpl(
565578
LockQueueArgs<ListErrorsResult>
566579

567580
private data class SerializationArgs(
568-
val cellId: Int,
569581
val descriptorsState: Map<String, SerializedVariablesState>,
582+
var cellId: Int = -1,
583+
val topLevelVarName: String = "",
570584
override val callback: (SerializationReply) -> Unit
571585
) : LockQueueArgs<SerializationReply>
572586

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,9 @@ interface InternalEvaluator {
3030
* returns empty data or null
3131
*/
3232
fun popAddedCompiledScripts(): SerializedCompiledScriptsData = SerializedCompiledScriptsData.EMPTY
33+
34+
/**
35+
* Get a cellId where particular variable is declared
36+
*/
37+
fun findVariableCell(variableName: String): Int
3338
}

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
@@ -48,6 +48,10 @@ internal class InternalEvaluatorImpl(
4848
return SerializedCompiledScriptsData(scripts)
4949
}
5050

51+
override fun findVariableCell(variableName: String): Int {
52+
return variablesWatcher.findDeclarationAddress(variableName) ?: -1
53+
}
54+
5155
override var writeCompiledClasses: Boolean
5256
get() = classWriter != null
5357
set(value) {

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

Lines changed: 23 additions & 0 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,
@@ -375,6 +391,7 @@ class VariablesSerializer(private val serializationDepth: Int = 2, private val s
375391
* Really wanted to use contracts here, but all usages should be provided with this annotation and,
376392
* perhaps, it may be a big overhead
377393
*/
394+
@OptIn(ExperimentalContracts::class)
378395
private fun iterateThrough(
379396
elem: Any,
380397
seenObjectsPerCell: MutableMap<RuntimeObjectWrapper, SerializedVariablesState>?,
@@ -383,6 +400,10 @@ class VariablesSerializer(private val serializationDepth: Int = 2, private val s
383400
instancesPerState: MutableMap<SerializedVariablesState, Any?>,
384401
callInstance: Any
385402
) {
403+
contract {
404+
returns() implies (elem is Field || elem is KProperty1<*, *>)
405+
}
406+
386407
val name = if (elem is Field) elem.name else (elem as KProperty1<Any, *>).name
387408
val value = if (elem is Field) tryGetValueFromProperty(elem, callInstance).toObjectWrapper()
388409
else {
@@ -491,6 +512,8 @@ class VariablesSerializer(private val serializationDepth: Int = 2, private val s
491512
return value
492513
}
493514

515+
// use of Java 9 required
516+
@SuppressWarnings("DEPRECATION")
494517
private fun tryGetValueFromProperty(property: Field, callInstance: Any): Any? {
495518
// some fields may be optimized out like array size. Thus, calling it.isAccessible would return error
496519
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
@@ -73,7 +73,7 @@ fun ResultsRenderersProcessor.registerDefaultRenderers() {
7373
* Stores info about where a variable Y was declared and info about what are they at the address X.
7474
* K: key, stands for a way of addressing variables, e.g. address.
7575
* V: value, from Variable, choose any suitable type for your variable reference.
76-
* Default: T=Int, V=String
76+
* Default: K=Int, V=String
7777
*/
7878
class VariablesUsagesPerCellWatcher<K : Any, V : Any> {
7979
val cellVariables = mutableMapOf<K, MutableSet<V>>()
@@ -106,5 +106,7 @@ class VariablesUsagesPerCellWatcher<K : Any, V : Any> {
106106
}
107107
}
108108

109+
fun findDeclarationAddress(variableRef: V) = variablesDeclarationInfo[variableRef]
110+
109111
fun ensureStorageCreation(address: K) = cellVariables.putIfAbsent(address, mutableSetOf())
110112
}

src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/ReplTests.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,9 @@ class ReplVarsSerializationTest : AbstractSingleReplTest() {
805805
assertEquals(2, actualContainer.fieldDescriptor.size)
806806
assertTrue(actualContainer.isContainer)
807807
assertEquals(listOf(1, 2, 3, 4).toString().substring(1, actualContainer.value!!.length + 1), actualContainer.value)
808+
809+
val serializer = repl.variablesSerializer
810+
val newData = serializer.doIncrementalSerialization(0, "data", actualContainer)
808811
}
809812

810813
@Test
@@ -904,6 +907,9 @@ class ReplVarsSerializationTest : AbstractSingleReplTest() {
904907
assertTrue(state.isContainer)
905908
assertEquals("${values++}", state.value)
906909
}
910+
911+
val depthMostNode = actualContainer.fieldDescriptor.entries.first { it.value!!.isContainer }
912+
val serializationAns = serializer.doIncrementalSerialization(0, depthMostNode.key, depthMostNode.value!!)
907913
}
908914

909915
@Test
@@ -982,5 +988,27 @@ class ReplVarsSerializationTest : AbstractSingleReplTest() {
982988
}
983989
}
984990
}
991+
992+
runBlocking {
993+
repl.serializeVariables("x", mapOf(propertyName to actualContainer)) { result ->
994+
val data = result.descriptorsState
995+
assertTrue(data.isNotEmpty())
996+
997+
val innerList = data.entries.last().value
998+
assertTrue(innerList.isContainer)
999+
var receivedDescriptor = innerList.fieldDescriptor
1000+
assertEquals(2, receivedDescriptor.size)
1001+
receivedDescriptor = receivedDescriptor.entries.last().value!!.fieldDescriptor
1002+
1003+
assertEquals(4, receivedDescriptor.size)
1004+
var values = 1
1005+
receivedDescriptor.forEach { (_, state) ->
1006+
val fieldDescriptor = state!!.fieldDescriptor
1007+
assertEquals(0, fieldDescriptor.size)
1008+
assertTrue(state.isContainer)
1009+
assertEquals("${values++}", state.value)
1010+
}
1011+
}
1012+
}
9851013
}
9861014
}

0 commit comments

Comments
 (0)