Skip to content

Commit 7ce5030

Browse files
authored
Add cross-platform exception serialization support (#7222)
* 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
1 parent b73fe64 commit 7ce5030

10 files changed

+89
-4
lines changed

src/core/Akka.Remote.Tests/Akka.Remote.Tests.csproj

+23-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<Import Project="..\..\xunitSettings.props" />
33

4-
<PropertyGroup>
5-
<TargetFrameworks>$(NetTestVersion)</TargetFrameworks>
4+
<PropertyGroup Condition="'$(OS)' == 'Windows_NT'">
5+
<TargetFrameworks>$(NetFrameworkTestVersion);$(NetTestVersion)</TargetFrameworks>
6+
</PropertyGroup>
7+
8+
<!-- disable .NET Framework on Linux-->
9+
<PropertyGroup Condition="'$(OS)' != 'Windows_NT'">
10+
<TargetFramework>$(NetTestVersion)</TargetFramework>
611
</PropertyGroup>
712

813
<ItemGroup>
@@ -13,6 +18,22 @@
1318
<None Include="Resources\akka-validcert.pfx">
1419
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
1520
</None>
21+
22+
<None Update="test-files\SerializedException-Net6.0.bin">
23+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
24+
</None>
25+
26+
<None Update="test-files\SerializedException-Net8.0.bin">
27+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
28+
</None>
29+
30+
<None Update="test-files\SerializedException-Net471.bin">
31+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
32+
</None>
33+
34+
<None Update="test-files\SerializedException-Net481.bin">
35+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
36+
</None>
1637
</ItemGroup>
1738

1839
<ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="Bugfix7215Spec.cs" company="Akka.NET Project">
3+
// Copyright (C) 2009-2024 Lightbend Inc. <http://www.lightbend.com>
4+
// Copyright (C) 2013-2024 .NET Foundation <https://github.com/akkadotnet/akka.net>
5+
// </copyright>
6+
// -----------------------------------------------------------------------
7+
8+
using System;
9+
using System.IO;
10+
using Akka.Actor;
11+
using Akka.Remote.Serialization;
12+
using Xunit;
13+
using Xunit.Abstractions;
14+
using FluentAssertions;
15+
using static FluentAssertions.FluentActions;
16+
17+
namespace Akka.Remote.Tests.Serialization;
18+
19+
public class Bugfix7215Spec: TestKit.Xunit2.TestKit
20+
{
21+
public Bugfix7215Spec(ITestOutputHelper output) : base(nameof(Bugfix7215Spec), output)
22+
{
23+
}
24+
25+
[Theory(DisplayName = "Should be able to deserialize InvalidOperationException from all frameworks")]
26+
[InlineData("Net471")]
27+
[InlineData("Net481")]
28+
[InlineData("Net6.0")]
29+
[InlineData("Net8.0")]
30+
public void DeserializeNet471Test(string framework)
31+
{
32+
var bytes = File.ReadAllBytes($"./test-files/SerializedException-{framework}.bin");
33+
var helper = new ExceptionSupport((ExtendedActorSystem)Sys);
34+
35+
Exception exception = null;
36+
Invoking(() => exception = helper.DeserializeException(bytes)).Should().NotThrow();
37+
exception.Should().BeOfType<InvalidOperationException>();
38+
exception.Message.Should().Be("You can't do this.");
39+
}
40+
}

src/core/Akka.Remote.Tests/Transport/DotNettySslSetupSpec.cs

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public DotNettySslSetupSpec(ITestOutputHelper output) : base(TestActorSystemSetu
7272
{
7373
}
7474

75+
#if !NET471
7576
[Fact]
7677
public async Task Secure_transport_should_be_possible_between_systems_sharing_the_same_certificate()
7778
{
@@ -88,6 +89,7 @@ await AwaitAssertAsync(async () =>
8889
await probe.ExpectMsgAsync("hello", TimeSpan.FromSeconds(3));
8990
}, TimeSpan.FromSeconds(30), TimeSpan.FromMilliseconds(100));
9091
}
92+
#endif
9193

9294
[Fact]
9395
public async Task Secure_transport_should_NOT_be_possible_between_systems_using_SSL_and_one_not_using_it()

src/core/Akka.Remote.Tests/Transport/DotNettySslSupportSpec.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ public DotNettySslSupportSpec(ITestOutputHelper output) : base(TestConfig(ValidC
164164
private string Thumbprint { get; set; }
165165

166166

167-
167+
#if !NET471
168168
[Fact]
169169
public async Task Secure_transport_should_be_possible_between_systems_sharing_the_same_certificate()
170170
{
@@ -181,6 +181,7 @@ await AwaitAssertAsync(async () =>
181181
await probe.ExpectMsgAsync("hello", TimeSpan.FromSeconds(3));
182182
}, TimeSpan.FromSeconds(30), TimeSpan.FromMilliseconds(100));
183183
}
184+
#endif
184185

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

225+
#if !NET471
224226
[Fact]
225227
public async Task If_EnableSsl_configuration_is_true_but_not_valid_certificate_is_provided_than_ArgumentNullException_should_be_thrown()
226228
{
@@ -236,6 +238,7 @@ public async Task If_EnableSsl_configuration_is_true_but_not_valid_certificate_i
236238
Assert.NotNull(realException);
237239
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);
238240
}
241+
#endif
239242

240243
[Fact]
241244
public async Task If_EnableSsl_configuration_is_true_but_not_valid_certificate_password_is_provided_than_WindowsCryptographicException_should_be_thrown()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

src/core/Akka.Remote/Serialization/ExceptionSupport.cs

+19-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System;
99
using System.Collections.Generic;
1010
using System.Reflection;
11+
using System.Runtime.CompilerServices;
1112
using Akka.Actor;
1213
using Akka.Util;
1314
using Akka.Util.Internal;
@@ -82,6 +83,11 @@ public Proto.Msg.ExceptionData ExceptionToProtoNet(Exception exception)
8283
message.StackTrace = exception.StackTrace ?? "";
8384
message.Source = exception.Source ?? "";
8485
message.InnerException = ExceptionToProto(exception.InnerException);
86+
87+
var forwardedFrom = exceptionType.GetCustomAttribute<TypeForwardedFromAttribute>();
88+
message.TypeForwardedFrom = forwardedFrom is not null
89+
? forwardedFrom.AssemblyFullName[..forwardedFrom.AssemblyFullName.IndexOf(',')]
90+
: string.Empty;
8591

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

113-
Type exceptionType = Type.GetType(proto.TypeName);
119+
var exceptionType = Type.GetType(proto.TypeName);
120+
121+
// If type loading failed and type was forwarded from an older assembly,
122+
// retry by loading the type from the older assembly name
123+
if (exceptionType is null && proto.TypeForwardedFrom != string.Empty)
124+
{
125+
var typeName = $"{proto.TypeName[..proto.TypeName.IndexOf(',')]}, {proto.TypeForwardedFrom}";
126+
exceptionType = Type.GetType(typeName);
127+
}
128+
129+
// If we still fail, throw.
130+
if (exceptionType is null)
131+
throw new SerializationException($"Failed to deserialize ExceptionData. Could not load {proto.TypeName}. {proto}");
114132

115133
var serializationInfo = new SerializationInfo(exceptionType, _defaultFormatterConverter);
116134

src/protobuf/ContainerFormats.proto

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ message ExceptionData {
6969
string source = 4;
7070
ExceptionData innerException = 5;
7171
map<string, Payload> customFields = 6;
72+
string typeForwardedFrom = 7;
7273
}
7374

7475
message StatusSuccess{

0 commit comments

Comments
 (0)