Skip to content

Commit

Permalink
Add IOnDeserialized post-deserialization lifecycle hook
Browse files Browse the repository at this point in the history
  • Loading branch information
ReubenBond committed Feb 23, 2017
1 parent 277082a commit 46d1e81
Show file tree
Hide file tree
Showing 14 changed files with 154 additions and 83 deletions.
1 change: 1 addition & 0 deletions src/Orleans/Orleans.csproj
Expand Up @@ -156,6 +156,7 @@
<Compile Include="Serialization\ILGenerationException.cs" />
<Compile Include="Serialization\ILSerializerGenerator.cs" />
<Compile Include="Serialization\ILDelegateBuilder.cs" />
<Compile Include="Serialization\IOnDeserialized.cs" />
<Compile Include="Serialization\OrleansJsonSerializer.cs" />
<Compile Include="Serialization\BinaryFormatterSerializer.cs" />
<Compile Include="Services\IGrainServiceClient.cs" />
Expand Down
9 changes: 5 additions & 4 deletions src/Orleans/Runtime/GrainReference.cs
Expand Up @@ -563,13 +563,14 @@ protected internal static object DeserializeGrainReference(Type t, IDeserializat
var genericArg = reader.ReadString();
if (string.IsNullOrEmpty(genericArg))
genericArg = null;


var runtimeClient = context.AdditionalContext as IRuntimeClient;
if (expectObserverId)
{
return NewObserverGrainReference(id, observerId, context.SerializationManager.RuntimeClient);
return NewObserverGrainReference(id, observerId, runtimeClient);
}

return FromGrainId(id, context.SerializationManager.RuntimeClient, genericArg, silo);
return FromGrainId(id, runtimeClient, genericArg, silo);
}

/// <summary> Copier function for grain reference. </summary>
Expand Down Expand Up @@ -734,7 +735,7 @@ protected GrainReference(SerializationInfo info, StreamingContext context)
private void OnDeserialized(StreamingContext context)
{
var serializerContext = context.Context as ISerializerContext;
this.runtimeClient = serializerContext?.AdditionalContext as IRuntimeClient; ;
this.runtimeClient = serializerContext?.AdditionalContext as IRuntimeClient;
}
#endif
#endregion
Expand Down
15 changes: 14 additions & 1 deletion src/Orleans/Serialization/ILSerializerGenerator.cs
Expand Up @@ -23,7 +23,12 @@ internal class ILSerializerGenerator
private static readonly SerializationManager.DeepCopier ImmutableTypeCopier = (obj, context) => obj;

private static readonly ILFieldBuilder FieldBuilder = new ILFieldBuilder();


private static readonly Type OnDeserializedLifecycleType = typeof(IOnDeserialized);

private static readonly MethodInfo OnDeserializedMethod =
TypeUtils.Method((IOnDeserialized i) => i.OnDeserialized(default(ISerializerContext)));

static ILSerializerGenerator()
{
DirectSerializers = new Dictionary<RuntimeTypeHandle, SimpleTypeSerializer>
Expand Down Expand Up @@ -278,6 +283,14 @@ private ILDelegateBuilder<SerializationManager.Deserializer> EmitDeserializer(Ty
}
}

// If the type implements the IOnDeserialized lifecycle handler, call that method now.
if (OnDeserializedLifecycleType.IsAssignableFrom(type))
{
il.LoadLocal(result);
il.LoadArgument(1);
il.Call(OnDeserializedMethod);
}

il.LoadLocal(result);
il.BoxIfValueType(type);
il.Return();
Expand Down
14 changes: 14 additions & 0 deletions src/Orleans/Serialization/IOnDeserialized.cs
@@ -0,0 +1,14 @@
namespace Orleans.Serialization
{
/// <summary>
/// Indicates that a class is to be notified when it has been deserialized.
/// </summary>
public interface IOnDeserialized
{
/// <summary>
/// Notifies this instance that it has been fully deserialized.
/// </summary>
/// <param name="context">The serializer context.</param>
void OnDeserialized(ISerializerContext context);
}
}
5 changes: 4 additions & 1 deletion src/Orleans/Serialization/SerializationContext.cs
Expand Up @@ -14,7 +14,10 @@ public interface ISerializerContext
/// Gets the service provider.
/// </summary>
IServiceProvider ServiceProvider { get; }


/// <summary>
/// Gets additional context associated with this instance.
/// </summary>
object AdditionalContext { get; }
}

Expand Down
@@ -1,10 +1,7 @@

using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Microsoft.WindowsAzure.Storage.Queue;
using Orleans.CodeGeneration;
using Orleans.Providers.Streams.Common;
using Orleans.Serialization;
using Orleans.Streams;
Expand All @@ -30,7 +27,7 @@ public interface IAzureQueueDataAdapter
/// <summary>
/// Original data adapter. Here to maintain backwards compatablity, but does not support json and other custom serializers
/// </summary>
public class AzureQueueDataAdapterV1 : IAzureQueueDataAdapter
public class AzureQueueDataAdapterV1 : IAzureQueueDataAdapter, IOnDeserialized
{
private SerializationManager serializationManager;

Expand Down Expand Up @@ -67,25 +64,16 @@ public IBatchContainer FromCloudQueueMessage(CloudQueueMessage cloudMsg, long se
return azureQueueBatch;
}

[SerializerMethod]
private static void Serialize(object obj, ISerializationContext context, Type expected)
void IOnDeserialized.OnDeserialized(ISerializerContext context)
{
this.serializationManager = context.SerializationManager;
}

[DeserializerMethod]
private static object Deserialize(Type expected, IDeserializationContext context)
{
return new DefaultMemoryMessageBodySerializer(context.SerializationManager);
}

[CopierMethod]
private static object Copy(object obj, ICopyContext context) => obj;
}

/// <summary>
/// Data adapter that uses types that support custom serializers (like json).
/// </summary>
public class AzureQueueDataAdapterV2 : IAzureQueueDataAdapter
public class AzureQueueDataAdapterV2 : IAzureQueueDataAdapter, IOnDeserialized
{
private SerializationManager serializationManager;

Expand Down Expand Up @@ -122,18 +110,9 @@ public IBatchContainer FromCloudQueueMessage(CloudQueueMessage cloudMsg, long se
return azureQueueBatch;
}

[SerializerMethod]
private static void Serialize(object obj, ISerializationContext context, Type expected)
void IOnDeserialized.OnDeserialized(ISerializerContext context)
{
this.serializationManager = context.SerializationManager;
}

[DeserializerMethod]
private static object Deserialize(Type expected, IDeserializationContext context)
{
return new DefaultMemoryMessageBodySerializer(context.SerializationManager);
}

[CopierMethod]
private static object Copy(object obj, ICopyContext context) => obj;
}
}
11 changes: 11 additions & 0 deletions src/OrleansCodeGenerator/SerializerGenerator.cs
Expand Up @@ -143,6 +143,17 @@ private static MemberDeclarationSyntax GenerateDeserializerMethod(Type type, Lis
SF.CastExpression(field.Type, deserialized))));
}

// If the type implements the internal IOnDeserialized lifecycle method, invoke it's method now.
if (typeof(IOnDeserialized).IsAssignableFrom(type))
{
Expression<Action<IOnDeserialized>> onDeserializedMethod = _ => _.OnDeserialized(default(ISerializerContext));

// C#: ((IOnDeserialized)result).OnDeserialized(context);
var typedResult = SF.ParenthesizedExpression(SF.CastExpression(typeof(IOnDeserialized).GetTypeSyntax(), resultVariable));
var invokeOnDeserialized = onDeserializedMethod.Invoke(typedResult).AddArgumentListArguments(SF.Argument(contextParameter));
body.Add(SF.ExpressionStatement(invokeOnDeserialized));
}

body.Add(SF.ReturnStatement(SF.CastExpression(type.GetTypeSyntax(), resultVariable)));
return
SF.MethodDeclaration(typeof(object).GetTypeSyntax(), "Deserializer")
Expand Down
12 changes: 10 additions & 2 deletions src/OrleansProviders/Streams/Memory/MemoryBatchContainer.cs
@@ -1,17 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Orleans.Providers.Streams.Common;
using Orleans.Runtime;
using Orleans.Serialization;
using Orleans.Streams;

namespace Orleans.Providers
{
[Serializable]
internal class MemoryBatchContainer<TSerializer> : IBatchContainer
internal class MemoryBatchContainer<TSerializer> : IBatchContainer, IOnDeserialized
where TSerializer : IMemoryMessageBodySerializer
{
private readonly IMemoryMessageBodySerializer serializer;
[NonSerialized]
private TSerializer serializer;
private readonly EventSequenceToken realToken;
public Guid StreamGuid => MessageData.StreamGuid;
public string StreamNamespace => MessageData.StreamNamespace;
Expand Down Expand Up @@ -54,5 +57,10 @@ public bool ShouldDeliver(IStreamIdentity stream, object filterData, StreamFilte
{
return true;
}

void IOnDeserialized.OnDeserialized(ISerializerContext context)
{
this.serializer = ActivatorUtilities.GetServiceOrCreateInstance<TSerializer>(context.ServiceProvider);
}
}
}
25 changes: 3 additions & 22 deletions src/OrleansProviders/Streams/Memory/MemoryMessageBody.cs
Expand Up @@ -2,8 +2,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Orleans.CodeGeneration;
using Orleans.Serialization;

namespace Orleans.Providers
Expand Down Expand Up @@ -32,7 +30,7 @@ public interface IMemoryMessageBodySerializer
/// Default IMemoryMessageBodySerializer
/// </summary>
[Serializable]
public class DefaultMemoryMessageBodySerializer : IMemoryMessageBodySerializer
public class DefaultMemoryMessageBodySerializer : IMemoryMessageBodySerializer, IOnDeserialized
{
[NonSerialized]
private SerializationManager serializationManager;
Expand All @@ -58,26 +56,9 @@ public MemoryMessageBody Deserialize(ArraySegment<byte> bodyBytes)
return serializationManager.DeserializeFromByteArray<MemoryMessageBody>(bodyBytes.ToArray());
}

[SerializerMethod]
private static void Serialize(object obj, ISerializationContext context, Type expected)
void IOnDeserialized.OnDeserialized(ISerializerContext context)
{
}

[DeserializerMethod]
private static object Deserialize(Type expected, IDeserializationContext context)
{
return new DefaultMemoryMessageBodySerializer(context.SerializationManager);
}

[CopierMethod]
private static object Copy(object obj, ICopyContext context) => obj;

[OnDeserialized]
private void OnDeserialized(StreamingContext streamingContext)
{
#if !NETSTANDARD_TODO
this.serializationManager = (streamingContext.Context as ISerializerContext)?.SerializationManager;
#endif
this.serializationManager = context.SerializationManager;
}
}

Expand Down
Expand Up @@ -9,7 +9,6 @@
using Microsoft.ServiceBus.Messaging;
#endif
using Newtonsoft.Json;
using Orleans.CodeGeneration;
using Orleans.Runtime;
using Orleans.Serialization;
using Orleans.Streams;
Expand All @@ -20,14 +19,14 @@ namespace Orleans.ServiceBus.Providers
/// Batch container that is delivers payload and stream position information for a set of events in an EventHub EventData.
/// </summary>
[Serializable]
public class EventHubBatchContainer : IBatchContainer
public class EventHubBatchContainer : IBatchContainer, IOnDeserialized
{
[JsonProperty]
private readonly EventHubMessage eventHubMessage;

[JsonIgnore]
[NonSerialized]
private readonly SerializationManager serializationManager;
private SerializationManager serializationManager;

[JsonProperty]
private readonly EventHubSequenceToken token;
Expand Down Expand Up @@ -125,28 +124,10 @@ internal static EventData ToEventData<T>(SerializationManager serializationManag
}
return eventData;
}

[SerializerMethod]
private static void Serialize(object obj, ISerializationContext context, Type expected)
{
var input = (EventHubBatchContainer) obj;
SerializationManager.SerializeInner(input.eventHubMessage, context, typeof(EventHubMessage));
}

[DeserializerMethod]
private static object Deserialize(Type expected, IDeserializationContext context)
{
var message = SerializationManager.DeserializeInner<EventHubMessage>(context);
return new EventHubBatchContainer(message, context.SerializationManager);
}

[CopierMethod]
private static object Copy(object obj, ICopyContext context)

void IOnDeserialized.OnDeserialized(ISerializerContext context)
{
var result =
new EventHubBatchContainer(((EventHubBatchContainer) obj).eventHubMessage, context.SerializationManager);
context.RecordCopy(obj, result);
return result;
this.serializationManager = context.SerializationManager;
}
}
}
22 changes: 22 additions & 0 deletions test/NonSiloTests/Serialization/BuiltInSerializerTests.cs
Expand Up @@ -417,6 +417,28 @@ public void Serialize_Stack(SerializerToUse serializerToUse)
}
}

/// <summary>
/// Tests that the <see cref="IOnDeserialized"/> callback is invoked after deserialization.
/// </summary>
/// <param name="serializerToUse"></param>
[Theory, TestCategory("Functional"), TestCategory("Serialization")]
[InlineData(SerializerToUse.NoFallback)]
public void Serialize_TypeWithOnDeserializedHook(SerializerToUse serializerToUse)
{
var environment = InitializeSerializer(serializerToUse);

var input = new TypeWithOnDeserializedHook
{
Int = 5
};
var deserialized = OrleansSerializationLoop(environment.SerializationManager, input);
var result = Assert.IsType<TypeWithOnDeserializedHook>(deserialized);
Assert.Equal(input.Int, result.Int);
Assert.Null(input.Context);
Assert.NotNull(result.Context);
Assert.Equal(environment.SerializationManager, result.Context.SerializationManager);
}

[Theory, TestCategory("Functional"), TestCategory("Serialization")]
[InlineData(SerializerToUse.NoFallback)]
public void Serialize_SortedSetWithComparer(SerializerToUse serializerToUse)
Expand Down

0 comments on commit 46d1e81

Please sign in to comment.