Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System messages containing BCL exceptions fail to deserialize on netfx when serialized on net5+/.net core #7215

Closed
BrettJaner opened this issue May 31, 2024 · 5 comments · Fixed by #7222

Comments

@BrettJaner
Copy link

BrettJaner commented May 31, 2024

Version Information
Akka.Remote v1.5.20

Describe the bug
I'm currently running a mixed net8.0/net481 Akka cluster. All the following proto messages contain an ExceptionData field:

  • Akka.Remote.Serialization.Proto.Msg.StatusFailure
  • Akka.Remote.Serialization.Proto.Msg.RecreateData
  • Akka.Remote.Serialization.Proto.Msg.ResumeData
  • Akka.Remote.Serialization.Proto.Msg.FailedData

When one of the above messages containing a BCL exception is serialized on a net8.0 node, sent, and then deserialized on a net481 node, I get the following deserialization exception.

Value cannot be null. Parameter name: type
at System.Runtime.Serialization.SerializationInfo..ctor(Type type, IFormatterConverter converter, Boolean requireSameTokenInPartialTrust)
at Akka.Remote.Serialization.ExceptionSupport.ExceptionFromProtoNet(ExceptionData proto)
at Akka.Remote.Serialization.SystemMessageSerializer.RecreateFromProto(Byte[] bytes)
at Akka.Serialization.Serialization.Deserialize(Byte[] bytes, Int32 serializerId, String manifest)
at Akka.Remote.MessageSerializer.Deserialize(ExtendedActorSystem system, Payload messageProtocol)
at Akka.Remote.DefaultMessageDispatcher.Dispatch(IInternalActorRef recipient, Address recipientAddress, Payload message, IActorRef senderOption)
at Akka.Remote.EndpointReader.b__16_0(Message msg)
at Akka.Util.Internal.Extensions.ForEach[T](IEnumerable1 source, Action1 action)
at Akka.Remote.EndpointReader.b__11_0(InboundPayload inbound)
at lambda_method(Closure , Object , Action1 , Action1 , Action1 ) at Akka.Tools.MatchHandler.PartialHandlerArgumentsCapture4.Handle(T value)
at Akka.Actor.ReceiveActor.ExecutePartialMessageHandler(Object message, PartialAction`1 partialAction)
at Akka.Actor.UntypedActor.Receive(Object message)
at Akka.Actor.ActorBase.AroundReceive(Receive receive, Object message)
at Akka.Actor.ActorCell.ReceiveMessage(Object message)
at Akka.Actor.ActorCell.Invoke(Envelope envelope)]

The issue is because in netfx BCL exceptions are defined in the mscorlib assembly, and in net5+/net core they are defined in the System.Private.Corlib assembly. The issue can be resolved if we are a bit smarter about how we define the proto message ExceptionData.TypeName field for these serialized payloads. If we are going to use the type name as the manifest, we should respect the TypeForwardedFromAttribute if one exists. This is exactly what the binary formatter does (see BinaryFormatter.GetTypeInformation()).

Additional details and info can also be found in the discussion: #7193

To Reproduce
https://github.com/BrettJaner/akka-exception-serialization-issue

Expected behavior
No deserialization exception on the netfx nodes.

Actual behavior
Deserialization exceptions on the netfx nodes.

Environment
Windows

Potential Fix
Update the following extension method from

namespace Akka.Util
{
    public static class TypeExtensions
    {
        public static string TypeQualifiedName(this Type type)
        {
            if (ShortenedTypeNames.TryGetValue(type, out var shortened))
            {
                return shortened;
            }

            shortened = cleanAssemblyVersionRegex.Replace(type.AssemblyQualifiedName, string.Empty);
            ShortenedTypeNames.TryAdd(type, shortened);
            
            return shortened;
        }
    }
}

To

namespace Akka.Util
{
    public static class TypeExtensions
    {
        public static string TypeQualifiedName(this Type type)
        {
            if (ShortenedTypeNames.TryGetValue(type, out var shortened))
            {
                return shortened;
            }

            // respect type forwarded from attribute since types can move assemblies between .net versions
            var typeForwardedFromAttribute = type.GetCustomAttribute<TypeForwardedFromAttribute>();

            if (typeForwardedFromAttribute != null)
            {
                var assemblyName = cleanAssemblyVersionRegex.Replace(typeForwardedFromAttribute.AssemblyFullName, string.Empty);

                shortened = $"{type.FullName}, {assemblyName}";
            }
            else
            {
                shortened = cleanAssemblyVersionRegex.Replace(type.AssemblyQualifiedName, string.Empty);
            }

            ShortenedTypeNames.TryAdd(type, shortened);
            
            return shortened;
        }
    }
}
@Aaronontheweb
Copy link
Member

Thanks for filing this issue @BrettJaner - we'll see what we can do to fix it here. Probably involves changing how we instantiate Exception types in one of the two platforms.

@Aaronontheweb
Copy link
Member

The potential fix looks like a reasonable suggestion as well - thank you.

@Aaronontheweb
Copy link
Member

Resolved via #7222

@BrettJaner
Copy link
Author

Thank you @Aaronontheweb & @Arkatufus for resolving this so quickly!

@Aaronontheweb
Copy link
Member

@BrettJaner no problem - this should be available in Akka.NET v1.5.22 on NuGet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants