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

Add custom AggregateException serializer. #266

Merged
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
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
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
Copy link
Member

Choose a reason for hiding this comment

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

Does the default ExceptionSerializerFactory not handle InnerExceptions? Or is this different because AggregateException can store multiple errors at each layer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Regular exceptions have InnerException, AggregateException have InnerException and InnerExceptions

{
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
}
}
}
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