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

Akka.Serialization: INoSerializationVerificationNeeded does not handle IWrappedMessage correctly #7010

Conversation

Aaronontheweb
Copy link
Member

@Aaronontheweb Aaronontheweb commented Nov 29, 2023

Changes

INoSerializationVerificationNeeded does not handle IWrappedMessage correctly. We handle DeadLetter correctly as a special case, but not any of the other IWrappedMessage implementations. We should handle this generically.

Checklist

For significant changes, please ensure that the following have been completed (delete if not relevant):

Sorry, something went wrong.

`INoSerializationVerificationNeeded` does not handle `IWrappedMessage` correctly
@Aaronontheweb
Copy link
Member Author

Added a repro for this; working on a fix now.

@Aaronontheweb Aaronontheweb modified the milestones: 1.5.14, 1.5.15 Nov 29, 2023
Copy link
Member Author

@Aaronontheweb Aaronontheweb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detailed my changes.

}

// add an unserializable member, such as a delegate
public Action MyAction { get; }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newtonsoft.Json and Hyperion will both barf on delegate serialization, so this message will not be received when the parent IWrappedMessage is not handled correctly.


public class SerializeAllMessagesSpec : AkkaSpec
{
public SerializeAllMessagesSpec(ITestOutputHelper output) : base("akka.actor.serialize-messages = on", output)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have to enable akka.actor.serialize-messages=on in order for this to even be an issue.

{
// Arrange
var myProbe = CreateTestProbe();
var action = () => { myProbe.Tell("worked"); };
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invoking the delegate closes on the loop on this spec by messaging the closed-over TestProbe.

{
// Act
myActor.Tell(wrappedMessage);
await myProbe.ExpectMsgAsync("worked");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both the EventFilter will fail and the ExpectMsgAsync will fail if the ActorCell of the recipient attempts to serialize this message - which is exactly what happened before I applied my fix.

var unwrapped = (deadLetter = envelope.Message as DeadLetter) != null ? deadLetter.Message : envelope.Message;
var unwrapped = envelope.Message;

if(envelope.Message is IWrappedMessage wrapped)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for all IWrappedMessages: if the underlying message doesn't want to be serialized (typically because it can't be), don't try it. Just return the message without performing any additional work.

@@ -534,7 +537,8 @@ private Envelope SerializeAndDeserialize(Envelope envelope)
throw new SerializationException($"Failed to serialize and deserialize payload object [{unwrapped.GetType()}]. Envelope: [{envelope}], Actor type: [{Actor.GetType()}]", e);
}

if (deadLetter != null)
// special case handling for DeadLetters
if (envelope.Message is DeadLetter deadLetter)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restore the special-case handling for DeadLetters that we had before.

@@ -534,7 +537,8 @@ private Envelope SerializeAndDeserialize(Envelope envelope)
throw new SerializationException($"Failed to serialize and deserialize payload object [{unwrapped.GetType()}]. Envelope: [{envelope}], Actor type: [{Actor.GetType()}]", e);
}

if (deadLetter != null)
// special case handling for DeadLetters
if (envelope.Message is DeadLetter deadLetter)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like we're missing a round of unwrap here, this code will return an Envelope that wraps a DeadLetter that wraps a DeadLetter.
The DeadLetter message still needs to be deserialized like it was before and then re-wrapped inside a new DeadLetter message.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I can use the Unwrapped utility from earlier to recursively unwrap it - let me look into that. Currently doing a full test suite run locally so I can fix some of the outstanding .NET 8 errors in the suite.

Copy link
Contributor

@Arkatufus Arkatufus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self review

Comment on lines +194 to +197
while (message is IWrappedMessage wm)
{
message = wm.Message;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unwrap tail recursion into loop

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines 541 to 544
// special case handling for DeadLetters
return deadLetter is not null
? new Envelope(new DeadLetter(deserializedMsg, deadLetter.Sender, deadLetter.Recipient), envelope.Sender)
: new Envelope(deserializedMsg, envelope.Sender);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code behaves exactly like the old code, only modernized

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine to me

var deadLetter = envelope.Message as DeadLetter;

// recursively unwraps message
var unwrapped = WrappedMessage.Unwrap(deadLetter is not null ? deadLetter.Message : envelope.Message);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the envelope message is a DeadLetter, grab the dead letter message instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also unwraps the message recursively if it is an IWrappedMessage. There is no need to check if the message is IWrappedMessage or not, it is done inside the Unwrap static function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need to check for a DeadLetter here as it implements IWrappedMessage.

DeadLetter deadLetter;
var unwrapped = (deadLetter = envelope.Message as DeadLetter) != null ? deadLetter.Message : envelope.Message;

var deadLetter = envelope.Message as DeadLetter;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check to see if the envelope message is a DeadLetter

Copy link
Member Author

@Aaronontheweb Aaronontheweb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nitpick on the duplicate check for a DeadLetter @Arkatufus

var deadLetter = envelope.Message as DeadLetter;

// recursively unwraps message
var unwrapped = WrappedMessage.Unwrap(deadLetter is not null ? deadLetter.Message : envelope.Message);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need to check for a DeadLetter here as it implements IWrappedMessage.

Comment on lines 541 to 544
// special case handling for DeadLetters
return deadLetter is not null
? new Envelope(new DeadLetter(deserializedMsg, deadLetter.Sender, deadLetter.Recipient), envelope.Sender)
: new Envelope(deserializedMsg, envelope.Sender);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine to me

@Aaronontheweb Aaronontheweb merged commit 04630da into akkadotnet:dev Dec 7, 2023
@Aaronontheweb Aaronontheweb deleted the fix-IWrappedMessage-INoSerializationVerificationNeeded branch December 7, 2023 13:01
Aaronontheweb added a commit to Aaronontheweb/akka.net that referenced this pull request May 23, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
need to serialize both the envelope and the underlying type when `serialize-messages=on`
Aaronontheweb added a commit that referenced this pull request May 24, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
…ng type when using `serialize-messages=on` (#7200)

* Fix bug introduced by #7010

need to serialize both the envelope and the underlying type when `serialize-messages=on`

* handle edge case with `IWrappedMessage` that implements `INoSerializationVerificationNeeded`

* Add unit test

* Fix pub-sub router spec

---------

Co-authored-by: Gregorius Soedharmo <arkatufus@yahoo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants