diff --git a/src/Orleans/Orleans.csproj b/src/Orleans/Orleans.csproj index c6d4da1085..6ad14110b5 100644 --- a/src/Orleans/Orleans.csproj +++ b/src/Orleans/Orleans.csproj @@ -156,6 +156,7 @@ + diff --git a/src/Orleans/Runtime/GrainReference.cs b/src/Orleans/Runtime/GrainReference.cs index 8167fb941d..3fdc1580c2 100644 --- a/src/Orleans/Runtime/GrainReference.cs +++ b/src/Orleans/Runtime/GrainReference.cs @@ -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); } /// Copier function for grain reference. @@ -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 diff --git a/src/Orleans/Serialization/ILSerializerGenerator.cs b/src/Orleans/Serialization/ILSerializerGenerator.cs index 53be03e2c0..051af80b87 100644 --- a/src/Orleans/Serialization/ILSerializerGenerator.cs +++ b/src/Orleans/Serialization/ILSerializerGenerator.cs @@ -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 @@ -278,6 +283,14 @@ private ILDelegateBuilder 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(); diff --git a/src/Orleans/Serialization/IOnDeserialized.cs b/src/Orleans/Serialization/IOnDeserialized.cs new file mode 100644 index 0000000000..655197f42a --- /dev/null +++ b/src/Orleans/Serialization/IOnDeserialized.cs @@ -0,0 +1,14 @@ +namespace Orleans.Serialization +{ + /// + /// Indicates that a class is to be notified when it has been deserialized. + /// + public interface IOnDeserialized + { + /// + /// Notifies this instance that it has been fully deserialized. + /// + /// The serializer context. + void OnDeserialized(ISerializerContext context); + } +} \ No newline at end of file diff --git a/src/Orleans/Serialization/SerializationContext.cs b/src/Orleans/Serialization/SerializationContext.cs index 5874d70168..a47f07e3e5 100644 --- a/src/Orleans/Serialization/SerializationContext.cs +++ b/src/Orleans/Serialization/SerializationContext.cs @@ -14,7 +14,10 @@ public interface ISerializerContext /// Gets the service provider. /// IServiceProvider ServiceProvider { get; } - + + /// + /// Gets additional context associated with this instance. + /// object AdditionalContext { get; } } diff --git a/src/OrleansAzureUtils/Providers/Streams/AzureQueue/IAzureQueueDataAdapter.cs b/src/OrleansAzureUtils/Providers/Streams/AzureQueue/IAzureQueueDataAdapter.cs index 1b2d2c5713..e3d587fadb 100644 --- a/src/OrleansAzureUtils/Providers/Streams/AzureQueue/IAzureQueueDataAdapter.cs +++ b/src/OrleansAzureUtils/Providers/Streams/AzureQueue/IAzureQueueDataAdapter.cs @@ -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; @@ -30,7 +27,7 @@ public interface IAzureQueueDataAdapter /// /// Original data adapter. Here to maintain backwards compatablity, but does not support json and other custom serializers /// - public class AzureQueueDataAdapterV1 : IAzureQueueDataAdapter + public class AzureQueueDataAdapterV1 : IAzureQueueDataAdapter, IOnDeserialized { private SerializationManager serializationManager; @@ -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; } /// /// Data adapter that uses types that support custom serializers (like json). /// - public class AzureQueueDataAdapterV2 : IAzureQueueDataAdapter + public class AzureQueueDataAdapterV2 : IAzureQueueDataAdapter, IOnDeserialized { private SerializationManager serializationManager; @@ -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; } } diff --git a/src/OrleansCodeGenerator/SerializerGenerator.cs b/src/OrleansCodeGenerator/SerializerGenerator.cs index 8ed050f640..5073af44a6 100644 --- a/src/OrleansCodeGenerator/SerializerGenerator.cs +++ b/src/OrleansCodeGenerator/SerializerGenerator.cs @@ -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> 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") diff --git a/src/OrleansProviders/Streams/Memory/MemoryBatchContainer.cs b/src/OrleansProviders/Streams/Memory/MemoryBatchContainer.cs index f35e698bfc..cb68c15df8 100644 --- a/src/OrleansProviders/Streams/Memory/MemoryBatchContainer.cs +++ b/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 : IBatchContainer + internal class MemoryBatchContainer : 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; @@ -54,5 +57,10 @@ public bool ShouldDeliver(IStreamIdentity stream, object filterData, StreamFilte { return true; } + + void IOnDeserialized.OnDeserialized(ISerializerContext context) + { + this.serializer = ActivatorUtilities.GetServiceOrCreateInstance(context.ServiceProvider); + } } } diff --git a/src/OrleansProviders/Streams/Memory/MemoryMessageBody.cs b/src/OrleansProviders/Streams/Memory/MemoryMessageBody.cs index 2637a6cc8f..90738c7453 100644 --- a/src/OrleansProviders/Streams/Memory/MemoryMessageBody.cs +++ b/src/OrleansProviders/Streams/Memory/MemoryMessageBody.cs @@ -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 @@ -32,7 +30,7 @@ public interface IMemoryMessageBodySerializer /// Default IMemoryMessageBodySerializer /// [Serializable] - public class DefaultMemoryMessageBodySerializer : IMemoryMessageBodySerializer + public class DefaultMemoryMessageBodySerializer : IMemoryMessageBodySerializer, IOnDeserialized { [NonSerialized] private SerializationManager serializationManager; @@ -58,26 +56,9 @@ public MemoryMessageBody Deserialize(ArraySegment bodyBytes) return serializationManager.DeserializeFromByteArray(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; } } diff --git a/src/OrleansServiceBus/Providers/Streams/EventHub/EventHubBatchContainer.cs b/src/OrleansServiceBus/Providers/Streams/EventHub/EventHubBatchContainer.cs index dea4d65191..cf519f0677 100644 --- a/src/OrleansServiceBus/Providers/Streams/EventHub/EventHubBatchContainer.cs +++ b/src/OrleansServiceBus/Providers/Streams/EventHub/EventHubBatchContainer.cs @@ -9,7 +9,6 @@ using Microsoft.ServiceBus.Messaging; #endif using Newtonsoft.Json; -using Orleans.CodeGeneration; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Streams; @@ -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. /// [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; @@ -125,28 +124,10 @@ internal static EventData ToEventData(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(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; } } } diff --git a/test/NonSiloTests/Serialization/BuiltInSerializerTests.cs b/test/NonSiloTests/Serialization/BuiltInSerializerTests.cs index 519292ab15..9b9dd7842d 100644 --- a/test/NonSiloTests/Serialization/BuiltInSerializerTests.cs +++ b/test/NonSiloTests/Serialization/BuiltInSerializerTests.cs @@ -417,6 +417,28 @@ public void Serialize_Stack(SerializerToUse serializerToUse) } } + /// + /// Tests that the callback is invoked after deserialization. + /// + /// + [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(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) diff --git a/test/NonSiloTests/Serialization/ILBasedSerializerTests.cs b/test/NonSiloTests/Serialization/ILBasedSerializerTests.cs index a486b5b638..5593424139 100644 --- a/test/NonSiloTests/Serialization/ILBasedSerializerTests.cs +++ b/test/NonSiloTests/Serialization/ILBasedSerializerTests.cs @@ -1,3 +1,4 @@ +using System; using TestExtensions; namespace UnitTests.Serialization @@ -56,13 +57,46 @@ public void ILSerializer_AllowCopiedFieldsToDifferFromSerializedFields() Assert.Equal(3, deserialized.Three); } + /// + /// Tests that supports the lifecycle hook. + /// + [Fact] + public void ILSerializer_LifecycleHooksAreCalled() + { + var input = new FieldTest(); + var generator = new ILSerializerGenerator(); + var serializers = generator.GenerateSerializer(input.GetType()); + var writer = new SerializationContext(this.fixture.SerializationManager) + { + StreamWriter = new BinaryTokenStreamWriter() + }; + serializers.Serialize(input, writer, input.GetType()); + var reader = new DeserializationContext(this.fixture.SerializationManager) + { + StreamReader = new BinaryTokenStreamReader(writer.StreamWriter.ToByteArray()) + }; + var deserialized = (FieldTest)serializers.Deserialize(input.GetType(), reader); + + Assert.Null(input.Context); + Assert.NotNull(deserialized.Context); + Assert.Equal(this.fixture.SerializationManager, deserialized.Context.SerializationManager); + } + [SuppressMessage("ReSharper", "StyleCop.SA1401", Justification = "This is for testing purposes.")] - private class FieldTest + private class FieldTest : IOnDeserialized { #pragma warning disable 169 public int One; public int Two; public int Three; + + [NonSerialized] + public ISerializerContext Context; + + public void OnDeserialized(ISerializerContext context) + { + this.Context = context; + } #pragma warning restore 169 } } diff --git a/test/TestGrainInterfaces/SerializerTestTypes.cs b/test/TestGrainInterfaces/SerializerTestTypes.cs new file mode 100644 index 0000000000..2373bdd9ee --- /dev/null +++ b/test/TestGrainInterfaces/SerializerTestTypes.cs @@ -0,0 +1,22 @@ +using System; +using Orleans.Serialization; + +namespace UnitTests.GrainInterfaces +{ + /// + /// A type with an hook, to test that it is correctly called by the internal serializers. + /// + [Serializable] + public class TypeWithOnDeserializedHook : IOnDeserialized + { + [NonSerialized] + public ISerializerContext Context; + + public int Int { get; set; } + + void IOnDeserialized.OnDeserialized(ISerializerContext context) + { + this.Context = context; + } + } +} diff --git a/test/TestGrainInterfaces/TestGrainInterfaces.csproj b/test/TestGrainInterfaces/TestGrainInterfaces.csproj index 8311021c56..63086560e8 100644 --- a/test/TestGrainInterfaces/TestGrainInterfaces.csproj +++ b/test/TestGrainInterfaces/TestGrainInterfaces.csproj @@ -129,6 +129,7 @@ +