-
Notifications
You must be signed in to change notification settings - Fork 934
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
New StaticProxyFactoryFactory #1451
Conversation
46477d6
to
5822c8e
Compare
At the current state of the PR, performance improvement is 16.8% on the data from #1446 |
@@ -81,10 +81,6 @@ public virtual object Invoke(MethodInfo method, object[] args, object proxy) | |||
{ | |||
return null; | |||
} | |||
else if ("get_HibernateLazyInitializer".Equals(methodName)) |
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.
We, probably, need to revert this change if we want to support [legacy] proxy factories.
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, and it looks to me there is no easy way to obsolete this code path: I do not think we can know there if the property implementation is itself proxied or not.
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 am not confortable yet about my understanding of those proxies inner working. I am not used to the emit IL stuff either. But I think it is a welcomed change.
var IL = getMethod.GetILGenerator(); | ||
|
||
// This is equivalent to: | ||
// get { return (INHibernateProxy)__interceptor; } |
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.
Comment should be // get { return (ILazyInitializer)__interceptor; }
@@ -81,10 +81,6 @@ public virtual object Invoke(MethodInfo method, object[] args, object proxy) | |||
{ | |||
return null; | |||
} | |||
else if ("get_HibernateLazyInitializer".Equals(methodName)) |
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, and it looks to me there is no easy way to obsolete this code path: I do not think we can know there if the property implementation is itself proxied or not.
Ok, I've finally come up with what I want to achieve with this PR. As all method implementations are known at the compile time, it would much beneficial to generate "static" proxies. This means that there will be no interceptors involved. This will allow removing the unnecessary overhead of a heavy reflection manipulations. The public classes and interfaces from DynamicProxy namespace will remain unchanged but marked as obsoleted in 5.1 and removed in 6.0. This will allow clients who use custom proxy factories with parts of our implementations to migrate/update their changes. We will also need to update NHibernateProxyGenerator (NHPG) to use the new approach. |
Finally was able to run a benchmark. With the current state of the PR memory usage has dropped to
And execution time has fell for about 30% (2s vs 2.6s on my laptop) |
bae9e88
to
eec3d10
Compare
Final number:
|
var result = Activator.CreateInstance(proxyType); | ||
var proxy = (IProxy) result; | ||
proxy.Interceptor = new DefaultDynamicLazyFieldInterceptor(); | ||
return result; |
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.
Changes to this method need to be reverted for now.
@@ -10,8 +11,9 @@ namespace NHibernate.Proxy | |||
/// <summary> | |||
/// Convenient common implementation for ProxyFactory | |||
/// </summary> | |||
public abstract class AbstractProxyFactory: IProxyFactory | |||
public abstract class AbstractProxyFactory: IProxyFactory, ISerializable |
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 easiest (and I think the correct) way to serialize a proxy was to serialize its proxy factory.
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.
Adding the ISerializable
interface to a public non sealed type is a gray area breaking change in my opinion, because all descendant need then to implement the deserialization constructor (and may need to extend the get data method for including their own additional data).
It is not binary breaking though, so maybe it can be accepted for a minor.
We should at least flag this as a possible breaking change. (I have just created the label for #1461.) In release notes we would have to state that descendants of AbstractProxyFactory
must now provide the deserialization constructor and need to override GetObjectData
if they have additional state data to serialize.
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...
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 now realize this point is a possible breaking change: static proxies are serialized without their entity state, even if they were initialized. They are always de-serialized in un-initialized state.
So, for proxies de-serialized by a session de-serialization, using them will always triggers a lazy load even if they were serialized while already being loaded. It looks to me dynamic proxies would not trigger a lazy load in that case.
For proxies directly serialized/de-serialized, with dynamic proxies their de-serialized state should be usable if they were initialized before serialization, while static proxies would be always un-initialized and un-usable because no more bound to a session.
Maybe a way to get the initializer _target
instance should be added, for serializing it along the other proxy data, thus allowing to supply it back to the proxy on deserialization.
[SecurityCritical] | ||
public object GetRealObject(StreamingContext context) | ||
{ | ||
return _proxyFactory.GetProxy(_identifier, 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.
null
here is for ISessionImplementation
. On deserialization, the PersistanceContext
will re-associate the session with the proxy. The proxy does support having a null
session.
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.
If the code previously was never calling this method with null
as the session parameter (that seems to be the case), it may be a breaking changed for a custom IProxyFactory
. The contract does not state anything explicit about this parameter nullability. Should we care for implementors who would not support receiving it 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.
it may be a breaking changed for a custom
IProxyFactory
In this codepath there will be no custom IProxyFactory
.
The LazyInitializer underneath does support accepting null
.
So, answer is no.
@hazzik Well done! I will try to find some time to review it properly this week (I sing in a choir, and it's Christmas time, so like six rehearsals this week.. :) ) |
@@ -48,9 +49,9 @@ public void TypeInequality() | |||
[Test] | |||
public void InterfaceEquality() | |||
{ | |||
var entry1 = new ProxyCacheEntry(typeof(Entity1), new[] { typeof(INHibernateProxy), typeof(IProxy) }); | |||
var entry1 = new ProxyCacheEntry(typeof(Entity1), new[] { typeof(INHibernateProxy), typeof(IFieldInterceptorAccessor) }); |
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.
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 not “not fully done” it’s simply “not done”. Yeah, going to rebase on #1454 then cleanup
using NHibernate.Proxy; | ||
using NHibernate.Proxy.DynamicProxy; | ||
using NUnit.Framework; | ||
|
||
namespace NHibernate.Test.NHSpecificTest.NH3954 | ||
{ | ||
[TestFixture, Explicit("Demonstrates bug impact on cache, but which tests will fail is a bit unpredictable")] | ||
[TestFixture, Explicit("Demonstrates bug impact on cache, but which tests will fail is a bit unpredictable"), Obsolete] |
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.
Same as previous comment, same for IProxyCache
, ProxyCache
, ProxyCacheEntry
, ProxyFactory
and some other places.
@@ -10,8 +11,9 @@ namespace NHibernate.Proxy | |||
/// <summary> | |||
/// Convenient common implementation for ProxyFactory | |||
/// </summary> | |||
public abstract class AbstractProxyFactory: IProxyFactory | |||
public abstract class AbstractProxyFactory: IProxyFactory, ISerializable |
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.
Adding the ISerializable
interface to a public non sealed type is a gray area breaking change in my opinion, because all descendant need then to implement the deserialization constructor (and may need to extend the get data method for including their own additional data).
It is not binary breaking though, so maybe it can be accepted for a minor.
We should at least flag this as a possible breaking change. (I have just created the label for #1461.) In release notes we would have to state that descendants of AbstractProxyFactory
must now provide the deserialization constructor and need to override GetObjectData
if they have additional state data to serialize.
{ | ||
EntityName = (string) info.GetValue(nameof(EntityName), typeof(string)); | ||
PersistentClass = (System.Type) info.GetValue(nameof(PersistentClass), typeof(System.Type)); | ||
Interfaces = (System.Type[]) info.GetValue(nameof(Interfaces), typeof(System.Type[])); |
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.
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 don’t think that proxies would even work in (all) netstandard as Reflection.Emit is a netstandard 1.x + platforms rather than a pure netstandard.
#1425 would not really help here as I’m seraializing a MethodInfo
as well.
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.
MethodInfo
in covered in #1425. I didn't think NHibernate on .netstandard worked without things like DynamicProxies. The target isn't iOS (which requires static compilation), but the targets able to run .Net Core.
Update: Is this pull request going to provide a compile-time proxy generation ability?
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.
Update: Is this pull request going to provide a compile-time proxy generation ability?
It looks to me as being the purpose of https://github.com/nhibernate/NHibernate.ProxyGenerators. I do not expect this to change. Moreover Alexander had mentioned this project will need an update after this PR.
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 have plans to include proxy generators into the core (as a tool + msbuild targets) to provide support for compile time proxies for the platforms which do not support runtime proxies. But not in this PR, and, most likely, not in this milestone.
return (INHibernateProxy) proxyInstance; | ||
var proxyBuilder = new NHibernateProxyBuilder(GetIdentifierMethod, SetIdentifierMethod, ComponentIdType, OverridesEquals); | ||
var cacheEntry = new ProxyCacheEntry(IsClassProxy ? PersistentClass : typeof(object), Interfaces); | ||
var proxyActivator = Cache.GetOrAdd(cacheEntry, pke => CreateProxyActivator(proxyBuilder, pke)); |
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.
When the proxyActivator
is retrieved from the cache, the proxyBuilder
has then be created for nothing. I think it should be inlined into the GetOrAdd()
second argument lambda.
@@ -51,5 +68,16 @@ public virtual object GetFieldInterceptionProxy(object instanceToWrap) | |||
{ | |||
throw new NotSupportedException(); | |||
} | |||
|
|||
public virtual void GetObjectData(SerializationInfo info, StreamingContext context) |
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.
Missing [SecurityCritical]
attribute.
@@ -206,7 +189,7 @@ private static ConstructorBuilder DefineConstructor(TypeBuilder typeBuilder, Sys | |||
return constructor; | |||
} | |||
|
|||
private static void ImplementGetObjectData(System.Type baseType, System.Type[] baseInterfaces, TypeBuilder typeBuilder, FieldInfo interceptorField) | |||
internal static void ImplementGetObjectData(System.Type baseType, IReadOnlyCollection<System.Type> baseInterfaces, TypeBuilder typeBuilder, FieldInfo interceptorField) |
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.
Related subject but not introduced by this PR: I wonder if in the case of dynamic code we should also add that [SecurityCritical]
attribute to GetObjectData
. I am not finding much information on the subject.
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.
We should, but I don't want to touch the DynamicProxy namespace
var parameterTypes = new[] {typeof (SerializationInfo), typeof (StreamingContext)}; | ||
|
||
MethodBuilder methodBuilder = | ||
typeBuilder.DefineMethod("GetObjectData", attributes, typeof (void), parameterTypes); |
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.
Here too, should we add SecurityCritical
?
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
@@ -257,7 +240,7 @@ private static void ImplementGetObjectData(System.Type baseType, System.Type[] b | |||
IL.Emit(OpCodes.Ret); | |||
} | |||
|
|||
private static void DefineSerializationConstructor(TypeBuilder typeBuilder, FieldInfo interceptorField, ConstructorBuilder defaultConstructor) | |||
internal static void DefineSerializationConstructor(TypeBuilder typeBuilder, FieldInfo interceptorField, ConstructorBuilder defaultConstructor) |
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.
Related subject but not introduced by this PR: Is the method body emitted here useful? Since it is supposed to be deserialized by ProxyObjectReference
. It should be implemented the same way than NHibernateProxyBuilder.ImplementDeserializationConstructor
I think.
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 don't want to touch/fix/change DynamicProxy namespace other than for major bugs. It's working, so let it be this way. The namespace itself is going to be retired in 6.0
IL.Emit(OpCodes.Ldarg_1); | ||
IL.Emit(OpCodes.Ldarg_2); | ||
|
||
IL.Emit(OpCodes.Callvirt, typeof(ISerializable).GetMethod(nameof(ISerializable.GetObjectData))); |
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.
So we have now two different ways of implementing the an IObjectReference
with proxies, since the one for DynamicProxy.ProxyFactory
does not delegate the GetObjectData
to DynamicProxy.ProxyObjectReference
. I prefer the pattern you have done for NHibernateProxyObjectReference
, but then we should align ProxyObjectReference
on the same pattern.
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 going to bother with DynamicProxy namespace. Going to obsolete it after converting lazy properties proxies to the same approach.
2575b33
to
8d4df90
Compare
I've cleaned the PR addressing some of the review comments. |
e4db57d
to
cc1222c
Compare
Now it's implemented as a completely new feature. |
e899fdb
to
6c0ec4c
Compare
- The factory generates high-efficient static proxies (no interceptor)
6c0ec4c
to
c48beb8
Compare
I am not forgetting it and was intending to look it again today anyway. |
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.
Shouldn't DefaultProxyFactoryFactory
, DefaultProxyFactory
,DefaultLazyInitializer
, BasicLazyInitializer
be obsoleted?
{ | ||
public class StaticProxyFactoryFactory : IProxyFactoryFactory | ||
{ | ||
internal static StaticProxyFactoryFactory Instance = new StaticProxyFactoryFactory(); |
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.
For enforcing the singleton pattern, I would define a private default constructor.
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.
This is a small memory optimization. I don't want to enforce the singleton pattern here. And, in fact, we cannot enforce it, as StaticProxyFactoryFactory
can be manually configured as a proxy factory factory and will require the public constructor to be creatable by reflection.
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, I forgot to think about configuration.
@@ -21,11 +21,11 @@ public sealed class ProxyFactory | |||
{ | |||
internal static readonly ConcurrentDictionary<ProxyCacheEntry, TypeInfo> _cache = new ConcurrentDictionary<ProxyCacheEntry, TypeInfo>(); | |||
|
|||
private static readonly ConstructorInfo defaultBaseConstructor = typeof(object).GetConstructor(new System.Type[0]); | |||
internal static readonly ConstructorInfo defaultBaseConstructor = typeof(object).GetConstructor(new System.Type[0]); |
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.
Since DynamicProxy
is supposed to be removed in some future further proxy refactoring, I would tend to duplicate such a field in NHibernateProxyBuilder
rather than sharing it.
Otherwise I would also consider putting it in ReflectionCache
.
Same for setType
field.
@@ -139,7 +139,7 @@ private IEnumerable<MethodInfo> GetProxiableMethods(System.Type type, IEnumerabl | |||
.Distinct(); | |||
} | |||
|
|||
private static ConstructorBuilder DefineConstructor(TypeBuilder typeBuilder, System.Type parentType) | |||
internal static ConstructorBuilder DefineConstructor(TypeBuilder typeBuilder, System.Type parentType) |
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 used outside of ProxyFactory
, could stay private.
@@ -166,7 +166,7 @@ private static ConstructorBuilder DefineConstructor(TypeBuilder typeBuilder, Sys | |||
return constructor; | |||
} | |||
|
|||
private static void ImplementGetObjectData(System.Type baseType, IReadOnlyCollection<System.Type> baseInterfaces, TypeBuilder typeBuilder, FieldInfo interceptorField) | |||
internal static void ImplementGetObjectData(System.Type baseType, IReadOnlyCollection<System.Type> baseInterfaces, TypeBuilder typeBuilder, FieldInfo interceptorField) |
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 used outside of ProxyFactory
, could stay private.
@@ -217,7 +217,7 @@ private static void ImplementGetObjectData(System.Type baseType, IReadOnlyCollec | |||
IL.Emit(OpCodes.Ret); | |||
} | |||
|
|||
private static void DefineSerializationConstructor(TypeBuilder typeBuilder, FieldInfo interceptorField, ConstructorBuilder defaultConstructor) | |||
internal static void DefineSerializationConstructor(TypeBuilder typeBuilder, FieldInfo interceptorField, ConstructorBuilder defaultConstructor) |
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 used outside of ProxyFactory
, could stay private.
} | ||
else if (_componentIdType != null && _componentIdType.IsMethodOf(method)) | ||
{ | ||
ImplementCallMethodOnEmbeddedComponentId(typeBuilder, method, lazyInitializerField); |
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 have not seen any implementation of IAbstractComponentType.IsMethodOf
yielding anything else than false
, so this code path looks untested. I know this comes from BasicLazyInitializer
logic, but was this part still relevant?
Could it be component id classes were supposed to be themselves proxified? But I do not see where this could happen. Otherwise is this something meant to allow some special method of the entity, supposed to just forward on the id component, to be called without initializing the proxy?
(On Hibernate's side, there is an implementation of IAbstractComponentType.IsMethodOf
yielding something else than false
(indeed componentTuplizer.isMethodOf(method)
), on EmbeddedComponentType
. But this override is lacking on NHibernate's side. And component tuplizer is lacking the method too on NHibernate's side. On Hibernate's side it has a non falsy implementation on PojoComponentTuplizer
. But I have not checked in which case the method
argument could actually belong to the component, and I think there are more things lacking for this feature on NHibernate's 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.
Not sure what is that, or how it's supposed to be used. I've copied this from DefaultProxyFactory. Do you think that this path needs to be removed?
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.
With what I have seen on Hibernate's side, it now looks to me as something partially ported, causing it to very likely never have any usage. Fully ascertaining this would require a bit more digging on Hibernate's side to see in which cases it serves.
I am always for removing useless code, but since it does not cause any harm, it is also not really worth it to dig more for checking it can really be removed without any risk.
So if you are not sure about it either, it is going to stay for now.
IL.Emit(OpCodes.Ldarg_0); | ||
IL.Emit(OpCodes.Call, baseConstructor); | ||
|
||
IL.Emit(OpCodes.Ldarg_0); |
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 would add before:
// __lazyInitializer == lazyInitializer;
IL.Emit(OpCodes.Ldarg_1); | ||
IL.Emit(OpCodes.Stfld, lazyInitializerField); | ||
|
||
IL.Emit(OpCodes.Ldarg_0); |
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 would add before:
// __proxyInfo == proxyInfo;
IL.Emit(OpCodes.Call, ReflectionCache.TypeMethods.GetTypeFromHandle); | ||
IL.Emit(OpCodes.Callvirt, ProxyFactory.setType); | ||
|
||
//this.__proxyInfo |
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 would add before:
// (new NHibernateProxyObjectReference(__proxyInfo, __lazyInitializer.Identifier)).GetObjectDataMethod(info, context);
/* | ||
set | ||
{ | ||
this.__lazyInitializer.Initialize(); |
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.
Indeed should be prepended with:
if (this.__lazyInitializer == null)
return base.<Method> (args..)
Same for next two methods.
Maybe in a next PR. |
Follow up to nhibernate#1451 for still testing NH-2622, NH-2628 and nhibernate#1389
No description provided.