akkadotnet / akka.net

Canonical actor model implementation for .NET with local + distributed actors in C# and F#.
http://getakka.net
Other
4.69k stars 1.04k forks source link

Message with F# record with field named "ÅrsakNr" fails #3934

Open BentTranberg opened 5 years ago

BentTranberg commented 5 years ago

Akka, Akka.FSharp, Akka.Remote and Akka.Serialization.Hyperion, versions 1.3.13 through 1.4.0-beta2.

Message sent between two .NET Framework 4.6.2 applications. Message contains an F# record with a list of records of type Stopptid.

    type StopptidId = StopptidId of int
    type StoppKilde = ServerStopp | Inaktivitet
    type StoppÅrsakNr = StoppÅrsakNr of int

    type Stopptid = {
        Id: StopptidId
        StoppKilde: StoppKilde
        Start: DateTime
        Stopp: DateTime option
        Kvittert: DateTime option
        ÅrsakNr: StoppÅrsakNr option }

The type Stopptid has a field named ÅrsakNr. It appears the letter "Å" is causing trouble for the streaming of the message. If that field is renamed to AarsakNr (we use double a when å is not available), then there is no error in the streaming. Also, if the list of Stopptid is empty, there is also no error even if the name of the field is still ÅrsakNr with an initial Å.

This is the error message:

[ERROR][25.09.2019 08:54:32][Thread 0016][[akka://client/system/endpointManager/reliableEndpointWriter-akka.tcp%3A%2F%2Ftre%40127.0.0.1%3A29210-1/endpointWriter#694463264]] AssociationError [akka.tcp://client@localhost:1470] -> akka.tcp://tre@127.0.0.1:29210: Error [Unable to cast object of type 'StopptidId' to type 'Microsoft.FSharp.Core.FSharpOption`1[System.DateTime]'.] [   ved Akka.Remote.EndpointReader.<Reading>b__11_1(InboundPayload inbound)
   ved lambda_method(Closure , Object , Action`1 , Action`1 , Action`1 )
   ved Akka.Tools.MatchHandler.PartialHandlerArgumentsCapture`4.Handle(T value)
   ved Akka.Actor.ReceiveActor.ExecutePartialMessageHandler(Object message, PartialAction`1 partialAction)
   ved Akka.Actor.UntypedActor.Receive(Object message)
   ved Akka.Actor.ActorBase.AroundReceive(Receive receive, Object message)
   ved Akka.Actor.ActorCell.ReceiveMessage(Object message)
   ved Akka.Actor.ActorCell.Invoke(Envelope envelope)]
Cause: Unknown

It says "Unable to cast object of type 'StopptidId' to type 'Microsoft.FSharp.Core.FSharpOption`1[System.DateTime]'."

Why is it trying to cast StopptidId to a DateTime? Obviously that special unicode character - Å - is causing the streaming to derail in some way in this particular case.

I've been using Akka.NET and letters like æøåÆØÅ for years, and haven't had any problems until now. I expect this to work, so this must be a rare bug.

Aaronontheweb commented 5 years ago

@BentTranberg that's a good question - I'm going to guess that it's a Hyperion issue.

@Horusiath - any ideas what could be going on here?

Horusiath commented 5 years ago

I can only to try to reproduce it (tbh. if @BentTranberg could create a repoducible example, that would be super) - Hyperion uses UTF8 for all strings, so specific names shouldn't be a problem.

BentTranberg commented 5 years ago

In UTF8 these letters are encoded with two bytes. It is a rather common mistake to confuse string length and byte length. There are also other pitfalls related to handling of UTF8 strings that doesn't contain just "plain ASCII". These were my first thoughts.

I can try to create a reproducible example from the VS-solution with the problem, but I don't have the time to do it soon, since I am totally overwhelmed by work. I'm hoping there's a quicker way to provoke this error. Are there calls that can be used just to serialize the message, in the hope it will trigger the exception? Or can Hyperion be used to send from one actor to another in the same test program, just to simplify a bit?

BentTranberg commented 5 years ago

I got another similar error today, in the same spot in the system. That in itself is suspicious. There's no source in the vicinity that I would suspect can corrupt anything. It looks like I need to dig into this sooner than I planned yesterday. As you can see from the error message, today it is likely the "Å" in a type name that causes it. About to start digging.

[ERROR][26.09.2019 08:34:13][Thread 0015][[akka://client/system/endpointManager/reliableEndpointWriter-akka.tcp%3A%2F%2Ftre%40127.0.0.1%3A29210-1/endpointWriter#1960773132]] AssociationError [akka.tcp://client@localhost:3431] -> akka.tcp://tre@127.0.0.1:29210: Error [Unable to cast object of type 'StoppÅrsakNr' to type 'System.String'.] [   ved Akka.Remote.EndpointReader.<Reading>b__11_1(InboundPayload inbound)
   ved lambda_method(Closure , Object , Action`1 , Action`1 , Action`1 )
   ved Akka.Tools.MatchHandler.PartialHandlerArgumentsCapture`4.Handle(T value)
   ved Akka.Actor.ReceiveActor.ExecutePartialMessageHandler(Object message, PartialAction`1 partialAction)
   ved Akka.Actor.UntypedActor.Receive(Object message)
   ved Akka.Actor.ActorBase.AroundReceive(Receive receive, Object message)
   ved Akka.Actor.ActorCell.ReceiveMessage(Object message)
   ved Akka.Actor.ActorCell.Invoke(Envelope envelope)]
Cause: Unknown
BentTranberg commented 5 years ago

Note: I deleted my last message where I had gotten the facts from my testing very wrong. I have now implemented the workaround, and everything is working well. So to get the facts straight:

The problem did not only occur on my machine, and it was always present.

The problem occured with two independent messages, where a record within the message structure had a field name that started with the letter "Å". Changing those field names to start with "Aa" instead, so that each entire record only had fields with "ascii names", solved the problem.

I noticed in WireShark that type names of fields that contain "Å" are streamed just fine. It seems it's the field names that can't have "Å", at least not in the start.

BentTranberg commented 5 years ago

Some more test results, from a quick test.

I tried with various "special" unicode characters instead of "Å" in the first position. All caused exceptions.

Then I tried with various "special" unicode characters elsewhere after the first character in the field name. No problems. So I thought, ok, seems like I have a basis for a good workaround - using the type name also as the field name, because it doesn't start with "Å". Like this.

StoppÅrsakNr: StoppÅrsakNr option

This failed. Darn! Could it be that the field name somehow clashed with the type name? So I tried StoppØrsakNr: StoppÅrsakNr option just to make sure, and there was still exceptions.

I tried various other naming patterns, but didn't succeed in finding a specific pattern that caused failure, other than what is already described.

The exceptions seem to be slightly different now and then. For example Unable to cast object of type 'StoppKilde' to type 'Microsoft.FSharp.Core.FSharpOption'1[TdTypes.Stopptider+StoppÅrsakNr], so it seems to get confused in various ways.

Turns out that on my development machine at home, this problem doesn't occur at all, and the difference is in the machine and not the program, because I even tested the same binaries too. The machines have both been installed by me, and I take great care making sure the environment is as equal as can reasonably be.

I will be working on a repro, as soon as my work situation permits.

BentTranberg commented 4 years ago

Updated from Hyperion 0.9.8 to 0.9.9, and it did not resolve this issue.

BentTranberg commented 4 years ago

Ported my applications from .NET Framework to .NET Core 3.1, and the issue is still unresolved. I still intend to create a repro as soon as I can find some time to do it, and I am heigthening the priority on that task.

Tested with Akka 1.3.17, Akka.FSharp 1.3.17, Akka.Remote 1.3.17, Akka.Serialization.Hyperion 1.3.13-beta, Hyperion 0.9.11.

Tested with Akka 1.4.0-beta3, Akka.FSharp 1.4.0-beta3, Akka.Remote 1.4.0-beta3, Akka.Serialization.Hyperion 1.4.0-beta3, Hyperion 0.9.11.