Skip to content

Fix static proxy serialization #1711

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

Merged

Conversation

fredericDelaporte
Copy link
Member

@fredericDelaporte fredericDelaporte commented May 28, 2018

The static proxy serialization logic causes a regression compared to the previous dynamic proxy serialization: it loses the initialized state of the proxy, causing it to be always un-initialized after de-serialization.

This causes a performance loss for session serialization cases, since all proxies will lazily initialize if used, even if prior to serialization they were already initialized. This causes some strange behavior for session serialization cases, since all proxies will be restored flagged as un-initialized, even if prior to serialization they were already initialized. But those previously loaded will, on demand, initialize without causing a round-trip to the database, so there is no performance loss here. Still it would be better to keep a coherent state of the proxies in regard to the entities actually loaded in the session.

This causes a failure in case of direct serialization of an initialized proxy, since after deserialization, if used, it will attempt to lazily initialize and it will fail, being moreover no more connected to a session. Previously, it would have still been initialized and it would not have tried to lazily initialize on use.

Additionally, the static proxy deserialization constructor implementation always fails PeVerify. This implementation is not really used since deserialization is delegated to an object reference, but still, better generate assemblies not failing PeVerify whenever possible.

if (baseConstructor == null || baseConstructor.IsPrivate || baseConstructor.IsAssembly)
baseConstructor = ObjectConstructor;
IL.Emit(OpCodes.Ldarg_0);
IL.Emit(OpCodes.Call, baseConstructor);
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 have checked PeVerify with following test added in PeVerifyFixture:

[Test]
public void VerifyStaticProxy()
{
	var proxyBuilderType = typeof(StaticProxyFactory).Assembly.GetType("NHibernate.Proxy.NHibernateProxyBuilder", true);
	var proxyBuilder = proxyBuilderType.GetMethod("CreateProxyType");
	Assert.That(proxyBuilder, Is.Not.Null, "Failed to find method CreateProxyType");
	var proxyBuilderAssemblyBuilder = proxyBuilderType.GetField("ProxyAssemblyBuilder", BindingFlags.NonPublic | BindingFlags.Static);
	Assert.That(proxyBuilderAssemblyBuilder, Is.Not.Null, "Failed to find assembly builder field");
	var builderCtor = proxyBuilderType.GetConstructor(
		new[] { typeof(MethodInfo), typeof(MethodInfo), typeof(IAbstractComponentType), typeof(bool) });
	Assert.That(builderCtor, Is.Not.Null, "Failed to find builder ctor");
	var builder = builderCtor.Invoke(new object[] { null, null, null, false });

	var assemblyBuilder = new SavingProxyAssemblyBuilder(assemblyName);
	var backupAssemblyBuilder = proxyBuilderAssemblyBuilder.GetValue(null);
	proxyBuilderAssemblyBuilder.SetValue(null, assemblyBuilder);
	try
	{
		proxyBuilder.Invoke(builder, new object[] { typeof(ClassWithPublicDefaultConstructor), new[] { typeof(INHibernateProxy) } });
	}
	finally
	{
		proxyBuilderAssemblyBuilder.SetValue(null, backupAssemblyBuilder);
	}

	new PeVerifier($"{assemblyName}.dll").AssertIsValid();
}

I have not added it in the PR as testing this would be much easier with #1709, without needs to depend on dynamic proxies test classes.

@hazzik
Copy link
Member

hazzik commented May 28, 2018

As I understand this could only happen if someone decides to serialise a proxy object directly as you do in the test cases?

@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented May 29, 2018

Yes, the described proxy failure case can only happen in direct serialization cases. (Anyone still using remoting?) The performance loss happen in session serialization case.

(Rebased for squashing third commit in first, and re-triggering TC builds which have timed-out.)

@fredericDelaporte fredericDelaporte merged commit 3dc86f3 into nhibernate:5.1.x May 30, 2018
@fredericDelaporte fredericDelaporte deleted the ProxySerialization branch May 30, 2018 10:36
@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented May 31, 2018

The performance loss happen in session serialization case.

In fact not, after further rechecking. I have changed the description about that point to:

This causes some strange behavior for session serialization cases, since all proxies will be restored flagged as un-initialized, even if prior to serialization they were already initialized. But those previously loaded will, on demand, initialize without causing a round-trip to the database, so there is no performance loss here. Still it would be better to keep a coherent state of the proxies in regard to the entities actually loaded in the session.

And in case someone wonder if having the proxies serializing their target entity along with the persistence context serializing all entities and all proxies would cause the deserialized proxy to have a distinct instance from the one enlisted in the persistence context, this is not the cause. Binary serialization in .Net takes care of instances uniqueness, and restores objects which were holding references to a common instance in such a way that they still shares their common reference after deserialization.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants