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

[PORT #6297] Exception serialization support for built-in messages #6300

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions src/core/Akka.Remote.Tests/Serialization/Bugfix3903Spec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//-----------------------------------------------------------------------
// <copyright file="Bugfix3903Specs.cs" company="Akka.NET Project">
// Copyright (C) 2009-2022 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2022 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Configuration;
using Akka.TestKit;
using Akka.Util.Internal;
using Xunit;
using Xunit.Abstractions;
using FluentAssertions;

namespace Akka.Remote.Tests.Serialization
{
public class Bugfix3903Spec : AkkaSpec
{
// hocon config enabling akka.remote
private static readonly Config Config = @"akka.actor.provider = remote
akka.remote.dot-netty.tcp.hostname = localhost
akka.remote.dot-netty.tcp.port = 0";

public Bugfix3903Spec(ITestOutputHelper outputHelper) : base(Config, outputHelper)
{
}

#region Internal Types

// parent actor type that will remotely deploy a child actor type onto a specific address
private class ParentActor : ReceiveActor
{
// message type that includes an Address
public class DeployChild
{
public DeployChild(Address address)
{
Address = address;
}

public Address Address { get; }
}

public ParentActor()
{
Receive<DeployChild>(s =>
{
// props to deploy an EchoActor at the address specified in DeployChild
var props = Props.Create<EchoActor>().WithDeploy(new Deploy(new RemoteScope(s.Address)));
var child = Context.ActorOf(props, "child");
Sender.Tell(child);
});
}
}

internal class EchoActor : ReceiveActor
{
public class Fail
{
public static readonly Fail Instance = new Fail();
private Fail(){}
}

public EchoActor()
{
// receive message that will cause this actor to fail
Receive<Fail>(s =>
{
throw new ApplicationException("fail");
});
ReceiveAny(o => Sender.Tell(o));
}
}

#endregion

// a test where Sys starts a ParentActor and has it remotely deploy an EchoActor onto a second ActorSystem
[Fact]
public async Task ParentActor_should_be_able_to_deploy_EchoActor_to_remote_system()
{
// create a second ActorSystem
var system2 = ActorSystem.Create(Sys.Name, Sys.Settings.Config);
InitializeLogger(system2);
try
{
// create a supervision strategy that will send a message to the TestActor including the exception of the child that failed
var strategy = new OneForOneStrategy(ex =>
{
TestActor.Tell(ex);
return Directive.Stop;
});

// create a ParentActor in the first ActorSystem
var parent = Sys.ActorOf(Props.Create<ParentActor>().WithSupervisorStrategy(strategy), "parent");

// have the ParentActor remotely deploy an EchoActor onto the second ActorSystem
var child = await parent
.Ask<IActorRef>(new ParentActor.DeployChild(
system2.AsInstanceOf<ExtendedActorSystem>().Provider.DefaultAddress), RemainingOrDefault).ConfigureAwait(false);

// assert that Child is a remote actor reference
child.Should().BeOfType<RemoteActorRef>();
Watch(child);

// send a message to the EchoActor and verify that it is received
(await child.Ask<string>("hello", RemainingOrDefault).ConfigureAwait(false)).Should().Be("hello");

// cause the child to crash
child.Tell(EchoActor.Fail.Instance);
var exception = ExpectMsg<ApplicationException>();
exception.Message.Should().Be("fail");
ExpectTerminated(child);
}
finally
{
// shut down the second ActorSystem
Shutdown(system2);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,30 @@ public void Can_serialize_IdentifyWithNull()
AssertEqual(identify);
}

[Theory]
[InlineData(null)]
[InlineData(1)]
[InlineData("hi")]
public void Can_serialize_StatusSuccess(object payload)
{
var success = new Status.Success(payload);
AssertEqual(success);
}

[Theory]
[InlineData(null)]
[InlineData(1)]
[InlineData("hi")]
public void Can_serialize_StatusFailure(object payload)
{
var success = new Status.Failure(new ApplicationException("foo"),payload);
// can't use AssertEqual here since the Exception data isn't 100% identical after round-trip serialization
var deserialized = AssertAndReturn(success);
deserialized.State.Should().BeEquivalentTo(success.State);
deserialized.Cause.Message.Should().BeEquivalentTo(success.Cause.Message);
deserialized.Cause.Should().BeOfType(success.Cause.GetType());
}

[Fact]
public void Can_serialize_ActorIdentity()
{
Expand Down Expand Up @@ -372,7 +396,7 @@ private T AssertAndReturn<T>(T message)
private void AssertEqual<T>(T message)
{
var deserialized = AssertAndReturn(message);
Assert.Equal(message, deserialized);
deserialized.Should().BeEquivalentTo(message);
}
}
}
2 changes: 2 additions & 0 deletions src/core/Akka.Remote/Configuration/Remote.conf
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ akka {
"Akka.Actor.PoisonPill, Akka" = akka-misc
"Akka.Actor.Kill, Akka" = akka-misc
"Akka.Actor.PoisonPill, Akka" = akka-misc
"Akka.Actor.Status+Failure, Akka" = akka-misc
"Akka.Actor.Status+Success, Akka" = akka-misc
#"Akka.Actor.LocalScope, Akka" = akka-misc
"Akka.Actor.RemoteScope, Akka" = akka-misc
"Akka.Routing.FromConfig, Akka" = akka-misc
Expand Down
2 changes: 1 addition & 1 deletion src/core/Akka.Remote/Serialization/ExceptionSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public Exception ExceptionFromProtoNet(Proto.Msg.ExceptionData proto)
return obj;
}

private string ValueOrNull(string value)
private static string ValueOrNull(string value)
=> string.IsNullOrEmpty(value) ? null : value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using System.Linq;
using System.Runtime.Serialization;
using Akka.Actor;
using Akka.Remote.Serialization.Proto.Msg;
using Akka.Serialization;
using Akka.Util;
using Google.Protobuf;
Expand Down Expand Up @@ -108,7 +107,7 @@ public override object FromBinary(byte[] bytes, Type type)
return new ActorSelectionMessage(message, elements);
}

private Proto.Msg.Selection BuildPattern(string matcher, Proto.Msg.Selection.Types.PatternType tpe)
private static Proto.Msg.Selection BuildPattern(string matcher, Proto.Msg.Selection.Types.PatternType tpe)
{
var selection = new Proto.Msg.Selection { Type = tpe };
if (matcher != null)
Expand Down
Loading