From 870e3bc1fe87ecf2f22444be8faaaf8e93bb9d40 Mon Sep 17 00:00:00 2001 From: "Ilya.Usov" Date: Tue, 30 Jan 2024 17:30:57 +0100 Subject: [PATCH] Fix `Already has RdId` assertion --- .../jetbrains/rd/framework/impl/RdProperty.kt | 3 ++ .../framework/test/cases/RdCollectionsTest.kt | 25 ++++++++++++++++ rd-net/RdFramework/Impl/RdProperty.cs | 4 +++ rd-net/Test.RdFramework/RdCollectionsTest.cs | 30 +++++++++++++++++++ 4 files changed, 62 insertions(+) diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdProperty.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdProperty.kt index ce1b517fc..4cf6cde68 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdProperty.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdProperty.kt @@ -77,6 +77,9 @@ abstract class RdPropertyBase(val valueSerializer: ISerializer) : RdReacti return@advise if (!optimizeNested && shouldIdentify) { + // We need to terminate the current lifetime to unbind the existing value before assigning a new value, especially in cases where we are reassigning it. + bindDefinition.get()?.terminate() + v.identifyPolymorphic(proto.identity, proto.identity.next(rdid)) val prevDefinition = bindDefinition.getAndSet(tryPreBindValue(lifetime, v, false)) diff --git a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/RdCollectionsTest.kt b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/RdCollectionsTest.kt index d5f721181..39dfef3f4 100644 --- a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/RdCollectionsTest.kt +++ b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/RdCollectionsTest.kt @@ -532,6 +532,31 @@ class RdCollectionsTest : RdFrameworkTestBase() { assertTrue(serverAsyncSet!!.contains(123)) } + @Test + fun reassignBindableValue() { + serverWire.autoFlush = false + clientWire.autoFlush = false + + val serverTopLevelProperty = RdProperty?>?>(null) + val clientTopLevelProperty = RdProperty?>?>(null) + + serverProtocol.bindStatic(serverTopLevelProperty, 1) + clientProtocol.bindStatic(clientTopLevelProperty, 1) + + val clientNested1 = RdProperty?>(null) + val clientNested2 = RdProperty?>(null) + val clientSet = RdSet() + clientNested1.value = clientSet + clientNested2.value = clientSet + + setSchedulerActive(SchedulerKind.Client) { + clientTopLevelProperty.value = clientNested1 + pumpAllProtocols(true) + clientTopLevelProperty.value = clientNested2 + pumpAllProtocols(true) + } + } + enum class SchedulerKind { Client, diff --git a/rd-net/RdFramework/Impl/RdProperty.cs b/rd-net/RdFramework/Impl/RdProperty.cs index d3ac9226c..824a03d44 100644 --- a/rd-net/RdFramework/Impl/RdProperty.cs +++ b/rd-net/RdFramework/Impl/RdProperty.cs @@ -9,6 +9,7 @@ using JetBrains.Rd.Util; using JetBrains.Serialization; using JetBrains.Annotations; +using JetBrains.Util.Internal; namespace JetBrains.Rd.Impl { @@ -144,6 +145,9 @@ protected override void Init(Lifetime lifetime, IProtocol proto, SerializationCt if (!OptimizeNested && shouldIdentify) { + // We need to terminate the current lifetime to unbind the existing value before assigning a new value, especially in cases where we are reassigning it. + Memory.VolatileRead(ref myBindDefinition)?.Terminate(); + v.IdentifyPolymorphic(proto.Identities, proto.Identities.Next(RdId)); var prevDefinition = Interlocked.Exchange(ref myBindDefinition, TryPreBindValue(lifetime, v, false)); diff --git a/rd-net/Test.RdFramework/RdCollectionsTest.cs b/rd-net/Test.RdFramework/RdCollectionsTest.cs index 9f5463332..522c93de9 100644 --- a/rd-net/Test.RdFramework/RdCollectionsTest.cs +++ b/rd-net/Test.RdFramework/RdCollectionsTest.cs @@ -440,6 +440,36 @@ public void ChangeCollectionsTest() Assert.AreEqual(BindState.NotBound,serverSet.BindState); } + [Test] + public void ReassignBindableValue() + { + ClientWire.AutoTransmitMode = false; + ServerWire.AutoTransmitMode = false; + + var serverTopLevelProperty = BindToServer(LifetimeDefinition.Lifetime, + NewRdProperty>>(), ourKey); + serverTopLevelProperty.ValueCanBeNull = true; + var clientTopLevelProperty = BindToClient(LifetimeDefinition.Lifetime, + NewRdProperty>>(), ourKey); + clientTopLevelProperty.ValueCanBeNull = true; + + var clientNested1 = NewRdProperty>(); + var clientNested2 = NewRdProperty>(); + var clientSet = NewRdSet(); + clientNested1.Value = clientSet; + + clientNested2.Value = clientSet; + + SetSchedulerActive(SchedulerKind.Client, () => + { + clientTopLevelProperty.Value = clientNested1; + PumpAllProtocols(true); + clientTopLevelProperty.Value = clientNested2; + PumpAllProtocols(true); + }); + + } + [Test] public void PropertyTest() {