akkadotnet / Hyperion

Polymorphic serialization for .NET
Apache License 2.0
277 stars 62 forks source link

Deserializing a message with two ActorPaths to the same actor throws an invalid cast exception #263

Closed jaydeboer closed 3 years ago

jaydeboer commented 3 years ago

Version Information Akka.NET version 1.4.26 running on net5.0 Which Akka.NET Modules? Akka.Persistence Akka.Serialization.Hyperion

Describe the bug When I have a message that contains a list of ActorPaths, deserialization with Hyperion fails if the same actor path is in the list multiple times. If there are unique actor paths, everything works as expected.

To Reproduce Here are 2 xUnit tests, the one with a duplicate ActorPath will cause the exception and the object will never be deserialized, the other, with different actor paths, will deserialize properly and will pass.

using Akka.Actor;
using FluentAssertions;
using System.Collections.Generic;
using Xunit;

namespace TE.ActorSystem.Core.UnitTests
{
    public class ReproForAkka : Akka.TestKit.Xunit2.TestKit
    {
        public ReproForAkka()
            : base(@"
        akka.actor {
            serializers { hyperion = ""Akka.Serialization.HyperionSerializer, Akka.Serialization.Hyperion"" }
            serialization-bindings { ""System.Object"" = hyperion }
        }")
        { }

        public class ContainerClass
        {
            public List<ActorPath> Destinations { get; init; }
        }

        [Fact]
        public void MakeItBlowUp()
        {
            DoIt(new ContainerClass { Destinations = new List<ActorPath> { TestActor.Path, TestActor.Path } });
        }

        [Fact]
        public void ThisOnePasses()
        {
            DoIt(new ContainerClass { Destinations = new List<ActorPath> { TestActor.Path, CreateTestProbe().TestActor.Path } });
        }

        private void DoIt(ContainerClass src)
        {
            var serialization = Sys.Serialization;
            var serializer = serialization.FindSerializerFor(src);
            var actual = serializer.FromBinary<ContainerClass>(serializer.ToBinary(src));

            actual.Should().BeEquivalentTo(src);

        }
    }
}

Expected behavior I expect that a message could contain multiple copies of the same ActorPath and be deserialized properly without the exception being thrown.

Actual behavior An InvalidCastException is thrown.

Environment Windows 10 Net 5.0

Arkatufus commented 3 years ago

The problem stems that when preserve-object-references is set to true, DeserializeSession stores the deserialized reference to ActorPath.Surrogate instead of the final ActorPath instance. Exception is then thrown when the deserializer tries to add the surrogate into the calling List<ActorPath> deserializer

Aaronontheweb commented 3 years ago

Nice work @Arkatufus - I'll review your PR.

jaydeboer commented 3 years ago

Thanks for the quick work guys!