-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Conversation
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); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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))); |
There was a problem hiding this comment.
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)
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 SerializationManager
in its constructor, and that we instantiate these serializer in the SerializationManager
.... boom.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
// { | ||
// [...] | ||
// } | ||
//} |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
@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()); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, will fix that
With this PR:
ActivatorUtilities
that relies on DI. Static serialization methods are still supported.System.Reflection.Emit.Lightweight
anymoreA shortcut that I took (and that we may want to fix in the future) is that if
SerializationManager
is instanciated without anyIServiceProvider
(such as inCodeGenerator
), the serializers will not be instanciated. Instead theSerializationManager
will only populate the data structure needed forSerializationManager.HasSerializer<T>()
(needed for codegen).