Skip to content

Commit

Permalink
Add cross-platform exception serialization support (#7222)
Browse files Browse the repository at this point in the history
* Add cross-platform exception serialization support

* Add unit tests

* Exclude net471 test in Linux

* Disable net471 build in linux

* Fix silly mistake

* Disable SSL tests that are not compatible with net471 API
  • Loading branch information
Arkatufus committed Jun 3, 2024
1 parent b73fe64 commit 7ce5030
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 4 deletions.
25 changes: 23 additions & 2 deletions src/core/Akka.Remote.Tests/Akka.Remote.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\xunitSettings.props" />

<PropertyGroup>
<TargetFrameworks>$(NetTestVersion)</TargetFrameworks>
<PropertyGroup Condition="'$(OS)' == 'Windows_NT'">
<TargetFrameworks>$(NetFrameworkTestVersion);$(NetTestVersion)</TargetFrameworks>
</PropertyGroup>

<!-- disable .NET Framework on Linux-->
<PropertyGroup Condition="'$(OS)' != 'Windows_NT'">
<TargetFramework>$(NetTestVersion)</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand All @@ -13,6 +18,22 @@
<None Include="Resources\akka-validcert.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

<None Update="test-files\SerializedException-Net6.0.bin">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

<None Update="test-files\SerializedException-Net8.0.bin">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

<None Update="test-files\SerializedException-Net471.bin">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

<None Update="test-files\SerializedException-Net481.bin">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
Expand Down
40 changes: 40 additions & 0 deletions src/core/Akka.Remote.Tests/Serialization/Bugfix7215Spec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// -----------------------------------------------------------------------
// <copyright file="Bugfix7215Spec.cs" company="Akka.NET Project">
// Copyright (C) 2009-2024 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2024 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.IO;
using Akka.Actor;
using Akka.Remote.Serialization;
using Xunit;
using Xunit.Abstractions;
using FluentAssertions;
using static FluentAssertions.FluentActions;

namespace Akka.Remote.Tests.Serialization;

public class Bugfix7215Spec: TestKit.Xunit2.TestKit
{
public Bugfix7215Spec(ITestOutputHelper output) : base(nameof(Bugfix7215Spec), output)
{
}

[Theory(DisplayName = "Should be able to deserialize InvalidOperationException from all frameworks")]
[InlineData("Net471")]
[InlineData("Net481")]
[InlineData("Net6.0")]
[InlineData("Net8.0")]
public void DeserializeNet471Test(string framework)
{
var bytes = File.ReadAllBytes($"./test-files/SerializedException-{framework}.bin");
var helper = new ExceptionSupport((ExtendedActorSystem)Sys);

Exception exception = null;
Invoking(() => exception = helper.DeserializeException(bytes)).Should().NotThrow();
exception.Should().BeOfType<InvalidOperationException>();
exception.Message.Should().Be("You can't do this.");
}
}
2 changes: 2 additions & 0 deletions src/core/Akka.Remote.Tests/Transport/DotNettySslSetupSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public DotNettySslSetupSpec(ITestOutputHelper output) : base(TestActorSystemSetu
{
}

#if !NET471
[Fact]
public async Task Secure_transport_should_be_possible_between_systems_sharing_the_same_certificate()
{
Expand All @@ -88,6 +89,7 @@ await AwaitAssertAsync(async () =>
await probe.ExpectMsgAsync("hello", TimeSpan.FromSeconds(3));
}, TimeSpan.FromSeconds(30), TimeSpan.FromMilliseconds(100));
}
#endif

[Fact]
public async Task Secure_transport_should_NOT_be_possible_between_systems_using_SSL_and_one_not_using_it()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public DotNettySslSupportSpec(ITestOutputHelper output) : base(TestConfig(ValidC
private string Thumbprint { get; set; }



#if !NET471
[Fact]
public async Task Secure_transport_should_be_possible_between_systems_sharing_the_same_certificate()
{
Expand All @@ -181,6 +181,7 @@ await AwaitAssertAsync(async () =>
await probe.ExpectMsgAsync("hello", TimeSpan.FromSeconds(3));
}, TimeSpan.FromSeconds(30), TimeSpan.FromMilliseconds(100));
}
#endif

[LocalFact(SkipLocal = "Racy in Azure AzDo CI/CD")]
public async Task Secure_transport_should_be_possible_between_systems_using_thumbprint()
Expand Down Expand Up @@ -221,6 +222,7 @@ await Assert.ThrowsAsync<RemoteTransportException>(async () =>
});
}

#if !NET471
[Fact]
public async Task If_EnableSsl_configuration_is_true_but_not_valid_certificate_is_provided_than_ArgumentNullException_should_be_thrown()
{
Expand All @@ -236,6 +238,7 @@ public async Task If_EnableSsl_configuration_is_true_but_not_valid_certificate_i
Assert.NotNull(realException);
Assert.Equal("Path to SSL certificate was not found (by default it can be found under `akka.remote.dot-netty.tcp.ssl.certificate.path`) (Parameter 'certificatePath')", realException.Message);
}
#endif

[Fact]
public async Task If_EnableSsl_configuration_is_true_but_not_valid_certificate_password_is_provided_than_WindowsCryptographicException_should_be_thrown()
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
20 changes: 19 additions & 1 deletion src/core/Akka.Remote/Serialization/ExceptionSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Akka.Actor;
using Akka.Util;
using Akka.Util.Internal;
Expand Down Expand Up @@ -82,6 +83,11 @@ public Proto.Msg.ExceptionData ExceptionToProtoNet(Exception exception)
message.StackTrace = exception.StackTrace ?? "";
message.Source = exception.Source ?? "";
message.InnerException = ExceptionToProto(exception.InnerException);

var forwardedFrom = exceptionType.GetCustomAttribute<TypeForwardedFromAttribute>();
message.TypeForwardedFrom = forwardedFrom is not null
? forwardedFrom.AssemblyFullName[..forwardedFrom.AssemblyFullName.IndexOf(',')]
: string.Empty;

var serializable = exception as ISerializable;
var serializationInfo = new SerializationInfo(exceptionType, _defaultFormatterConverter);
Expand Down Expand Up @@ -110,7 +116,19 @@ public Exception ExceptionFromProtoNet(Proto.Msg.ExceptionData proto)
if (string.IsNullOrEmpty(proto.TypeName))
return null;

Type exceptionType = Type.GetType(proto.TypeName);
var exceptionType = Type.GetType(proto.TypeName);

// If type loading failed and type was forwarded from an older assembly,
// retry by loading the type from the older assembly name
if (exceptionType is null && proto.TypeForwardedFrom != string.Empty)
{
var typeName = $"{proto.TypeName[..proto.TypeName.IndexOf(',')]}, {proto.TypeForwardedFrom}";
exceptionType = Type.GetType(typeName);
}

// If we still fail, throw.
if (exceptionType is null)
throw new SerializationException($"Failed to deserialize ExceptionData. Could not load {proto.TypeName}. {proto}");

var serializationInfo = new SerializationInfo(exceptionType, _defaultFormatterConverter);

Expand Down
1 change: 1 addition & 0 deletions src/protobuf/ContainerFormats.proto
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ message ExceptionData {
string source = 4;
ExceptionData innerException = 5;
map<string, Payload> customFields = 6;
string typeForwardedFrom = 7;
}

message StatusSuccess{
Expand Down

0 comments on commit 7ce5030

Please sign in to comment.