Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for non-static serializer #3595

Merged
merged 13 commits into from
Nov 15, 2017

Conversation

benjaminpetit
Copy link
Member

With this PR:

  • Orleans generated serializers are no longer statics
  • Developers can implement their own non static serializers. Orleans will create instances with ActivatorUtilities that relies on DI. Static serialization methods are still supported.
  • Orleans.Core.Abstractions does not depends on System.Reflection.Emit.Lightweight anymore

A shortcut that I took (and that we may want to fix in the future) is that if SerializationManager is instanciated without any IServiceProvider (such as in CodeGenerator), the serializers will not be instanciated. Instead the SerializationManager will only populate the data structure needed for SerializationManager.HasSerializer<T>() (needed for codegen).

@jdom
Copy link
Member

jdom commented Oct 24, 2017

    private static MemberDeclarationSyntax[] GenerateStaticFields(List<FieldInfoMember> fields)

This and the documentation are no longer true, right? BTW, do we still need these fields? Can't they be used in the constructor as variables and then drop them or are they really needed as fields?


Refers to: src/Orleans.CodeGeneration/SerializerGenerator.cs:367 in f4b5d73. [](commit_id = f4b5d73, deletion_comment = False)

@@ -428,11 +444,12 @@ object DeserializeGeneric(Type expected, IDeserializationContext context)
}
else
{
var target = this.InitializeSerializer(serializerType);
Copy link
Member

Choose a reason for hiding this comment

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

target is missleading, as target usually refers to the target type being serialized, and in this case it is the serializer object itself

private object InitializeSerializer(Type serializerType)
{
if (this.ServiceProvider == null)
return null;
Copy link
Member

Choose a reason for hiding this comment

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

Hmmm, this shortcut can be error prone, as there are cases where it is valid to return null at runtime...

Copy link
Member Author

Choose a reason for hiding this comment

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

I could throw in the GetSerializer/Copier/Deserializer methods if the ServiceProvider wasn't set?

Copy link
Member

Choose a reason for hiding this comment

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

It's fine, since we should be removing this statement entirely in #3669 now that we'll stop using the SM for the codegen path. /cc @ReubenBond

Copy link
Member

Choose a reason for hiding this comment

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

BTW, @ReubenBond, remember to remove this and not allow a null service provider once you rebase your other PR to not use SerializationManager for codegen.

return null;

var constructors = serializerType.GetConstructors();
if (constructors == null || constructors.Length < 1)
Copy link
Member

Choose a reason for hiding this comment

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

would default constructors show up in this list even if they aren't explicitly defined in code?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. This will return an empty list only if the constructor is not accessible or the class is static.

@jdom
Copy link
Member

jdom commented Oct 24, 2017

As I mentioned in person, I'm not that fond of that shortcut, unless the alternative is too costly. I believe we are using SerializationManager mainly as a collection of registered serializers with just a tiny little bit of extra logic in the HasSerializer method. If that's the case, we could remove the dependency on it and just use a HashSet and share that small logic on how to know if there is a serializer defined already.

@@ -575,6 +594,19 @@ internal static void GetSerializationMethods(Type type, out MethodInfo copier, o
}
}

private Delegate CreateDelegate(MethodInfo methodInfo, Type delegateType, object target)
Copy link
Member

Choose a reason for hiding this comment

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

delegateType could be a generic type parameter, to avoid all the unnecessary casting on the caller side

Copy link
Member Author

Choose a reason for hiding this comment

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

I previously tried to do that, it wasn't possible to use a generic parameter and cast it in the return statement. It seems that if I use the as operator it will do the trick.

https://stackoverflow.com/questions/35321586/cannot-cast-delegate-to-a-generic-type-t

if (constructors == null || constructors.Length < 1)
return null;

var serializer = ActivatorUtilities.CreateInstance(this.ServiceProvider, serializerType);
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it's out of scope, but does it make sense to use GetServiceOrCreateInstance here instead since we expect 1 instance of each serializer?

Copy link
Member Author

@benjaminpetit benjaminpetit Nov 2, 2017

Choose a reason for hiding this comment

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

To use that, we would need to register the serializers in the IServiceProvider, correct?

I am not sure we want to register all of them, since you may have A LOT of serializers in your app

Copy link
Member

Choose a reason for hiding this comment

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

No, this means that if it is registered as a service, it will use that, otherwise falls back to CreateInstance.

Copy link
Member

Choose a reason for hiding this comment

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

BTW, are you doing this change or decided not to?

if (this.ServiceProvider == null)
return null;

if (!serializerType.GetCustomAttributes(typeof(SerializerAttribute), true).Any())
Copy link
Member

Choose a reason for hiding this comment

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

Worth adding a comment here, like:
// If the type lacks a Serializer attribute then it's a self-serializing type and all serialization methods must be static.


result.Add(
SF.FieldDeclaration(SF.VariableDeclaration(setterType).AddVariables(fieldSetterVariable))
.AddModifiers(
SF.Token(SyntaxKind.PrivateKeyword),
SF.Token(SyntaxKind.StaticKeyword),
SF.Token(SyntaxKind.ReadOnlyKeyword)));
Copy link
Member

@ReubenBond ReubenBond Oct 25, 2017

Choose a reason for hiding this comment

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

not readonly? (here and above)

@ReubenBond
Copy link
Member

It's worth adding a comment to include what the new generated code looks like, since it's hard to determine without building it.

Left some comments but otherwise LGTM.

rename SerializerGenerator.GenerateStaticFields to GenerateFields
@@ -173,6 +166,15 @@ internal IRuntimeClient RuntimeClient
RegisterSerializationProviders(serializatonProviderOptionsValue.SerializationProviders);
}

public void SetApplicationPartManager(ApplicationPartManager applicationPartManager)
Copy link
Member

Choose a reason for hiding this comment

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

How does this break the dependency cycle? Is it possible to break it in another way so that SerializationManager doesn't need an initialization step?

Copy link
Member

Choose a reason for hiding this comment

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

To be honest, logically it does make sense that there is an explicit point in time that we populate the serializer feature from the parts and feature providers, instead of relying on luck in when the container happens to resolve the SM

Copy link
Member

Choose a reason for hiding this comment

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

It's deterministic: you cannot access the serialization manager until it's created and the registrations are populated on creation. This change makes it less certain. It's not like SM is populating some state which might already be in use by some other class

Copy link
Member Author

Choose a reason for hiding this comment

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

The issue is that, if a serializer ask for a SerializationManagerin its constructor, and that we instantiate these serializer in the SerializationManager.... boom.

Copy link
Member

Choose a reason for hiding this comment

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

@benjaminpetit oh right :( Alternatively we could call the method Initialize and store the ApplicationPartManager in the ctor so that it's more obvious that it needs initialization before use.

Copy link
Member

Choose a reason for hiding this comment

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

My preference is for calling this method RegisterSerializers instead and pass the ApplicationPartManager in at this point in time, and is never stored by the SM.

In theory in the future we could call this multiple times (not that we will, nor that we should support it now, but would be applicable logically)

Copy link
Member

Choose a reason for hiding this comment

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

My intention was that registration occurs exactly once, before usage. As a result, not all of the data structures are protected by locks / concurrent collections.

// {
// [...]
// }
//}
Copy link
Member

Choose a reason for hiding this comment

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

You can use XML comments for these comments

@@ -173,6 +166,15 @@ internal IRuntimeClient RuntimeClient
RegisterSerializationProviders(serializatonProviderOptionsValue.SerializationProviders);
}

public void SetApplicationPartManager(ApplicationPartManager applicationPartManager)
Copy link
Member

Choose a reason for hiding this comment

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

would RegisterSerializers be a more appropriate name? this is not "setting the app manager for later use", it's commanding the SM to register the serializers (from the app manager) now

@jdom
Copy link
Member

jdom commented Nov 14, 2017

@dotnet-bot test functional

@@ -34,6 +34,7 @@ public static void AddDefaultServices(IClientBuilder builder, IServiceCollection
services.TryAddFromExisting<IInternalGrainFactory, GrainFactory>();
services.TryAddFromExisting<IGrainReferenceConverter, GrainFactory>();
services.TryAddSingleton<ClientProviderRuntime>();
services.TryAddSingleton<IFieldUtils>(new FieldUtils());
Copy link
Member

Choose a reason for hiding this comment

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

any reason why this is passing an instance instead of just the concrete type, as you did in DefaultSiloServices?

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, will fix that

@jdom jdom merged commit 2713356 into dotnet:master Nov 15, 2017
@benjaminpetit benjaminpetit deleted the non-static-serializers branch March 15, 2018 16:54
@github-actions github-actions bot locked and limited conversation to collaborators Dec 9, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants