diff --git a/src/NuGet/BondSerializerInstall.ps1 b/src/NuGet/BondSerializerInstall.ps1 index 7e859f7819..c38e0326b6 100644 --- a/src/NuGet/BondSerializerInstall.ps1 +++ b/src/NuGet/BondSerializerInstall.ps1 @@ -1,6 +1,6 @@ param($installPath, $toolsPath, $package, $project) -$bondSerializerTypeName = 'Orleans.Serialization.BondSerializer, BondSerializer' +$bondSerializerTypeName = 'Orleans.Serialization.BondSerializer, OrleansBondUtils' function AddOrGetElement( [OutputType([System.Xml.XmlElement])] @@ -9,7 +9,7 @@ function AddOrGetElement( [Parameter(Mandatory=$true)] [string]$name, [Parameter(Mandatory=$true)] - [System.Xml.XmlNamespaceManager]$namespaceManager + [System.Xml.XmlNamespaceManager]$namespaceManager ) { $node = $xml.ChildNodes | where { $_.Name -eq $name } @@ -51,7 +51,7 @@ function RegisterSerializer( $bondTypeProvider = $providersnode.Provider | where { $_.type -eq $type } - if ($bondTypeProvider -eq $null) + if ($bondTypeProvider -eq $null) { $provider = AddOrGetElement -xml $providersNode -name "Provider" -namespaceManager $namespaceManager $typeAttribute = $fileXml.CreateAttribute("type"); diff --git a/src/NuGet/BondSerializerUninstall.ps1 b/src/NuGet/BondSerializerUninstall.ps1 index ec8103351b..30d277d61a 100644 --- a/src/NuGet/BondSerializerUninstall.ps1 +++ b/src/NuGet/BondSerializerUninstall.ps1 @@ -1,6 +1,6 @@ param($installPath, $toolsPath, $package, $project) -$bondSerializerTypeName = 'Orleans.Serialization.BondSerializer, BondSerializer' +$bondSerializerTypeName = 'Orleans.Serialization.BondSerializer, OrleansBondUtils' function UnregisterSerializer( [Parameter(Mandatory=$true)] diff --git a/src/NuGet/Microsoft.Orleans.OrleansAzureUtils.nuspec b/src/NuGet/Microsoft.Orleans.OrleansAzureUtils.nuspec index d1429f8a67..baa6338602 100644 --- a/src/NuGet/Microsoft.Orleans.OrleansAzureUtils.nuspec +++ b/src/NuGet/Microsoft.Orleans.OrleansAzureUtils.nuspec @@ -22,8 +22,7 @@ - - + diff --git a/src/Orleans/CodeGeneration/TypeFormattingOptions.cs b/src/Orleans/CodeGeneration/TypeFormattingOptions.cs new file mode 100644 index 0000000000..65d977f70b --- /dev/null +++ b/src/Orleans/CodeGeneration/TypeFormattingOptions.cs @@ -0,0 +1,135 @@ +namespace Orleans.Runtime +{ + using System; + + /// + /// Options for formatting type names. + /// + public class TypeFormattingOptions : IEquatable + { + public TypeFormattingOptions( + string nameSuffix = null, + bool includeNamespace = true, + bool includeGenericParameters = true, + bool includeTypeParameters = true, + char nestedClassSeparator = '.', + bool includeGlobal = true) + { + + this.NameSuffix = nameSuffix; + this.IncludeNamespace = includeNamespace; + this.IncludeGenericTypeParameters = includeGenericParameters; + this.IncludeTypeParameters = includeTypeParameters; + this.NestedTypeSeparator = nestedClassSeparator; + this.IncludeGlobal = includeGlobal; + } + + /// + /// Gets a value indicating whether or not to include the fully-qualified namespace of the class in the result. + /// + public bool IncludeNamespace { get; private set; } + + /// + /// Gets a value indicating whether or not to include concrete type parameters in the result. + /// + public bool IncludeTypeParameters { get; private set; } + + /// + /// Gets a value indicating whether or not to include generic type parameters in the result. + /// + public bool IncludeGenericTypeParameters { get; private set; } + + /// + /// Gets the separator used between declaring types and their declared types. + /// + public char NestedTypeSeparator { get; private set; } + + /// + /// Gets the name to append to the formatted name, before any type parameters. + /// + public string NameSuffix { get; private set; } + + /// + /// Gets a value indicating whether or not to include the global namespace qualifier. + /// + public bool IncludeGlobal { get; private set; } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// if the specified object is equal to the current object; otherwise, . + /// + public bool Equals(TypeFormattingOptions other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + if (ReferenceEquals(this, other)) + { + return true; + } + return this.IncludeNamespace == other.IncludeNamespace + && this.IncludeTypeParameters == other.IncludeTypeParameters + && this.IncludeGenericTypeParameters == other.IncludeGenericTypeParameters + && this.NestedTypeSeparator == other.NestedTypeSeparator + && string.Equals(this.NameSuffix, other.NameSuffix) && this.IncludeGlobal == other.IncludeGlobal; + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// + /// if the specified object is equal to the current object; otherwise, . + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != this.GetType()) + { + return false; + } + return Equals((TypeFormattingOptions)obj); + } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// A hash code for the current object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = this.IncludeNamespace.GetHashCode(); + hashCode = (hashCode * 397) ^ this.IncludeTypeParameters.GetHashCode(); + hashCode = (hashCode * 397) ^ this.IncludeGenericTypeParameters.GetHashCode(); + hashCode = (hashCode * 397) ^ this.NestedTypeSeparator.GetHashCode(); + hashCode = (hashCode * 397) ^ (this.NameSuffix != null ? this.NameSuffix.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ this.IncludeGlobal.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(TypeFormattingOptions left, TypeFormattingOptions right) + { + return Equals(left, right); + } + + public static bool operator !=(TypeFormattingOptions left, TypeFormattingOptions right) + { + return !Equals(left, right); + } + } +} \ No newline at end of file diff --git a/src/Orleans/CodeGeneration/TypeUtils.cs b/src/Orleans/CodeGeneration/TypeUtils.cs index 9aa060a40c..2c70c55137 100644 --- a/src/Orleans/CodeGeneration/TypeUtils.cs +++ b/src/Orleans/CodeGeneration/TypeUtils.cs @@ -42,7 +42,7 @@ internal static class TypeUtils /// private static readonly string OrleansCoreAssembly = typeof(IGrain).Assembly.GetName().FullName; - private static readonly ConcurrentDictionary, string> ParseableNameCache = new ConcurrentDictionary, string>(); + private static readonly ConcurrentDictionary, string> ParseableNameCache = new ConcurrentDictionary, string>(); private static readonly ConcurrentDictionary, List> ReferencedTypes = new ConcurrentDictionary, List>(); @@ -60,8 +60,15 @@ public static string GetSimpleTypeName(Type t, Func fullName=null, L if (typeInfo.IsNestedPublic || typeInfo.IsNestedPrivate) { if (typeInfo.DeclaringType.IsGenericType) - return GetTemplatedName(GetUntemplatedTypeName(typeInfo.DeclaringType.Name), typeInfo.DeclaringType, typeInfo.GetGenericArguments(), _ => true, language) + "." + GetUntemplatedTypeName(typeInfo.Name); - + { + return GetTemplatedName( + GetUntemplatedTypeName(typeInfo.DeclaringType.Name), + typeInfo.DeclaringType, + typeInfo.GetGenericArguments(), + _ => true, + language) + "." + GetUntemplatedTypeName(typeInfo.Name); + } + return GetTemplatedName(typeInfo.DeclaringType, language: language) + "." + GetUntemplatedTypeName(typeInfo.Name); } @@ -618,25 +625,25 @@ public static string GetUnadornedMethodName(this MethodInfo method) /// /// A string representation of the . /// - public static string GetParseableName(this Type type, string nameSuffix = null, bool includeNamespace = true, bool includeGenericParameters = true) + public static string GetParseableName(this Type type, TypeFormattingOptions options = null) { - return - ParseableNameCache.GetOrAdd( - Tuple.Create(type, nameSuffix, includeNamespace, includeGenericParameters), - _ => - { - var builder = new StringBuilder(); - var typeInfo = type.GetTypeInfo(); - GetParseableName( - type, - nameSuffix ?? string.Empty, - builder, - new Queue( - typeInfo.IsGenericTypeDefinition ? typeInfo.GetGenericArguments() : typeInfo.GenericTypeArguments), - includeNamespace, - includeGenericParameters); - return builder.ToString(); - }); + options = options ?? new TypeFormattingOptions(); + return ParseableNameCache.GetOrAdd( + Tuple.Create(type, options), + _ => + { + var builder = new StringBuilder(); + var typeInfo = type.GetTypeInfo(); + GetParseableName( + type, + builder, + new Queue( + typeInfo.IsGenericTypeDefinition + ? typeInfo.GetGenericArguments() + : typeInfo.GenericTypeArguments), + options); + return builder.ToString(); + }); } /// @@ -651,33 +658,28 @@ public static string GetParseableName(this Type type, string nameSuffix = null, /// /// The type arguments of . /// - /// - /// A value indicating whether or not to include the namespace name. + /// + /// The type formatting options. /// private static void GetParseableName( Type type, - string nameSuffix, StringBuilder builder, Queue typeArguments, - bool includeNamespace = true, - bool includeGenericParameters = true) + TypeFormattingOptions options) { var typeInfo = type.GetTypeInfo(); if (typeInfo.IsArray) { builder.AppendFormat( "{0}[{1}]", - typeInfo.GetElementType() - .GetParseableName( - includeNamespace: includeNamespace, - includeGenericParameters: includeGenericParameters), + typeInfo.GetElementType().GetParseableName(options), string.Concat(Enumerable.Range(0, type.GetArrayRank() - 1).Select(_ => ','))); return; } if (typeInfo.IsGenericParameter) { - if (includeGenericParameters) + if (options.IncludeGenericTypeParameters) { builder.Append(typeInfo.GetUnadornedTypeName()); } @@ -688,57 +690,63 @@ private static void GetParseableName( if (typeInfo.DeclaringType != null) { // This is not the root type. - GetParseableName(typeInfo.DeclaringType, string.Empty, builder, typeArguments, includeNamespace, includeGenericParameters); - builder.Append('.'); + GetParseableName(typeInfo.DeclaringType, builder, typeArguments, options); + builder.Append(options.NestedTypeSeparator); } - else if (!string.IsNullOrWhiteSpace(type.Namespace) && includeNamespace) + else if (!string.IsNullOrWhiteSpace(type.Namespace) && options.IncludeNamespace) { - // This is the root type. - builder.AppendFormat("global::{0}.", type.Namespace); + // This is the root type, so include the namespace. + var namespaceName = type.Namespace; + if (options.NestedTypeSeparator != '.') + { + namespaceName = namespaceName.Replace('.', options.NestedTypeSeparator); + } + + if (options.IncludeGlobal) + { + builder.AppendFormat("global::"); + } + + builder.AppendFormat("{0}{1}", namespaceName, options.NestedTypeSeparator); } if (typeInfo.IsConstructedGenericType) { // Get the unadorned name, the generic parameters, and add them together. - var unadornedTypeName = typeInfo.GetUnadornedTypeName() + nameSuffix; + var unadornedTypeName = typeInfo.GetUnadornedTypeName() + options.NameSuffix; builder.Append(EscapeIdentifier(unadornedTypeName)); var generics = Enumerable.Range(0, Math.Min(typeInfo.GetGenericArguments().Count(), typeArguments.Count)) .Select(_ => typeArguments.Dequeue()) .ToList(); - if (generics.Count > 0) + if (generics.Count > 0 && options.IncludeTypeParameters) { var genericParameters = string.Join( ",", - generics.Select( - generic => - GetParseableName( - generic, - includeNamespace: includeNamespace, - includeGenericParameters: includeGenericParameters))); + generics.Select(generic => GetParseableName(generic, options))); builder.AppendFormat("<{0}>", genericParameters); } } else if (typeInfo.IsGenericTypeDefinition) { // Get the unadorned name, the generic parameters, and add them together. - var unadornedTypeName = type.GetUnadornedTypeName() + nameSuffix; + var unadornedTypeName = type.GetUnadornedTypeName() + options.NameSuffix; builder.Append(EscapeIdentifier(unadornedTypeName)); var generics = Enumerable.Range(0, Math.Min(type.GetGenericArguments().Count(), typeArguments.Count)) .Select(_ => typeArguments.Dequeue()) .ToList(); - if (generics.Count > 0) + if (generics.Count > 0 && options.IncludeTypeParameters) { var genericParameters = string.Join( ",", - generics.Select(_ => includeGenericParameters ? _.ToString() : string.Empty)); + generics.Select(_ => options.IncludeGenericTypeParameters ? _.ToString() : string.Empty)); builder.AppendFormat("<{0}>", genericParameters); } } else { - builder.Append(EscapeIdentifier(type.GetUnadornedTypeName() + nameSuffix)); + builder.Append(EscapeIdentifier(type.GetUnadornedTypeName() + options.NameSuffix)); } } diff --git a/src/Orleans/Configuration/ClusterConfiguration.cs b/src/Orleans/Configuration/ClusterConfiguration.cs index e4d1ebc225..c64c813d16 100644 --- a/src/Orleans/Configuration/ClusterConfiguration.cs +++ b/src/Orleans/Configuration/ClusterConfiguration.cs @@ -535,7 +535,7 @@ internal static IPAddress GetLocalIPAddress(AddressFamily family = AddressFamily if (!string.IsNullOrWhiteSpace(interfaceName) && !netInterface.Name.StartsWith(interfaceName, StringComparison.Ordinal)) continue; - bool isLoopbackInterface = (i == NetworkInterface.LoopbackInterfaceIndex); + bool isLoopbackInterface = (netInterface.NetworkInterfaceType == NetworkInterfaceType.Loopback); // get list of all unicast IPs from current interface UnicastIPAddressInformationCollection ipAddresses = netInterface.GetIPProperties().UnicastAddresses; diff --git a/src/Orleans/Core/GrainAttributes.cs b/src/Orleans/Core/GrainAttributes.cs index 1dc12df6e8..2a81fb3c19 100644 --- a/src/Orleans/Core/GrainAttributes.cs +++ b/src/Orleans/Core/GrainAttributes.cs @@ -302,8 +302,8 @@ public static FactoryTypes CollectFactoryTypesSpecified() [AttributeUsage(AttributeTargets.Class, AllowMultiple=true)] public sealed class ImplicitStreamSubscriptionAttribute : Attribute { - internal string Namespace { get; private set; } - + public string Namespace { get; private set; } + // We have not yet come to an agreement whether the provider should be specified as well. public ImplicitStreamSubscriptionAttribute(string streamNamespace) { diff --git a/src/Orleans/Core/GrainClient.cs b/src/Orleans/Core/GrainClient.cs index 353b036f44..98167bc090 100644 --- a/src/Orleans/Core/GrainClient.cs +++ b/src/Orleans/Core/GrainClient.cs @@ -35,6 +35,8 @@ The above copyright notice and this permission notice shall be included in all c namespace Orleans { + using System.Runtime.ExceptionServices; + /// /// Client runtime for connecting to Orleans system /// @@ -225,9 +227,24 @@ private static void InternalInitialize(ClientConfiguration config, OutsideRuntim tcs.SetException(exc); // Break promise } }; + // Queue Init call to thread pool thread ThreadPool.QueueUserWorkItem(doInit, null); - CurrentConfig = tcs.Task.Result; // Wait for Init to finish + try + { + CurrentConfig = tcs.Task.Result; // Wait for Init to finish + } + catch (AggregateException ae) + { + // Flatten the aggregate exception, which can be deeply nested. + ae = ae.Flatten(); + + // If there is just one exception in the aggregate exception, throw that, otherwise throw the entire + // flattened aggregate exception. + var innerExceptions = ae.InnerExceptions; + var exceptionToThrow = innerExceptions.Count == 1 ? innerExceptions[0] : ae; + ExceptionDispatchInfo.Capture(exceptionToThrow).Throw(); + } } /// diff --git a/src/Orleans/Orleans.csproj b/src/Orleans/Orleans.csproj index 79f29a7f82..26ce350bca 100644 --- a/src/Orleans/Orleans.csproj +++ b/src/Orleans/Orleans.csproj @@ -89,6 +89,7 @@ + diff --git a/src/Orleans/Serialization/OrleansJsonSerializer.cs b/src/Orleans/Serialization/OrleansJsonSerializer.cs index 5160047467..31850993b9 100644 --- a/src/Orleans/Serialization/OrleansJsonSerializer.cs +++ b/src/Orleans/Serialization/OrleansJsonSerializer.cs @@ -1,10 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Net; -using System.Runtime.Serialization.Formatters.Binary; -using System.Text; -using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Orleans.Runtime; @@ -34,6 +29,7 @@ static OrleansJsonSerializer() settings.Converters.Add(new GrainIdConverter()); settings.Converters.Add(new SiloAddressConverter()); settings.Converters.Add(new UniqueKeyConverter()); + settings.Converters.Add(new GuidJsonConverter()); } /// @@ -235,5 +231,93 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist } } + /// + /// JSON converter for . + /// + class GuidJsonConverter : JsonConverter + { + /// + /// Gets a value indicating whether this can read JSON. + /// + /// if this can read JSON; otherwise, . + /// + public override bool CanRead { get { return true; } } + + /// + /// Gets a value indicating whether this can write JSON. + /// + /// if this can write JSON; otherwise, . + /// + public override bool CanWrite { get { return true; } } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// + /// Kind of the object. + /// + /// + /// if this instance can convert the specified object type; otherwise, + /// . + /// + public override bool CanConvert(Type objectType) + { + return objectType.IsAssignableFrom(typeof(Guid)) || objectType.IsAssignableFrom(typeof(Guid?)); + } + + /// + /// Writes the JSON representation of the object. + /// + /// + /// The to write to. + /// + /// + /// The value. + /// + /// + /// The calling serializer. + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteValue(default(string)); + } + else if (value is Guid) + { + var guid = (Guid)value; + writer.WriteValue(guid.ToString("N")); + } + } + + /// + /// Reads the JSON representation of the object. + /// + /// + /// The to read from. + /// + /// + /// Kind of the object. + /// + /// + /// The existing value of object being read. + /// + /// + /// The calling serializer. + /// + /// + /// The object value. + /// + public override object ReadJson( + JsonReader reader, + Type objectType, + object existingValue, + JsonSerializer serializer) + { + var str = reader.Value as string; + return str != null ? Guid.Parse(str) : default(Guid); + } + } + #endregion } diff --git a/src/Orleans/Serialization/TypeUtilities.cs b/src/Orleans/Serialization/TypeUtilities.cs index f50ef42580..376860705d 100644 --- a/src/Orleans/Serialization/TypeUtilities.cs +++ b/src/Orleans/Serialization/TypeUtilities.cs @@ -238,11 +238,11 @@ public static bool IsTypeIsInaccessibleForSerialization(Type type, Module fromMo return true; } } - - return IsTypeIsInaccessibleForSerialization( - typeInfo.GetGenericTypeDefinition(), - fromModule, - fromAssembly); + + if (IsTypeIsInaccessibleForSerialization(typeInfo.GetGenericTypeDefinition(), fromModule, fromAssembly)) + { + return true; + } } if ((typeInfo.IsNotPublic || !typeInfo.IsVisible) && !AreInternalsVisibleTo(typeInfo.Assembly, fromAssembly)) @@ -260,14 +260,25 @@ public static bool IsTypeIsInaccessibleForSerialization(Type type, Module fromMo } } + // For arrays, check the element type. if (typeInfo.IsArray) { - return IsTypeIsInaccessibleForSerialization(typeInfo.GetElementType(), fromModule, fromAssembly); + if (IsTypeIsInaccessibleForSerialization(typeInfo.GetElementType(), fromModule, fromAssembly)) + { + return true; + } + } + + // For nested types, check that the declaring type is accessible. + if (typeInfo.IsNested) + { + if (IsTypeIsInaccessibleForSerialization(typeInfo.DeclaringType, fromModule, fromAssembly)) + { + return true; + } } - var result = typeInfo.IsNestedPrivate || typeInfo.IsNestedFamily || type.IsPointer; - - return result; + return typeInfo.IsNestedPrivate || typeInfo.IsNestedFamily || type.IsPointer; } /// @@ -289,12 +300,7 @@ private static bool AreInternalsVisibleTo(Assembly fromAssembly, Assembly toAsse // Check InternalsVisibleTo attributes on the from-assembly, pointing to the to-assembly. var serializationAssemblyName = toAssembly.GetName().FullName; var internalsVisibleTo = fromAssembly.GetCustomAttributes(); - if (internalsVisibleTo.All(_ => _.AssemblyName != serializationAssemblyName)) - { - return true; - } - - return false; + return internalsVisibleTo.Any(_ => _.AssemblyName == serializationAssemblyName); } } } \ No newline at end of file diff --git a/src/Orleans/Streams/Internal/StreamConsumer.cs b/src/Orleans/Streams/Internal/StreamConsumer.cs index 99cef24ae1..e00ca4eb60 100644 --- a/src/Orleans/Streams/Internal/StreamConsumer.cs +++ b/src/Orleans/Streams/Internal/StreamConsumer.cs @@ -88,9 +88,30 @@ public async Task> SubscribeAsync( pubSub, myGrainReference, token); GuidId subscriptionId = pubSub.CreateSubscriptionId(stream.StreamId, myGrainReference); - await pubSub.RegisterConsumer(subscriptionId, stream.StreamId, streamProviderName, myGrainReference, filterWrapper); - return myExtension.SetObserver(subscriptionId, stream, observer, token, filterWrapper); + // Optimistic Concurrency: + // In general, we should first register the subsription with the pubsub (pubSub.RegisterConsumer) + // and only if it succeeds store it locally (myExtension.SetObserver). + // Basicaly, those 2 operations should be done as one atomic transaction - either both or none and isolated from concurrent reads. + // BUT: there is a distributed race here: the first msg may arrive before the call is awaited + // (since the pubsub notifies the producer that may immideately produce) + // and will thus not find the subriptionHandle in the extension, basically violating "isolation". + // Therefore, we employ Optimistic Concurrency Control here to guarantee isolation: + // we optimisticaly store subscriptionId in the handle first before calling pubSub.RegisterConsumer + // and undo it in the case of failure. + // There is no problem with that we call myExtension.SetObserver too early before the handle is registered in pub sub, + // since this subscriptionId is unique (random Guid) and no one knows it anyway, unless successfully subscribed in the pubsub. + var subriptionHandle = myExtension.SetObserver(subscriptionId, stream, observer, token, filterWrapper); + try + { + await pubSub.RegisterConsumer(subscriptionId, stream.StreamId, streamProviderName, myGrainReference, filterWrapper); + return subriptionHandle; + } catch(Exception) + { + // Undo the previous call myExtension.SetObserver. + myExtension.RemoveObserver(subscriptionId); + throw; + } } public async Task> ResumeAsync( diff --git a/src/Orleans/Streams/SimpleMessageStream/SimpleMessageStreamProducer.cs b/src/Orleans/Streams/SimpleMessageStream/SimpleMessageStreamProducer.cs index 3375a0f608..dd78b760a6 100644 --- a/src/Orleans/Streams/SimpleMessageStream/SimpleMessageStreamProducer.cs +++ b/src/Orleans/Streams/SimpleMessageStream/SimpleMessageStreamProducer.cs @@ -51,12 +51,13 @@ internal class SimpleMessageStreamProducer : IInternalAsyncBatchObserver internal bool IsRewindable { get; private set; } - internal SimpleMessageStreamProducer(StreamImpl stream, string streamProviderName, IStreamProviderRuntime providerUtilities, bool fireAndForgetDelivery, bool isRewindable) + internal SimpleMessageStreamProducer(StreamImpl stream, string streamProviderName, + IStreamProviderRuntime providerUtilities, bool fireAndForgetDelivery, IStreamPubSub pubSub, bool isRewindable) { this.stream = stream; this.streamProviderName = streamProviderName; providerRuntime = providerUtilities; - pubSub = providerRuntime.PubSub(SimpleMessageStreamProvider.DEFAULT_STREAM_PUBSUB_TYPE); + this.pubSub = pubSub; connectedToRendezvous = false; this.fireAndForgetDelivery = fireAndForgetDelivery; IsRewindable = isRewindable; diff --git a/src/Orleans/Streams/SimpleMessageStream/SimpleMessageStreamProvider.cs b/src/Orleans/Streams/SimpleMessageStream/SimpleMessageStreamProvider.cs index 6477d8214d..cf5ecec74b 100644 --- a/src/Orleans/Streams/SimpleMessageStream/SimpleMessageStreamProvider.cs +++ b/src/Orleans/Streams/SimpleMessageStream/SimpleMessageStreamProvider.cs @@ -35,8 +35,11 @@ public class SimpleMessageStreamProvider : IInternalStreamProvider private Logger logger; private IStreamProviderRuntime providerRuntime; private bool fireAndForgetDelivery; - internal const string FIRE_AND_FORGET_DELIVERY = "FireAndForgetDelivery"; - internal const StreamPubSubType DEFAULT_STREAM_PUBSUB_TYPE = StreamPubSubType.ExplicitGrainBasedAndImplicit; + private StreamPubSubType pubSubType; + + private const string STREAM_PUBSUB_TYPE = "PubSubType"; + internal const string FIRE_AND_FORGET_DELIVERY = "FireAndForgetDelivery"; + private const StreamPubSubType DEFAULT_STREAM_PUBSUB_TYPE = StreamPubSubType.ExplicitGrainBasedAndImplicit; public bool IsRewindable { get { return false; } } @@ -47,8 +50,14 @@ public Task Init(string name, IProviderRuntime providerUtilitiesManager, IProvid string fireAndForgetDeliveryStr; fireAndForgetDelivery = config.Properties.TryGetValue(FIRE_AND_FORGET_DELIVERY, out fireAndForgetDeliveryStr) && Boolean.Parse(fireAndForgetDeliveryStr); + string pubSubTypeString; + pubSubType = !config.Properties.TryGetValue(STREAM_PUBSUB_TYPE, out pubSubTypeString) + ? DEFAULT_STREAM_PUBSUB_TYPE + : (StreamPubSubType)Enum.Parse(typeof(StreamPubSubType), pubSubTypeString); + logger = providerRuntime.GetLogger(this.GetType().Name); - logger.Info("Initialized SimpleMessageStreamProvider with name {0} and with property FireAndForgetDelivery: {1}.", Name, fireAndForgetDelivery); + logger.Info("Initialized SimpleMessageStreamProvider with name {0} and with property FireAndForgetDelivery: {1} " + + "and PubSubType: {2}", Name, fireAndForgetDelivery, pubSubType); return TaskDone.Done; } @@ -72,7 +81,8 @@ public IAsyncStream GetStream(Guid id, string streamNamespace) IInternalAsyncBatchObserver IInternalStreamProvider.GetProducerInterface(IAsyncStream stream) { - return new SimpleMessageStreamProducer((StreamImpl)stream, Name, providerRuntime, fireAndForgetDelivery, IsRewindable); + return new SimpleMessageStreamProducer((StreamImpl)stream, Name, providerRuntime, + fireAndForgetDelivery, providerRuntime.PubSub(pubSubType), IsRewindable); } IInternalAsyncObservable IInternalStreamProvider.GetConsumerInterface(IAsyncStream streamId) @@ -82,7 +92,8 @@ IInternalAsyncObservable IInternalStreamProvider.GetConsumerInterface(IAsy private IInternalAsyncObservable GetConsumerInterfaceImpl(IAsyncStream stream) { - return new StreamConsumer((StreamImpl)stream, Name, providerRuntime, providerRuntime.PubSub(DEFAULT_STREAM_PUBSUB_TYPE), IsRewindable); + return new StreamConsumer((StreamImpl)stream, Name, providerRuntime, + providerRuntime.PubSub(pubSubType), IsRewindable); } } } diff --git a/src/Orleans/Telemetry/Consumers/ConsoleTelemetryConsumer.cs b/src/Orleans/Telemetry/Consumers/ConsoleTelemetryConsumer.cs index 039ddf2fa9..1a5b3bd638 100644 --- a/src/Orleans/Telemetry/Consumers/ConsoleTelemetryConsumer.cs +++ b/src/Orleans/Telemetry/Consumers/ConsoleTelemetryConsumer.cs @@ -40,9 +40,10 @@ public void TrackTrace(string message, Severity severity) break; case Severity.Off: return; + default: + TrackTrace(message); + break; } - - TrackTrace(message); } public void TrackTrace(string message, Severity severityLevel, IDictionary properties = null) diff --git a/src/OrleansAzureUtils/OrleansAzureUtils.csproj b/src/OrleansAzureUtils/OrleansAzureUtils.csproj index ef060396c6..cdff78a1ec 100644 --- a/src/OrleansAzureUtils/OrleansAzureUtils.csproj +++ b/src/OrleansAzureUtils/OrleansAzureUtils.csproj @@ -60,7 +60,7 @@ True - $(SolutionDir)packages\WindowsAzure.Storage.5.0.0\lib\net40\Microsoft.WindowsAzure.Storage.dll + $(SolutionDir)packages\WindowsAzure.Storage.5.0.2\lib\net40\Microsoft.WindowsAzure.Storage.dll True diff --git a/src/OrleansAzureUtils/packages.config b/src/OrleansAzureUtils/packages.config index dae4581a1d..eb7cc0d85d 100644 --- a/src/OrleansAzureUtils/packages.config +++ b/src/OrleansAzureUtils/packages.config @@ -6,5 +6,5 @@ - + \ No newline at end of file diff --git a/src/OrleansCodeGenerator/GrainMethodInvokerGenerator.cs b/src/OrleansCodeGenerator/GrainMethodInvokerGenerator.cs index 5fe79d0825..e7015380dc 100644 --- a/src/OrleansCodeGenerator/GrainMethodInvokerGenerator.cs +++ b/src/OrleansCodeGenerator/GrainMethodInvokerGenerator.cs @@ -31,6 +31,7 @@ namespace Orleans.CodeGenerator using System.Reflection; using System.Threading.Tasks; + using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -105,6 +106,7 @@ internal static TypeDeclarationSyntax GenerateClass(Type grainType) CodeGeneratorCommon.ClassPrefix + TypeUtils.GetSuitableClassName(grainType) + ClassSuffix) .AddModifiers(SF.Token(SyntaxKind.InternalKeyword)) .AddBaseListTypes(baseTypes.ToArray()) + .AddConstraintClauses(grainType.GetTypeConstraintSyntax()) .AddMembers(members.ToArray()) .AddAttributeLists(SF.AttributeList().AddAttributes(attributes.ToArray())); if (genericTypes.Length > 0) diff --git a/src/OrleansCodeGenerator/GrainReferenceGenerator.cs b/src/OrleansCodeGenerator/GrainReferenceGenerator.cs index f349dda0df..35faf81e61 100644 --- a/src/OrleansCodeGenerator/GrainReferenceGenerator.cs +++ b/src/OrleansCodeGenerator/GrainReferenceGenerator.cs @@ -98,6 +98,7 @@ internal static TypeDeclarationSyntax GenerateClass(Type grainType, Action .AddBaseListTypes( SF.SimpleBaseType(typeof(GrainReference).GetTypeSyntax()), SF.SimpleBaseType(grainType.GetTypeSyntax())) + .AddConstraintClauses(grainType.GetTypeConstraintSyntax()) .AddMembers(GenerateConstructors(className)) .AddMembers( GenerateInterfaceIdProperty(grainType), diff --git a/src/OrleansCodeGenerator/RoslynCodeGenerator.cs b/src/OrleansCodeGenerator/RoslynCodeGenerator.cs index 3e2cd854b9..fbf0c63d16 100644 --- a/src/OrleansCodeGenerator/RoslynCodeGenerator.cs +++ b/src/OrleansCodeGenerator/RoslynCodeGenerator.cs @@ -1,26 +1,4 @@ -/* -Project Orleans Cloud Service SDK ver. 1.0 - -Copyright (c) Microsoft Corporation - -All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the ""Software""), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - + namespace Orleans.CodeGenerator { using System; @@ -470,6 +448,24 @@ private static void ConsiderType( } } + private static void RecordType(Type type, Module module, Assembly targetAssembly, ISet includedTypes) + { + if (SerializerGenerationManager.RecordTypeToGenerate(type, module, targetAssembly)) + includedTypes.Add(type); + } + + private static bool IsPersistentGrain(TypeInfo typeInfo, out Type stateType) + { + stateType = null; + + if (typeInfo.BaseType == null) return false; + if (!typeInfo.BaseType.IsGenericType) return false; + if (typeof(Grain<>) != typeInfo.BaseType.GetGenericTypeDefinition()) return false; + + stateType = typeInfo.BaseType.GetGenericArguments()[0]; + return true; + } + /// /// Returns a value indicating whether or not code should be generated for the provided assembly. /// diff --git a/src/OrleansCodeGenerator/SerializerGenerationManager.cs b/src/OrleansCodeGenerator/SerializerGenerationManager.cs index 0d62da95a2..6b10373c97 100644 --- a/src/OrleansCodeGenerator/SerializerGenerationManager.cs +++ b/src/OrleansCodeGenerator/SerializerGenerationManager.cs @@ -75,7 +75,8 @@ internal static bool RecordTypeToGenerate(Type t, Module module, Assembly target var typeInfo = t.GetTypeInfo(); if (typeInfo.IsGenericParameter || ProcessedTypes.Contains(t) || TypesToProcess.Contains(t) - || typeof(Exception).GetTypeInfo().IsAssignableFrom(t)) return false; + || typeof(Exception).GetTypeInfo().IsAssignableFrom(t) + || typeof(Delegate).GetTypeInfo().IsAssignableFrom(t)) return false; if (typeInfo.IsArray) { @@ -83,7 +84,7 @@ internal static bool RecordTypeToGenerate(Type t, Module module, Assembly target return false; } - if (typeInfo.IsNestedPublic || typeInfo.IsNestedFamily || typeInfo.IsNestedPrivate) + if (typeInfo.IsNestedFamily || typeInfo.IsNestedPrivate) { Log.Warn( ErrorCode.CodeGenIgnoringTypes, diff --git a/src/OrleansCodeGenerator/SerializerGenerator.cs b/src/OrleansCodeGenerator/SerializerGenerator.cs index fce9cb67a2..f4b978c9fb 100644 --- a/src/OrleansCodeGenerator/SerializerGenerator.cs +++ b/src/OrleansCodeGenerator/SerializerGenerator.cs @@ -48,6 +48,13 @@ namespace Orleans.CodeGenerator /// public static class SerializerGenerator { + private static readonly TypeFormattingOptions GeneratedTypeNameOptions = new TypeFormattingOptions( + ClassSuffix, + includeGenericParameters: false, + includeTypeParameters: false, + nestedClassSeparator: '_', + includeGlobal: false); + /// /// The suffix appended to the name of generated classes. /// @@ -84,9 +91,7 @@ internal static IEnumerable GenerateClass(Type type, Acti SF.AttributeArgument(SF.TypeOfExpression(type.GetTypeSyntax(includeGenericParameters: false)))) }; - var className = CodeGeneratorCommon.ClassPrefix - + TypeUtils.GetSimpleTypeName(type, _ => !_.IsGenericParameter).Replace('.', '_') - + ClassSuffix; + var className = CodeGeneratorCommon.ClassPrefix + type.GetParseableName(GeneratedTypeNameOptions); var fields = GetFields(type); // Mark each field type for generation @@ -567,7 +572,7 @@ private static List GetFields(Type type) { var result = type.GetAllFields() - .Where(field => field.GetCustomAttribute() == null) + .Where(field => !field.IsNotSerialized) .Select((info, i) => new FieldInfoMember { FieldInfo = info, FieldNumber = i }) .ToList(); result.Sort(FieldInfoMember.Comparer.Instance); @@ -624,26 +629,25 @@ public string SetterFieldName } } - /// - /// Gets a value indicating whether or not this field represents a property with an accessible getter. + /// Gets a value indicating whether or not this field represents a property with an accessible, non-obsolete getter. /// public bool IsGettableProperty { get { - return this.PropertyInfo != null && this.PropertyInfo.GetGetMethod() != null; + return this.PropertyInfo != null && this.PropertyInfo.GetGetMethod() != null && !this.IsObsolete; } } /// - /// Gets a value indicating whether or not this field represents a property with an accessible setter. + /// Gets a value indicating whether or not this field represents a property with an accessible, non-obsolete setter. /// public bool IsSettableProperty { get { - return this.PropertyInfo != null && this.PropertyInfo.GetSetMethod() != null; + return this.PropertyInfo != null && this.PropertyInfo.GetSetMethod() != null && !this.IsObsolete; } } @@ -683,35 +687,48 @@ private PropertyInfo PropertyInfo } /// - /// Returns syntax for retrieving the value of this field. + /// Gets a value indicating whether or not this field is obsolete. + /// + private bool IsObsolete + { + get + { + var obsoleteAttr = this.FieldInfo.GetCustomAttribute(); + + // Get the attribute from the property, if present. + if (this.property != null && obsoleteAttr == null) + { + obsoleteAttr = this.property.GetCustomAttribute(); + } + + return obsoleteAttr != null; + } + } + + /// + /// Returns syntax for retrieving the value of this field, deep copying it if neccessary. /// /// The instance of the containing type. /// Whether or not to ensure that no copy of the field is made. /// Syntax for retrieving the value of this field. public ExpressionSyntax GetGetter(ExpressionSyntax instance, bool forceAvoidCopy = false) { - var typeSyntax = this.FieldInfo.FieldType.GetTypeSyntax(); - var getFieldExpression = - SF.InvocationExpression(SF.IdentifierName(this.GetterFieldName)) - .AddArgumentListArguments(SF.Argument(instance)); - - // If the field is the backing field for an auto-property, try to use the property directly. - if (this.PropertyInfo != null && this.PropertyInfo.GetGetMethod() != null) - { - return instance.Member(this.PropertyInfo.Name); - } + // Retrieve the value of the field. + var getValueExpression = this.GetValueExpression(instance); + // Avoid deep-copying the field if possible. if (forceAvoidCopy || this.FieldInfo.FieldType.IsOrleansShallowCopyable()) { - // Shallow-copy the field. - return getFieldExpression; + // Return the value without deep-copying it. + return getValueExpression; } - // Deep-copy the field. + // Deep-copy the value. Expression deepCopyInner = () => SerializationManager.DeepCopyInner(default(object)); + var typeSyntax = this.FieldInfo.FieldType.GetTypeSyntax(); return SF.CastExpression( typeSyntax, - deepCopyInner.Invoke().AddArgumentListArguments(SF.Argument(getFieldExpression))); + deepCopyInner.Invoke().AddArgumentListArguments(SF.Argument(getValueExpression))); } /// @@ -722,8 +739,8 @@ public ExpressionSyntax GetGetter(ExpressionSyntax instance, bool forceAvoidCopy /// Syntax for setting the value of this field. public ExpressionSyntax GetSetter(ExpressionSyntax instance, ExpressionSyntax value) { - // If the field is the backing field for an auto-property, try to use the property directly. - if (this.PropertyInfo != null && this.PropertyInfo.GetSetMethod() != null) + // If the field is the backing field for an accessible auto-property use the property directly. + if (this.PropertyInfo != null && this.PropertyInfo.GetSetMethod() != null && !this.IsObsolete) { return SF.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, @@ -742,6 +759,30 @@ public ExpressionSyntax GetSetter(ExpressionSyntax instance, ExpressionSyntax va .AddArgumentListArguments(instanceArg, SF.Argument(value)); } + /// + /// Returns syntax for retrieving the value of this field. + /// + /// The instance of the containing type. + /// Syntax for retrieving the value of this field. + private ExpressionSyntax GetValueExpression(ExpressionSyntax instance) + { + // If the field is the backing field for an accessible auto-property use the property directly. + ExpressionSyntax result; + if (this.PropertyInfo != null && this.PropertyInfo.GetGetMethod() != null && !this.IsObsolete) + { + result = instance.Member(this.PropertyInfo.Name); + } + else + { + // Retrieve the field using the generated getter. + result = + SF.InvocationExpression(SF.IdentifierName(this.GetterFieldName)) + .AddArgumentListArguments(SF.Argument(instance)); + } + + return result; + } + /// /// A comparer for which compares by name. /// @@ -752,11 +793,6 @@ public class Comparer : IComparer /// private static readonly Comparer Singleton = new Comparer(); - public int Compare(FieldInfoMember x, FieldInfoMember y) - { - return string.Compare(x.FieldInfo.Name, y.FieldInfo.Name, StringComparison.Ordinal); - } - /// /// Gets the singleton instance of this class. /// @@ -767,6 +803,11 @@ public static Comparer Instance return Singleton; } } + + public int Compare(FieldInfoMember x, FieldInfoMember y) + { + return string.Compare(x.FieldInfo.Name, y.FieldInfo.Name, StringComparison.Ordinal); + } } } } diff --git a/src/OrleansCodeGenerator/Utilities/SyntaxFactoryExtensions.cs b/src/OrleansCodeGenerator/Utilities/SyntaxFactoryExtensions.cs index 8637a076fa..2a1c8d209d 100644 --- a/src/OrleansCodeGenerator/Utilities/SyntaxFactoryExtensions.cs +++ b/src/OrleansCodeGenerator/Utilities/SyntaxFactoryExtensions.cs @@ -67,8 +67,9 @@ public static TypeSyntax GetTypeSyntax( return SyntaxFactory.ParseTypeName( type.GetParseableName( - includeNamespace: includeNamespace, - includeGenericParameters: includeGenericParameters)); + new TypeFormattingOptions( + includeNamespace: includeNamespace, + includeGenericParameters: includeGenericParameters))); } /// @@ -85,7 +86,9 @@ public static TypeSyntax GetTypeSyntax( /// public static NameSyntax GetNameSyntax(this Type type, bool includeNamespace = true) { - return SyntaxFactory.ParseName(type.GetParseableName(includeNamespace: includeNamespace)); + return + SyntaxFactory.ParseName( + type.GetParseableName(new TypeFormattingOptions(includeNamespace: includeNamespace))); } /// @@ -124,7 +127,9 @@ public static SimpleNameSyntax GetNameSyntax(this MethodInfo method) public static ArrayTypeSyntax GetArrayTypeSyntax(this Type type, bool includeNamespace = true) { return - SyntaxFactory.ArrayType(SyntaxFactory.ParseTypeName(type.GetParseableName(includeNamespace: includeNamespace))) + SyntaxFactory.ArrayType( + SyntaxFactory.ParseTypeName( + type.GetParseableName(new TypeFormattingOptions(includeNamespace: includeNamespace)))) .AddRankSpecifiers( SyntaxFactory.ArrayRankSpecifier().AddSizes(SyntaxFactory.OmittedArraySizeExpression())); } @@ -235,15 +240,15 @@ public static ParameterSyntax[] GetParameterListSyntax(this ConstructorInfo cons /// /// Returns type constraint syntax for the provided generic type argument. /// - /// + /// /// The type. /// /// /// Type constraint syntax for the provided generic type argument. /// - public static TypeParameterConstraintClauseSyntax[] GetTypeConstraintSyntax(this Type genericTypeArgument) + public static TypeParameterConstraintClauseSyntax[] GetTypeConstraintSyntax(this Type type) { - var typeInfo = genericTypeArgument.GetTypeInfo(); + var typeInfo = type.GetTypeInfo(); if (typeInfo.IsGenericTypeDefinition) { var constraints = new List(); @@ -251,6 +256,8 @@ public static TypeParameterConstraintClauseSyntax[] GetTypeConstraintSyntax(this { var parameterConstraints = new List(); var attributes = genericParameter.GenericParameterAttributes; + + // The "class" or "struct" constraints must come first. if (attributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint)) { parameterConstraints.Add(SyntaxFactory.ClassOrStructConstraint(SyntaxKind.ClassConstraint)); @@ -260,12 +267,21 @@ public static TypeParameterConstraintClauseSyntax[] GetTypeConstraintSyntax(this parameterConstraints.Add(SyntaxFactory.ClassOrStructConstraint(SyntaxKind.StructConstraint)); } + // Follow with the base class or interface constraints. foreach (var genericType in genericParameter.GetGenericParameterConstraints()) { + // If the "struct" constraint was specified, skip the corresponding "ValueType" constraint. + if (genericType == typeof(ValueType)) + { + continue; + } + parameterConstraints.Add(SyntaxFactory.TypeConstraint(genericType.GetTypeSyntax())); } - if (attributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint)) + // The "new()" constraint must be the last constraint in the sequence. + if (attributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint) + && !attributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint)) { parameterConstraints.Add(SyntaxFactory.ConstructorConstraint()); } diff --git a/src/OrleansRuntime/ReminderService/InMemoryRemindersTable.cs b/src/OrleansRuntime/ReminderService/InMemoryRemindersTable.cs index 27ac8b2b06..284f3757ce 100644 --- a/src/OrleansRuntime/ReminderService/InMemoryRemindersTable.cs +++ b/src/OrleansRuntime/ReminderService/InMemoryRemindersTable.cs @@ -25,7 +25,6 @@ The above copyright notice and this permission notice shall be included in all c using System.Collections.Generic; using System.Linq; - namespace Orleans.Runtime.ReminderService { [Serializable] @@ -41,8 +40,8 @@ internal class InMemoryRemindersTable // enable after adding updates ... even then, you will probably only need etags per row, not the whole // table version, as each read/insert/update should touch & depend on only one row at a time //internal TableVersion TableVersion; - - [NonSerialized] + + [NonSerialized] private readonly TraceLogger logger = TraceLogger.GetLogger("InMemoryReminderTable", TraceLogger.LoggerType.Runtime); public InMemoryRemindersTable() @@ -54,10 +53,10 @@ public ReminderTableData ReadRows(GrainReference grainRef) { Dictionary reminders; reminderTable.TryGetValue(grainRef, out reminders); - return reminders == null ? new ReminderTableData() : + return reminders == null ? new ReminderTableData() : new ReminderTableData(reminders.Values.ToList()); } - + /// /// Return all rows that have their GrainReference's.GetUniformHashCode() in the range (start, end] /// @@ -68,18 +67,18 @@ public ReminderTableData ReadRows(uint begin, uint end) { var range = RangeFactory.CreateRange(begin, end); IEnumerable keys = reminderTable.Keys.Where(range.InRange); - + // is there a sleaker way of doing this in C#? var list = new List(); foreach (GrainReference k in keys) list.AddRange(reminderTable[k].Values); - - if (logger.IsVerbose3) logger.Verbose3("Selected {0} out of {1} reminders from memory for {2}. List is: {3}{4}", list.Count, reminderTable.Count, range.ToString(), + + if (logger.IsVerbose3) logger.Verbose3("Selected {0} out of {1} reminders from memory for {2}. List is: {3}{4}", list.Count, reminderTable.Count, range.ToString(), Environment.NewLine, Utils.EnumerableToString(list, e => e.ToString())); return new ReminderTableData(list); } - + /// /// Return all rows that have their GrainReference's.GetUniformHashCode() in the range (start, end] /// @@ -88,9 +87,21 @@ public ReminderTableData ReadRows(uint begin, uint end) /// public ReminderEntry ReadRow(GrainReference grainRef, string reminderName) { - ReminderEntry r = reminderTable[grainRef][reminderName]; - if (logger.IsVerbose3) logger.Verbose3("Read for grain {0} reminder {1} row {2}", grainRef, reminderName, r.ToString()); - return r; + ReminderEntry result = null; + Dictionary reminders; + if (reminderTable.TryGetValue(grainRef, out reminders)) + { + reminders.TryGetValue(reminderName, out result); + } + + if (logger.IsVerbose3) + { + if (result == null) + logger.Verbose3("Reminder not found for grain {0} reminder {1} ", grainRef, reminderName); + else + logger.Verbose3("Read for grain {0} reminder {1} row {2}", grainRef, reminderName, result.ToString()); + } + return result; } public string UpsertRow(ReminderEntry entry) @@ -111,7 +122,7 @@ public string UpsertRow(ReminderEntry entry) if (logger.IsVerbose3) logger.Verbose3("Upserted entry {0}, replaced {1}", entry, old); return entry.ETag; } - + /// /// Remove a row from the table /// @@ -124,7 +135,7 @@ public bool RemoveRow(GrainReference grainRef, string reminderName, string eTag) Dictionary data = null; ReminderEntry e = null; - // assuming the calling grain executes one call at a time, so no need to lock + // assuming the calling grain executes one call at a time, so no need to lock if (!reminderTable.TryGetValue(grainRef, out data)) { logger.Info("1"); @@ -160,7 +171,7 @@ public ReminderTableData ReadAll() foreach (GrainReference k in reminderTable.Keys) { list.AddRange(reminderTable[k].Values); - } + } return new ReminderTableData(list); } diff --git a/src/OrleansRuntime/ReminderService/LocalReminderService.cs b/src/OrleansRuntime/ReminderService/LocalReminderService.cs index 9646d6cb0c..3c5edfdb83 100644 --- a/src/OrleansRuntime/ReminderService/LocalReminderService.cs +++ b/src/OrleansRuntime/ReminderService/LocalReminderService.cs @@ -59,10 +59,10 @@ private enum ReminderServiceStatus private readonly TimeSpan initTimeout; internal LocalReminderService( - SiloAddress addr, - GrainId id, - IConsistentRingProvider ring, - OrleansTaskScheduler localScheduler, + SiloAddress addr, + GrainId id, + IConsistentRingProvider ring, + OrleansTaskScheduler localScheduler, IReminderTable reminderTable, GlobalConfiguration config, TimeSpan initTimeout) @@ -128,7 +128,7 @@ public Task Stop() } foreach (LocalReminderData r in localReminders.Values) r.StopReminder(logger); - + // for a graceful shutdown, also handover reminder responsibilities to new owner, and update the ReminderTable // currently, this is taken care of by periodically reading the reminder table return TaskDone.Done; @@ -178,7 +178,7 @@ public async Task UnregisterReminder(IGrainReminder reminder) await DoResponsibilitySanityCheck(grainRef, "RemoveReminder"); - // it may happen that we dont have this reminder locally ... even then, we attempt to remove the reminder from the reminder + // it may happen that we dont have this reminder locally ... even then, we attempt to remove the reminder from the reminder // table ... the periodic mechanism will stop this reminder at any silo's LocalReminderService that might have this reminder locally // remove from persistent/memory store @@ -209,7 +209,7 @@ public async Task GetReminder(GrainReference grainRef, string re { if(logger.IsVerbose) logger.Verbose(ErrorCode.RS_GetReminder,"GetReminder: GrainReference={0} ReminderName={1}", grainRef.ToDetailedString(), reminderName); var entry = await reminderTable.ReadRow(grainRef, reminderName); - return entry.ToIGrainReminder(); + return entry == null ? null : entry.ToIGrainReminder(); } public async Task> GetReminders(GrainReference grainRef) @@ -278,7 +278,7 @@ private async Task ReadTableAndStartTimers(IRingRange range) ReminderTableData table = await reminderTable.ReadRows(srange.Begin, srange.End); // get all reminders, even the ones we already have // if null is a valid value, it means that there's nothing to do. - if (null == table && reminderTable is MockReminderTable) return; + if (null == table && reminderTable is MockReminderTable) return; var remindersNotInTable = new Dictionary(localReminders); // shallow copy if (logger.IsVerbose) logger.Verbose("For range {0}, I read in {1} reminders from table. LocalTableSequence {2}, CachedSequence {3}", range.ToString(), table.Reminders.Count, localTableSequence, cachedSequence); @@ -411,7 +411,7 @@ private async Task AsyncTimerCallback(object rem) { var reminder = (LocalReminderData)rem; - if (!localReminders.ContainsKey(reminder.Identity) // we have already stopped this timer + if (!localReminders.ContainsKey(reminder.Identity) // we have already stopped this timer || reminder.Timer == null) // this timer was unregistered, and is waiting to be gc-ied return; @@ -425,7 +425,7 @@ private async Task DoResponsibilitySanityCheck(GrainReference grainRef, string d { if (status != ReminderServiceStatus.Started) await startedTask.Task; - + if (!myRange.InRange(grainRef)) { logger.Warn(ErrorCode.RS_NotResponsible, "I shouldn't have received request '{0}' for {1}. It is not in my responsibility range: {2}", @@ -455,12 +455,12 @@ private class LocalReminderData private readonly TimeSpan period; private GrainReference GrainRef { get { return Identity.GrainRef; } } private string ReminderName { get { return Identity.ReminderName; } } - + internal ReminderIdentity Identity { get; private set; } internal string ETag; internal GrainTimer Timer; internal long LocalSequenceNumber; // locally, we use this for resolving races between the periodic table reader, and any concurrent local register/unregister requests - + internal LocalReminderData(ReminderEntry entry) { Identity = new ReminderIdentity(entry.GrainRef, entry.ReminderName); @@ -484,7 +484,7 @@ public void StopReminder(TraceLogger logger) { if (Timer != null) Timer.Dispose(); - + Timer = null; } @@ -536,8 +536,8 @@ public async Task OnTimerTick(AverageTimeSpanStatistic tardinessStat, TraceLogge stopwatch.Restart(); var after = DateTime.UtcNow; - if (logger.IsVerbose2) - logger.Verbose2("Tick triggered for {0}, dt {1} sec, next@~ {2}", this.ToString(), (after - before).TotalSeconds, + if (logger.IsVerbose2) + logger.Verbose2("Tick triggered for {0}, dt {1} sec, next@~ {2}", this.ToString(), (after - before).TotalSeconds, // the next tick isn't actually scheduled until we return control to // AsyncSafeTimer but we can approximate it by adding the period of the reminder // to the after time. @@ -547,7 +547,7 @@ public async Task OnTimerTick(AverageTimeSpanStatistic tardinessStat, TraceLogge { var after = DateTime.UtcNow; logger.Error( - ErrorCode.RS_Tick_Delivery_Error, + ErrorCode.RS_Tick_Delivery_Error, String.Format("Could not deliver reminder tick for {0}, next {1}.", this.ToString(), after + period), exc); // What to do with repeated failures to deliver a reminder's ticks? diff --git a/src/TestGrainInterfaces/CodegenTestInterfaces.cs b/src/TestGrainInterfaces/CodegenTestInterfaces.cs index ffe99c7219..791475b93e 100644 --- a/src/TestGrainInterfaces/CodegenTestInterfaces.cs +++ b/src/TestGrainInterfaces/CodegenTestInterfaces.cs @@ -45,6 +45,7 @@ public interface ISomeGrainWithInvocationOptions : IGrainWithIntegerKey public interface ISerializationGenerationGrain : IGrainWithIntegerKey { + Task RoundTripObject(object input); Task RoundTripStruct(SomeStruct input); Task RoundTripClass(SomeAbstractClass input); Task RoundTripInterface(ISomeInterface input); @@ -137,12 +138,34 @@ public interface ISomeInterface { int Int { get; set; } } [Serializable] public abstract class SomeAbstractClass : ISomeInterface { + [NonSerialized] + private int nonSerializedIntField; + public abstract int Int { get; set; } public List Interfaces { get; set; } public List Classes { get; set; } + [Obsolete("This field should not be serialized", true)] + public int ObsoleteIntWithError { get; set; } + + [Obsolete("This field should be serialized")] + public int ObsoleteInt { get; set; } + + public int NonSerializedInt + { + get + { + return this.nonSerializedIntField; + } + + set + { + this.nonSerializedIntField = value; + } + } + [Serializable] public enum SomeEnum { @@ -199,4 +222,11 @@ public override int GetHashCode() return base.GetHashCode(); } } + + [Serializable] + public class ClassWithStructConstraint + where T : struct + { + public T Value { get; set; } + } } \ No newline at end of file diff --git a/src/TestGrainInterfaces/IGenericInterfaces.cs b/src/TestGrainInterfaces/IGenericInterfaces.cs index c424869fca..78d61f2c81 100644 --- a/src/TestGrainInterfaces/IGenericInterfaces.cs +++ b/src/TestGrainInterfaces/IGenericInterfaces.cs @@ -188,4 +188,11 @@ public interface ILongRunningTaskGrain : IGrainWithGuidKey Task LongRunningTask(T t, TimeSpan delay); Task CallOtherLongRunningTask(ILongRunningTaskGrain target, T t, TimeSpan delay); } + + public interface IGenericGrainWithConstraints : IGrainWithStringKey where A : ICollection, new() + { + Task GetCount(); + + Task Add(B item); + } } diff --git a/src/TestGrainInterfaces/IReminderTestGrain.cs b/src/TestGrainInterfaces/IReminderTestGrain.cs new file mode 100644 index 0000000000..3c841d7356 --- /dev/null +++ b/src/TestGrainInterfaces/IReminderTestGrain.cs @@ -0,0 +1,10 @@ +using Orleans; +using System.Threading.Tasks; + +namespace UnitTests.GrainInterfaces +{ + public interface IReminderTestGrain : IGrainWithIntegerKey + { + Task IsReminderExists(string reminderName); + } +} \ No newline at end of file diff --git a/src/TestGrainInterfaces/IValueTypeTestGrain.cs b/src/TestGrainInterfaces/IValueTypeTestGrain.cs new file mode 100644 index 0000000000..071318a69d --- /dev/null +++ b/src/TestGrainInterfaces/IValueTypeTestGrain.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Orleans; +using Orleans.Concurrency; +using Orleans.Runtime.Configuration; + +namespace UnitTests.GrainInterfaces +{ + [Serializable] + public struct ValueTypeTestData + { + private readonly int intValue; + + public ValueTypeTestData(int i) + { + intValue = i; + } + + public int GetValue() + { + return intValue; + } + } + + [Serializable] + public enum TestEnum : byte + { + First, + Second, + Third + } + + [Serializable] + public enum CampaignEnemyTestType : sbyte + { + None = -1, + Brute = 0, + Enemy1, + Enemy2, + Enemy3 + } + + [Serializable] + public class ClassWithEnumTestData + { + public TestEnum EnumValue { get; set; } + public CampaignEnemyTestType Enemy { get; set; } + } + + [Serializable] + public class LargeTestData + { + public string TestString { get; set; } + private readonly bool[] boolArray; + protected Dictionary stringIntDict; + public TestEnum EnumValue { get; set; } + private readonly ClassWithEnumTestData[] classArray; + public string Description { get; set; } + + public LargeTestData() + { + boolArray = new bool[20]; + stringIntDict = new Dictionary(); + classArray = new ClassWithEnumTestData[50]; + for (var i = 0; i < 50; i++) + { + classArray[i] = new ClassWithEnumTestData(); + } + } + + public void SetBit(int n, bool value = true) + { + boolArray[n] = value; + } + public bool GetBit(int n) + { + return boolArray[n]; + } + public void SetEnemy(int n, CampaignEnemyTestType enemy) + { + classArray[n].Enemy = enemy; + } + public CampaignEnemyTestType GetEnemy(int n) + { + return classArray[n].Enemy; + } + public void SetNumber(string name, int value) + { + stringIntDict[name] = value; + } + public int GetNumber(string name) + { + return stringIntDict[name]; + } + + + // This class is not actually used anywhere. It is here to test that the serializer generator properly handles + // nested generic classes. If it doesn't, then the generated serializer for this class will fail to compile. + [Serializable] + public class NestedGeneric + { + private T myT; + private string s; + + public NestedGeneric(T t) + { + myT = t; + s = myT.ToString(); + } + + public override string ToString() + { + return s; + } + + public void SetT(T t) + { + myT = t; + s = myT.ToString(); + } + } + } + + public interface IValueTypeTestGrain : IGrainWithIntegerKey + { + Task GetStateData(); + + Task SetStateData(ValueTypeTestData d); + + Task GetEnemyType(); + } + + public interface IEnumResultGrain : IGrainWithIntegerKey + { + Task GetEnemyType(); + + Task GetConfiguration(); + } + + [Serializable] + [Immutable] + public class ImmutableType + { + private readonly int a; + private readonly int b; + + public int A { get { return a; } } + public int B { get { return b; } } + + public ImmutableType(int aval, int bval) + { + a = aval; + b = bval; + } + } + + [Serializable] + public class EmbeddedImmutable + { + public string A { get; set; } + private readonly Immutable> list; + public Immutable> B { get { return list; } } + + public EmbeddedImmutable(string a, params int[] listOfInts) + { + A = a; + var l = new List(); + l.AddRange(listOfInts); + list = new Immutable>(l); + } + } +} diff --git a/src/TestGrainInterfaces/TestGrainInterfaces.csproj b/src/TestGrainInterfaces/TestGrainInterfaces.csproj index ce9cea7f1a..a7c0da6d62 100644 --- a/src/TestGrainInterfaces/TestGrainInterfaces.csproj +++ b/src/TestGrainInterfaces/TestGrainInterfaces.csproj @@ -60,6 +60,7 @@ + @@ -72,6 +73,7 @@ + Properties\GlobalAssemblyInfo.cs diff --git a/src/TestGrains/GenericGrains.cs b/src/TestGrains/GenericGrains.cs index 936f2a3ff8..423a9bd23d 100644 --- a/src/TestGrains/GenericGrains.cs +++ b/src/TestGrains/GenericGrains.cs @@ -577,4 +577,23 @@ public Task GetRuntimeInstanceId() return Task.FromResult(RuntimeIdentity); } } + + public class GenericGrainWithContraints: Grain, IGenericGrainWithConstraints where A : ICollection, new() + { + private A collection; + + public override Task OnActivateAsync() + { + collection = new A(); + return TaskDone.Done; + } + + public Task GetCount() { return Task.FromResult(collection.Count); } + + public Task Add(B item) + { + collection.Add(item); + return TaskDone.Done; + } + } } diff --git a/src/TestGrains/ReminderTestGrain.cs b/src/TestGrains/ReminderTestGrain.cs new file mode 100644 index 0000000000..457099e876 --- /dev/null +++ b/src/TestGrains/ReminderTestGrain.cs @@ -0,0 +1,15 @@ +using Orleans; +using System.Threading.Tasks; +using UnitTests.GrainInterfaces; + +namespace UnitTests.Grains +{ + internal class ReminderTestGrain : Grain, IReminderTestGrain + { + public async Task IsReminderExists(string reminderName) + { + var reminder = await this.GetReminder(reminderName); + return reminder != null; + } + } +} \ No newline at end of file diff --git a/src/TestGrains/SerializationGenerationGrain.cs b/src/TestGrains/SerializationGenerationGrain.cs index 0fb9afe484..6da5f8aa28 100644 --- a/src/TestGrains/SerializationGenerationGrain.cs +++ b/src/TestGrains/SerializationGenerationGrain.cs @@ -33,6 +33,11 @@ namespace TestGrains using UnitTests.GrainInterfaces; public class SerializationGenerationGrain : Grain, ISerializationGenerationGrain { + public Task RoundTripObject(object input) + { + return Task.FromResult(input); + } + public Task RoundTripStruct(SomeStruct input) { return Task.FromResult(input); diff --git a/src/TestGrains/TestGrains.csproj b/src/TestGrains/TestGrains.csproj index 8f2d54a910..68dbe35935 100644 --- a/src/TestGrains/TestGrains.csproj +++ b/src/TestGrains/TestGrains.csproj @@ -68,6 +68,7 @@ + diff --git a/src/Tester/CodeGenTests/GeneratorGrainTest.cs b/src/Tester/CodeGenTests/GeneratorGrainTest.cs index 6b4129a1dc..ee249f10e9 100644 --- a/src/Tester/CodeGenTests/GeneratorGrainTest.cs +++ b/src/Tester/CodeGenTests/GeneratorGrainTest.cs @@ -54,41 +54,66 @@ public async Task CodeGenRoundTripSerialization() Assert.AreEqual(expectedStruct.GetValueWithPrivateGetter(), actualStruct.GetValueWithPrivateGetter()); // Test abstract class serialization. - var expectedAbstract = new OuterClass.SomeConcreteClass { Int = 89, String = Guid.NewGuid().ToString() }; - expectedAbstract.Classes = new List + var input = new OuterClass.SomeConcreteClass { Int = 89, String = Guid.NewGuid().ToString() }; + input.Classes = new List { - expectedAbstract, + input, new AnotherConcreteClass { AnotherString = "hi", - Interfaces = new List { expectedAbstract } + Interfaces = new List { input } } }; - var actualAbstract = await grain.RoundTripClass(expectedAbstract); - Assert.AreEqual(expectedAbstract.Int, actualAbstract.Int); - Assert.AreEqual(expectedAbstract.String, ((OuterClass.SomeConcreteClass)actualAbstract).String); - Assert.AreEqual(expectedAbstract.Classes.Count, actualAbstract.Classes.Count); - Assert.AreEqual(expectedAbstract.String, ((OuterClass.SomeConcreteClass)actualAbstract.Classes[0]).String); - Assert.AreEqual(expectedAbstract.Classes[1].Interfaces[0].Int, actualAbstract.Classes[1].Interfaces[0].Int); + + // Set fields which should not be serialized. +#pragma warning disable 618 + input.ObsoleteInt = 38; +#pragma warning restore 618 + + input.NonSerializedInt = 39; + + var output = await grain.RoundTripClass(input); + + Assert.AreEqual(input.Int, output.Int); + Assert.AreEqual(input.String, ((OuterClass.SomeConcreteClass)output).String); + Assert.AreEqual(input.Classes.Count, output.Classes.Count); + Assert.AreEqual(input.String, ((OuterClass.SomeConcreteClass)output.Classes[0]).String); + Assert.AreEqual(input.Classes[1].Interfaces[0].Int, output.Classes[1].Interfaces[0].Int); + +#pragma warning disable 618 + Assert.AreEqual(input.ObsoleteInt, output.ObsoleteInt); +#pragma warning restore 618 + + Assert.AreEqual(0, output.NonSerializedInt); // Test abstract class serialization with state. - await grain.SetState(expectedAbstract); - actualAbstract = await grain.GetState(); - Assert.AreEqual(expectedAbstract.Int, actualAbstract.Int); - Assert.AreEqual(expectedAbstract.String, ((OuterClass.SomeConcreteClass)actualAbstract).String); - Assert.AreEqual(expectedAbstract.Classes.Count, actualAbstract.Classes.Count); - Assert.AreEqual(expectedAbstract.String, ((OuterClass.SomeConcreteClass)actualAbstract.Classes[0]).String); - Assert.AreEqual(expectedAbstract.Classes[1].Interfaces[0].Int, actualAbstract.Classes[1].Interfaces[0].Int); + await grain.SetState(input); + output = await grain.GetState(); + Assert.AreEqual(input.Int, output.Int); + Assert.AreEqual(input.String, ((OuterClass.SomeConcreteClass)output).String); + Assert.AreEqual(input.Classes.Count, output.Classes.Count); + Assert.AreEqual(input.String, ((OuterClass.SomeConcreteClass)output.Classes[0]).String); + Assert.AreEqual(input.Classes[1].Interfaces[0].Int, output.Classes[1].Interfaces[0].Int); +#pragma warning disable 618 + Assert.AreEqual(input.ObsoleteInt, output.ObsoleteInt); +#pragma warning restore 618 + Assert.AreEqual(0, output.NonSerializedInt); // Test interface serialization. - var expectedInterface = expectedAbstract; + var expectedInterface = input; var actualInterface = await grain.RoundTripInterface(expectedInterface); - Assert.AreEqual(expectedAbstract.Int, actualInterface.Int); + Assert.AreEqual(input.Int, actualInterface.Int); // Test enum serialization. const SomeAbstractClass.SomeEnum ExpectedEnum = SomeAbstractClass.SomeEnum.Something; var actualEnum = await grain.RoundTripEnum(ExpectedEnum); Assert.AreEqual(ExpectedEnum, actualEnum); + + // Test serialization of a generic class which has a value-type constraint. + var expectedStructConstraintObject = new ClassWithStructConstraint { Value = 38 }; + var actualStructConstraintObject = + (ClassWithStructConstraint)await grain.RoundTripObject(expectedStructConstraintObject); + Assert.AreEqual(expectedStructConstraintObject.Value, actualStructConstraintObject.Value); } [TestMethod, TestCategory("BVT"), TestCategory("Functional"), TestCategory("GetGrain")] diff --git a/src/Tester/GenericGrainTests.cs b/src/Tester/GenericGrainTests.cs index c95f58ef5e..747e880977 100644 --- a/src/Tester/GenericGrainTests.cs +++ b/src/Tester/GenericGrainTests.cs @@ -676,5 +676,17 @@ public async Task Generic_CircularReferenceTest() var grain = GrainFactory.GetGrain(primaryKey: grainId, keyExtension: grainId.ToString("N")); var c1 = await grain.GetState(); } + + [TestMethod, TestCategory("BVT"), TestCategory("Functional"), TestCategory("Generics")] + public async Task Generic_GrainWithTypeConstraints() + { + var grainId = Guid.NewGuid().ToString(); + var grain = GrainFactory.GetGrain, int>>(grainId); + var result = await grain.GetCount(); + Assert.AreEqual(0, result); + await grain.Add(42); + result = await grain.GetCount(); + Assert.AreEqual(1, result); + } } } diff --git a/src/Tester/ReminderTest.cs b/src/Tester/ReminderTest.cs new file mode 100644 index 0000000000..47027583cc --- /dev/null +++ b/src/Tester/ReminderTest.cs @@ -0,0 +1,31 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Orleans.TestingHost; +using System.Threading.Tasks; +using UnitTests.GrainInterfaces; +using UnitTests.Tester; + +namespace Tester +{ + [TestClass] + public class ReminderTest : UnitTestSiloHost + { + public ReminderTest() + : base(new TestingSiloOptions { StartPrimary = true, StartSecondary = false }) + { + } + + [ClassCleanup] + public static void MyClassCleanup() + { + StopAllSilos(); + } + + [TestMethod, TestCategory("BVT"), TestCategory("Functional"), TestCategory("Reminders")] + public async Task SimpleGrainGetGrain() + { + IReminderTestGrain grain = GrainFactory.GetGrain(0); + bool notExists = await grain.IsReminderExists("not exists"); + Assert.IsFalse(notExists); + } + } +} \ No newline at end of file diff --git a/src/Tester/SerializationTests.cs b/src/Tester/SerializationTests.cs index 6b49eed123..656edee580 100644 --- a/src/Tester/SerializationTests.cs +++ b/src/Tester/SerializationTests.cs @@ -23,7 +23,6 @@ The above copyright notice and this permission notice shall be included in all c using System; -using System.Globalization; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -31,6 +30,7 @@ The above copyright notice and this permission notice shall be included in all c using Orleans.Serialization; using UnitTests.GrainInterfaces; using System.Collections.Generic; +using System.Globalization; using Orleans.TestingHost; using System.Runtime.Serialization; @@ -182,6 +182,69 @@ public void SerializationTests_JObject_Example1() Assert.AreEqual(input.ToString(), output.ToString()); } + [TestMethod, TestCategory("Functional"), TestCategory("Serialization"), TestCategory("JSON")] + [ExpectedException(typeof(AssertFailedException))] + public void SerializationTests_Json_Guid_WithoutConverter() + { + var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }; + Do_Json_Guid_Test(settings); + } + + [TestMethod, TestCategory("BVT"), TestCategory("Functional"), TestCategory("Serialization"), TestCategory("JSON")] + public void SerializationTests_Json_Guid_WithConverter() + { + var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }; + settings.Converters.Add(new GuidJsonTestConverter()); + Do_Json_Guid_Test(settings); + } + + private void Do_Json_Guid_Test(JsonSerializerSettings settings) + { + var dict = new Dictionary + { + {"key", Guid.NewGuid()}, + }; + + string dictSerialized = JsonConvert.SerializeObject(dict, settings); + var dictDeserialized = JsonConvert.DeserializeObject>(dictSerialized, settings); + var originalGuid = dict["key"]; + var deserGuid = dictDeserialized["key"]; + + Assert.AreEqual(typeof(Guid), originalGuid.GetType()); + Assert.AreEqual(typeof(Guid), deserGuid.GetType()); + Assert.AreEqual(originalGuid, deserGuid); + } + + private class GuidJsonTestConverter : JsonConverter + { + public override bool CanRead { get { return true; } } + public override bool CanWrite { get { return true; } } + + public override bool CanConvert(Type objectType) + { + return objectType.IsAssignableFrom(typeof(Guid)) || objectType.IsAssignableFrom(typeof(Guid?)); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteValue(default(string)); + } + else if (value is Guid) + { + var guid = (Guid)value; + writer.WriteValue(guid.ToString("N")); + } + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var str = reader.Value as string; + return str != null ? Guid.Parse(str) : default(Guid); + } + } + [RegisterSerializerAttribute] public class JObjectSerializationExample1 { diff --git a/src/Tester/SerializationTests/DeepCopyTests.cs b/src/Tester/SerializationTests/DeepCopyTests.cs new file mode 100644 index 0000000000..60bf4ff10d --- /dev/null +++ b/src/Tester/SerializationTests/DeepCopyTests.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Orleans.Serialization; +using Orleans.UnitTest.GrainInterfaces; +using UnitTests.GrainInterfaces; + +namespace UnitTests.Serialization +{ + /// + /// Test the deep copy of built-in and user-defined types + /// + [TestClass] + public class DeepCopyTests + { + + [TestInitialize] + public void InitializeForTesting() + { + SerializationManager.InitializeForTesting(); + } + + + [TestMethod, TestCategory("BVT"), TestCategory("Functional"), TestCategory("Serialization")] + public void DeepCopyTests_BuiltinCollections() + { + { + var original = new int[] { 0, 1, 2 }; + var copy = (int[])SerializationManager.DeepCopy(original); + copy[2] = 0; + Assert.AreEqual(original[2], 2); + } + { + var original = new int[] { 0, 1, 2 }.ToList(); + var copy = (List)SerializationManager.DeepCopy(original); + copy[2] = 0; + Assert.AreEqual(original[2], 2); + } + { + var original = new int[][] { new int[] { 0, 1 }, new int[] { 2, 3 } }; + var copy = (int[][])SerializationManager.DeepCopy(original); + copy[1][0] = 0; + Assert.AreEqual(original[1][0], 2); + } + { + var original = new Dictionary(); + original[0] = 1; + original[1] = 2; + var copy = (Dictionary)SerializationManager.DeepCopy(original); + copy[1] = 0; + Assert.AreEqual(original[1], 2); + } + { + var original = new Dictionary>(); + original["a"] = new Dictionary(); + original["a"]["0"] = "1"; + original["a"]["1"] = "2"; + var copy = (Dictionary>)SerializationManager.DeepCopy(original); + copy["a"]["1"] = ""; + Assert.AreEqual(original["a"]["1"], "2"); + } + } + + [TestMethod, TestCategory("BVT"), TestCategory("Functional"), TestCategory("Serialization")] + public void DeepCopyTests_UserDefinedType() + { + { + var original = new LargeTestData(); + original.SetNumber("a", 1); + original.SetNumber("b", 2); + original.SetEnemy(0, CampaignEnemyTestType.Enemy3); + original.SetBit(19); + var copy = (LargeTestData)SerializationManager.DeepCopy(original); + Assert.AreEqual(1, copy.GetNumber("a")); + Assert.AreEqual(2, copy.GetNumber("b")); + Assert.AreEqual(CampaignEnemyTestType.Enemy3, copy.GetEnemy(0)); + Assert.AreEqual(true, copy.GetBit(19)); + // change copy + copy.SetNumber("b", 0); + copy.SetEnemy(0, CampaignEnemyTestType.Brute); + copy.SetBit(19, false); + // original must be unchanged + Assert.AreEqual(2, original.GetNumber("b")); + Assert.AreEqual(CampaignEnemyTestType.Enemy3, original.GetEnemy(0)); + Assert.AreEqual(true, original.GetBit(19)); + } + } + + } +} diff --git a/src/Tester/Tester.csproj b/src/Tester/Tester.csproj index 005540378f..0425f5592f 100644 --- a/src/Tester/Tester.csproj +++ b/src/Tester/Tester.csproj @@ -96,7 +96,7 @@ False - $(SolutionDir)packages\WindowsAzure.Storage.5.0.0\lib\net40\Microsoft.WindowsAzure.Storage.dll + $(SolutionDir)packages\WindowsAzure.Storage.5.0.2\lib\net40\Microsoft.WindowsAzure.Storage.dll True @@ -116,12 +116,14 @@ + + diff --git a/src/Tester/packages.config b/src/Tester/packages.config index dd4c537b8c..7142035709 100644 --- a/src/Tester/packages.config +++ b/src/Tester/packages.config @@ -11,5 +11,5 @@ - + \ No newline at end of file diff --git a/src/TesterInternal/TesterInternal.csproj b/src/TesterInternal/TesterInternal.csproj index 06af97c2af..c1f3c9cffb 100644 --- a/src/TesterInternal/TesterInternal.csproj +++ b/src/TesterInternal/TesterInternal.csproj @@ -62,7 +62,7 @@ False - $(SolutionDir)packages\WindowsAzure.Storage.5.0.0\lib\net40\Microsoft.WindowsAzure.Storage.dll + $(SolutionDir)packages\WindowsAzure.Storage.5.0.2\lib\net40\Microsoft.WindowsAzure.Storage.dll True diff --git a/src/TesterInternal/packages.config b/src/TesterInternal/packages.config index f6675669cb..218eede3ec 100644 --- a/src/TesterInternal/packages.config +++ b/src/TesterInternal/packages.config @@ -7,5 +7,5 @@ - + \ No newline at end of file