Skip to content

Commit

Permalink
0.11.2 Release
Browse files Browse the repository at this point in the history
0.11.2 Release
  • Loading branch information
Arkatufus authored Oct 7, 2021
2 parents 728edc8 + ca89f72 commit 10a8b03
Show file tree
Hide file tree
Showing 13 changed files with 303 additions and 6 deletions.
5 changes: 5 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### 0.11.2 October 7 2021 ####
* Fix exception thrown during deserialization when preserve object reference was turned on
and a surrogate instance was inserted into a collection multiple times. [#264](https://github.com/akkadotnet/Hyperion/pull/264)
* Add support for AggregateException serialization. [#266](https://github.com/akkadotnet/Hyperion/pull/266)

### 0.11.1 August 17 2021 ####
* Add [unsafe deserialization type blacklist](https://github.com/akkadotnet/Hyperion/pull/242)
* Bump [Akka version from 1.4.21 to 1.4.23](https://github.com/akkadotnet/Hyperion/pull/246)
Expand Down
1 change: 1 addition & 0 deletions src/Hyperion.API.Tests/CoreApiSpec.ApproveApi.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ namespace Hyperion
public object GetDeserializedObject(int id) { }
public System.Type GetTypeFromTypeId(int typeId) { }
public Hyperion.TypeVersionInfo GetVersionInfo([Hyperion.Internal.NotNull] System.Type type) { }
public void ReplaceOrAddTrackedDeserializedObject([Hyperion.Internal.NotNull] object origin, [Hyperion.Internal.NotNull] object replacement) { }
public void TrackDeserializedObject([Hyperion.Internal.NotNull] object obj) { }
public void TrackDeserializedType([Hyperion.Internal.NotNull] System.Type type) { }
public void TrackDeserializedTypeWithVersion([Hyperion.Internal.NotNull] System.Type type, [Hyperion.Internal.NotNull] Hyperion.TypeVersionInfo versionInfo) { }
Expand Down
2 changes: 1 addition & 1 deletion src/Hyperion.API.Tests/Hyperion.API.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ApprovalTests" Version="5.7.0" />
<PackageReference Include="ApprovalTests" Version="5.7.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="PublicApiGenerator" Version="10.2.0" />
<PackageReference Include="xunit" Version="$(XunitVersion)" />
Expand Down
27 changes: 27 additions & 0 deletions src/Hyperion.Akka.Integration.Tests/IntegrationSpec.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Akka.Actor;
using Akka.Configuration;
using Akka.Serialization;
Expand Down Expand Up @@ -60,6 +61,32 @@ public void Akka_HyperionSerializer_should_serialize_properly()
deserialized.Count.Should().Be(24);
}

[Fact]
public void Bugfix263_Akka_HyperionSerializer_should_serialize_ActorPath_list()
{
var actor = Sys.ActorOf(Props.Create<MyActor>());
var container = new ContainerClass(new List<ActorPath>{ actor.Path, actor.Path });
var serialized = _serializer.ToBinary(container);
var deserialized = _serializer.FromBinary<ContainerClass>(serialized);
deserialized.Destinations.Count.Should().Be(2);
deserialized.Destinations[0].Should().Be(deserialized.Destinations[1]);
}

private class MyActor: ReceiveActor
{

}

private class ContainerClass
{
public ContainerClass(List<ActorPath> destinations)
{
Destinations = destinations;
}

public List<ActorPath> Destinations { get; }
}

private class MyPocoClass
{
public string Name { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Update="FSharp.Core" Version="5.0.2" />
<PackageReference Update="FSharp.Core" Version="6.0.0" />
</ItemGroup>

</Project>
80 changes: 80 additions & 0 deletions src/Hyperion.Tests/CollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Dynamic;
using System.IO;
using System.Linq;
using FluentAssertions;
using Xunit;
using Hyperion.SerializerFactories;
using Hyperion.ValueSerializers;
Expand Down Expand Up @@ -415,6 +416,35 @@ public void Issue18()
Assert.True(msg.SequenceEqual(deserialized));
}

[Fact]
public void Issue263_CanSerializeArrayOfSurrogate_WhenPreservingObjectReference()
{
var invoked = new List<SurrogatedClass.ClassSurrogate>();
var serializer = new Serializer(new SerializerOptions(
preserveObjectReferences: true,
surrogates: new []
{
Surrogate.Create<SurrogatedClass, SurrogatedClass.ClassSurrogate>(
to => to.ToSurrogate(),
from => {
invoked.Add(from);
return from.FromSurrogate();
}),
}));

var objectRef = new SurrogatedClass(5);
var expected = new List<SurrogatedClass> { objectRef, objectRef };
using (var stream = new MemoryStream())
{
serializer.Serialize(expected, stream);
stream.Position = 0;
var deserialized = serializer.Deserialize<List<SurrogatedClass>>(stream);
deserialized.Count.Should().Be(2);
invoked.Count.Should().Be(1);
ReferenceEquals(deserialized[0], deserialized[1]).Should().BeTrue();
}
}

#region test classes

public class CustomAdd : IEnumerable<int>
Expand Down Expand Up @@ -451,6 +481,56 @@ public CustomAddRange(IImmutableList<int> inner)
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class SurrogatedClass
{
public class ClassSurrogate
{
public ClassSurrogate(string value)
{
Value = value;
}

public string Value { get; }

public SurrogatedClass FromSurrogate()
=> new SurrogatedClass(int.Parse(Value));

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj is ClassSurrogate s && s.Value == Value;
}

public override int GetHashCode()
{
return (Value != null ? Value.GetHashCode() : 0);
}
}

public SurrogatedClass(int value)
{
Value = value;
}

public int Value { get; }

public ClassSurrogate ToSurrogate()
=> new ClassSurrogate(Value.ToString());

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj is SurrogatedClass s && s.Value == Value;
}

public override int GetHashCode()
{
return Value;
}
}

#endregion

[Fact]
Expand Down
38 changes: 38 additions & 0 deletions src/Hyperion.Tests/CustomObjectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,44 @@ public void CanSerializeException()
Assert.Equal(expected.Message, actual.Message);
}

[Fact]
public void CanSerializeAggregateException()
{
Exception ex1;
Exception ex2;
AggregateException expected;
try
{
throw new Exception("hello wire 1");
}
catch (Exception e)
{
ex1 = e;
}
try
{
throw new Exception("hello wire 2");
}
catch (Exception e)
{
ex2 = e;
}
try
{
throw new AggregateException("Aggregate", ex1, ex2);
}
catch (AggregateException e)
{
expected = e;
}
Serialize(expected);
Reset();
var actual = Deserialize<AggregateException>();
Assert.Equal(expected.StackTrace, actual.StackTrace);
Assert.Equal(expected.Message, actual.Message);
Assert.Equal(expected.InnerExceptions.Count, actual.InnerExceptions.Count);
}

[Fact]
public void CanSerializePolymorphicObject()
{
Expand Down
9 changes: 9 additions & 0 deletions src/Hyperion/DeserializeSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ public object GetDeserializedObject(int id)
return _objectById[id];
}

public void ReplaceOrAddTrackedDeserializedObject([NotNull] object origin, [NotNull] object replacement)
{
var index = _objectById.IndexOf(origin);
if (index == -1)
_objectById.Add(origin);
else
_objectById[index] = replacement;
}

public void TrackDeserializedType([NotNull]Type type)
{
if (_identifierToType == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#region copyright
// -----------------------------------------------------------------------
// <copyright file="ExceptionSerializerFactory.cs" company="Akka.NET Team">
// Copyright (C) 2015-2016 AsynkronIT <https://github.com/AsynkronIT>
// Copyright (C) 2016-2016 Akka.NET Team <https://github.com/akkadotnet>
// </copyright>
// -----------------------------------------------------------------------
#endregion

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Reflection;
using System.Runtime.Serialization;
using Hyperion.Extensions;
using Hyperion.ValueSerializers;

namespace Hyperion.SerializerFactories
{
internal sealed class AggregateExceptionSerializerFactory : ValueSerializerFactory
{
private static readonly TypeInfo ExceptionTypeInfo = typeof(Exception).GetTypeInfo();
private static readonly TypeInfo AggregateExceptionTypeInfo = typeof(AggregateException).GetTypeInfo();
private readonly FieldInfo _className;
private readonly FieldInfo _innerException;
private readonly FieldInfo _stackTraceString;
private readonly FieldInfo _remoteStackTraceString;
private readonly FieldInfo _message;
private readonly FieldInfo _innerExceptions;

public AggregateExceptionSerializerFactory()
{
_className = ExceptionTypeInfo.GetField("_className", BindingFlagsEx.All);
_innerException = ExceptionTypeInfo.GetField("_innerException", BindingFlagsEx.All);
_message = AggregateExceptionTypeInfo.GetField("_message", BindingFlagsEx.All);
_remoteStackTraceString = ExceptionTypeInfo.GetField("_remoteStackTraceString", BindingFlagsEx.All);
_stackTraceString = ExceptionTypeInfo.GetField("_stackTraceString", BindingFlagsEx.All);
_innerExceptions = AggregateExceptionTypeInfo.GetField("m_innerExceptions", BindingFlagsEx.All);
}

public override bool CanSerialize(Serializer serializer, Type type) =>
#if NETSTANDARD16
false;
#else
AggregateExceptionTypeInfo.IsAssignableFrom(type.GetTypeInfo());
#endif

public override bool CanDeserialize(Serializer serializer, Type type) => CanSerialize(serializer, type);

#if NETSTANDARD16
// Workaround for CoreCLR where FormatterServices.GetUninitializedObject is not public
private static readonly Func<Type, object> GetUninitializedObject =
(Func<Type, object>)
typeof(string).GetTypeInfo().Assembly.GetType("System.Runtime.Serialization.FormatterServices")
.GetMethod("GetUninitializedObject", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
.CreateDelegate(typeof(Func<Type, object>));
#else
private static readonly Func<Type,object> GetUninitializedObject = System.Runtime.Serialization.FormatterServices.GetUninitializedObject;
#endif

public override ValueSerializer BuildSerializer(Serializer serializer, Type type,
ConcurrentDictionary<Type, ValueSerializer> typeMapping)
{
#if !NETSTANDARD1_6
var exceptionSerializer = new ObjectSerializer(type);
exceptionSerializer.Initialize((stream, session) =>
{
var info = new SerializationInfo(type, new FormatterConverter());

info.AddValue("ClassName", stream.ReadString(session), typeof (string));
info.AddValue("Message", stream.ReadString(session), typeof (string));
info.AddValue("Data", stream.ReadObject(session), typeof (IDictionary));
info.AddValue("InnerException", stream.ReadObject(session), typeof (Exception));
info.AddValue("HelpURL", stream.ReadString(session), typeof (string));
info.AddValue("StackTraceString", stream.ReadString(session), typeof (string));
info.AddValue("RemoteStackTraceString", stream.ReadString(session), typeof (string));
info.AddValue("RemoteStackIndex", stream.ReadInt32(session), typeof (int));
info.AddValue("ExceptionMethod", stream.ReadString(session), typeof (string));
info.AddValue("HResult", stream.ReadInt32(session));
info.AddValue("Source", stream.ReadString(session), typeof (string));
info.AddValue("InnerExceptions", stream.ReadObject(session), typeof (Exception[]));

return Activator.CreateInstance(type, BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.Instance, null, new object[]{info, new StreamingContext()}, null);
}, (stream, exception, session) =>
{
var info = new SerializationInfo(type, new FormatterConverter());
var context = new StreamingContext();
((AggregateException)exception).GetObjectData(info, context);

var className = info.GetString("ClassName");
var message = info.GetString("Message");
var data = info.GetValue("Data", typeof(IDictionary));
var innerException = info.GetValue("InnerException", typeof(Exception));
var helpUrl = info.GetString("HelpURL");
var stackTraceString = info.GetString("StackTraceString");
var remoteStackTraceString = info.GetString("RemoteStackTraceString");
var remoteStackIndex = info.GetInt32("RemoteStackIndex");
var exceptionMethod = info.GetString("ExceptionMethod");
var hResult = info.GetInt32("HResult");
var source = info.GetString("Source");
var innerExceptions = (Exception[]) info.GetValue("InnerExceptions", typeof(Exception[]));

StringSerializer.WriteValueImpl(stream, className, session);
StringSerializer.WriteValueImpl(stream, message, session);
stream.WriteObjectWithManifest(data, session);
stream.WriteObjectWithManifest(innerException, session);
StringSerializer.WriteValueImpl(stream, helpUrl, session);
StringSerializer.WriteValueImpl(stream, stackTraceString, session);
StringSerializer.WriteValueImpl(stream, remoteStackTraceString, session);
Int32Serializer.WriteValueImpl(stream, remoteStackIndex, session);
StringSerializer.WriteValueImpl(stream, exceptionMethod, session);
Int32Serializer.WriteValueImpl(stream, hResult, session);
StringSerializer.WriteValueImpl(stream, source, session);
stream.WriteObjectWithManifest(innerExceptions, session);
});
if (serializer.Options.KnownTypesDict.TryGetValue(type, out var index))
{
var wrapper = new KnownTypeObjectSerializer(exceptionSerializer, index);
typeMapping.TryAdd(type, wrapper);
}
else
typeMapping.TryAdd(type, exceptionSerializer);
return exceptionSerializer;
#else
return null;
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type
var surrogate = serializer.Options.Surrogates.FirstOrDefault(s => s.To.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()));
var objectSerializer = new ObjectSerializer(type);
// ReSharper disable once PossibleNullReferenceException
var fromSurrogateSerializer = new FromSurrogateSerializer(surrogate.FromSurrogate, objectSerializer);
var fromSurrogateSerializer = new FromSurrogateSerializer(surrogate.FromSurrogate, objectSerializer, serializer.Options.PreserveObjectReferences);
typeMapping.TryAdd(type, fromSurrogateSerializer);


Expand Down
1 change: 1 addition & 0 deletions src/Hyperion/SerializerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ internal static List<Func<string, string>> DefaultPackageNameOverrides()
new FSharpMapSerializerFactory(),
new FSharpListSerializerFactory(),
//order is important, try dictionaries before enumerables as dicts are also enumerable
new AggregateExceptionSerializerFactory(),
new ExceptionSerializerFactory(),
new ImmutableCollectionsSerializerFactory(),
new ExpandoObjectSerializerFactory(),
Expand Down
Loading

0 comments on commit 10a8b03

Please sign in to comment.