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

NH-3954 - Dynamic proxy cache may yield a wrong proxy #561

Merged
merged 1 commit into from
Mar 26, 2017

Conversation

fredericDelaporte
Copy link
Member

@fredericDelaporte fredericDelaporte commented Mar 3, 2017

Fixes NH-3954 - Dynamic proxy cache may yield a wrong proxy.

It relies on a dictionary cache, using a key which was implementing equals as hashcode equality.
But hascode are not unique, and two different objects, even of the same class with different properties/members values, may have the same hashcode. This is documented on msdn.

Quite unlikely, but it happened to me this night. While testing some other changes, StatelessSessionFetchingTest.DynamicFetch was unexpectedly failing. And this was because some other proxy got cached with the same hashcode as the proxy required by this test.

if (_uniqueInterfaces == null)
_uniqueInterfaces = new HashSet<System.Type>(Interfaces);
if (that._uniqueInterfaces == null)
that._uniqueInterfaces = new HashSet<System.Type>(Interfaces);
Copy link
Member

Choose a reason for hiding this comment

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

this shall be

that._uniqueInterfaces = new HashSet<System.Type>(that.Interfaces);

(note that. in front of Interfaces)

Copy link
Member Author

Choose a reason for hiding this comment

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

Damn, I should not code that late...

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed, squashed, ... I think this Serializable attribute is indeed unneeded for current usage of that class. Removing it would allow a simpler implementation. Maybe I have been too conservative here.

@fredericDelaporte
Copy link
Member Author

I forget to mention in my PR message: on Jira side in a comment, I have listed a bunch of other classes having the same defect in NHibernate. This PR does not fix them.

@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented Mar 3, 2017

The TeamCity failure occured for MySql build, new test failure in ExpressionSessionLeakTest.SessionGetsCollected.

It appears that it has not fail on the fisrt build for this PR, executed on code before the fix I have done in response to Hazzik's review.

Due to the last change being very small, I doubt this failure has something to do with the change. Is it a test case known for unexpected failures?
(Furthermore, debugging the test and setting breakpoint in ProxyCache, it appears this test does not use this feature.)

@fredericDelaporte
Copy link
Member Author

I have made changes to tests: added direct tests on Equals, set cache tests as only run explicitly since a bit unpredictable, and simplified test implementation. (Tests locally re-tested both with original code and fixed code.)

@fredericDelaporte fredericDelaporte changed the title NH-3954: wrong equality fix and test cases. NH-3954: Dynamic proxy cache may yield a wrong proxy Mar 4, 2017
@fredericDelaporte
Copy link
Member Author

Updated: was re-implementing set equality, used .Net built-in method instead. And re-based.

@fredericDelaporte fredericDelaporte changed the title NH-3954: Dynamic proxy cache may yield a wrong proxy NH-3954 - Dynamic proxy cache may yield a wrong proxy Mar 18, 2017
@fredericDelaporte
Copy link
Member Author

Rebased for resolving a conflict on test project.

if (that._uniqueInterfaces == null)
that._uniqueInterfaces = new HashSet<System.Type>(that.Interfaces);

return _uniqueInterfaces.SetEquals(that._uniqueInterfaces);
Copy link
Member

Choose a reason for hiding this comment

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

SetEquals does not require the argument to be ISet<>, so the second if (if (that._uniqueInterfaces == null)) is not required

@hazzik
Copy link
Member

hazzik commented Mar 19, 2017

There is a possibility that two instances of ProxyCacheEntry will be semantically equal (have the same base type and interfaces) but falsely declared unequal due the fact that set is not ordered, especially after deserialization. Could you please check the deserialization case?

hazzik
hazzik previously requested changes Mar 19, 2017
Copy link
Member

@hazzik hazzik left a comment

Choose a reason for hiding this comment

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

Please also implement IEquatable<>

@@ -43,15 +44,26 @@ public ProxyCacheEntry(System.Type baseType, System.Type[] interfaces)
public System.Type BaseType { get; private set; }
public System.Type[] Interfaces { get; private set; }
Copy link
Member

@hazzik hazzik Mar 19, 2017

Choose a reason for hiding this comment

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

As we are allowed to break everything in NH5, Please make this IReadOnlyCollection<System.Type> without setter.

Copy link
Member

Choose a reason for hiding this comment

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

Fixed. I was talking about setter, of cource

@@ -43,15 +44,26 @@ public ProxyCacheEntry(System.Type baseType, System.Type[] interfaces)
public System.Type BaseType { get; private set; }
Copy link
Member

@hazzik hazzik Mar 19, 2017

Choose a reason for hiding this comment

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

Please remove setter as per below.

@@ -24,6 +24,7 @@ public ProxyCacheEntry(System.Type baseType, System.Type[] interfaces)
}
BaseType = baseType;
Interfaces = interfaces ?? new System.Type[0];
_uniqueInterfaces = new HashSet<System.Type>(Interfaces);
Copy link
Member

Choose a reason for hiding this comment

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

I think, what we need is presort Interfaces (eg. interfaces.Distinct().OrderBy(x=>x.FullName).ToArray()).

Copy link
Member Author

Choose a reason for hiding this comment

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

SetEquals is not sensible to order.

This method ignores the order of elements and any duplicate elements in other.

So I do not expect any order mismatch to cause an issue.

Copy link
Member

Choose a reason for hiding this comment

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

Order mismatch can (?) happen in the calculation of hashCode

Copy link
Member

Choose a reason for hiding this comment

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

Ok, no it can not, as hashCode is calculated using just xor (which is strange as there will be lots of collisions)

Copy link
Member Author

@fredericDelaporte fredericDelaporte Mar 19, 2017

Choose a reason for hiding this comment

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

But there is the hashcode computation too. Am I wrong thinking xor is permutable? (Will yield same result whatever the evaluation order.) I am not completly sure about that, and do not find a reference for it currently. But I can not find an exemple for which it would be false. (And I have a test case which test types permutation and succeed.)

Copy link
Member Author

Choose a reason for hiding this comment

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

There is a second HashSet used on the one I have introduced, which is now useless and could be replaced by a simple List. I will change that too.

Copy link
Member Author

@fredericDelaporte fredericDelaporte Mar 20, 2017

Choose a reason for hiding this comment

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

Ok, no it can not, as hashCode is calculated using just xor (which is strange as there will be lots of collisions)

When used to combine hashcode from a set (so different objects), it should not be that bad. It is not like & or | which ends up zeroing or "one-ing" all bits. It seems it is even a common practice in Java.

Now if we want to change it for a more classical approach like multiplying by a prime then adding next value while overflowing, we will need to take care of the order.

public override bool Equals(object obj)
{
var that = obj as ProxyCacheEntry;
if (ReferenceEquals(null, that))
if (ReferenceEquals(null, that) ||
hashCode != that.hashCode ||
Copy link
Member

Choose a reason for hiding this comment

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

I think hashCode check is redundant here, but it's ok to leave it as is.

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 left it as an easy early "exit case". It will most of time speed up non equal cases, while being a very minor overhead for equal cases. I should add a comment for warning the hashcode is only good for early false yielding, not for telling it is equal.

@fredericDelaporte
Copy link
Member Author

Could you please check the deserialization case?

About this serialization, I was wondering:

I think this Serializable attribute is indeed unneeded for current usage of that class. Removing it would allow a simpler implementation. Maybe I have been too conservative here.

Is there a known reason for having this class serializable? It may be something inherited from the LinFu library but maybe useless for NHibernate.

@hazzik hazzik dismissed their stale review March 19, 2017 23:41

I'll take another look at this.

@fredericDelaporte
Copy link
Member Author

A more convoluted test case is added, with many interfaces, different order, duplicates, resolving to same set.

Code changed as suggested (with a bit more c#6/7 syntax, and more comments).

Additionally, I have tested the 5 thousands tests without this [Serializable] on ProxyEntryCache, no failure. (I have not committed this removal.)

@fredericDelaporte
Copy link
Member Author

Hum, team city build failed. Maybe using C#7 features is too early. I removed the "throw expression".

@hazzik
Copy link
Member

hazzik commented Mar 20, 2017

Yep, no C#7 until #569 merged

@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented Mar 26, 2017

Rebased (& commit reworded). I do not merge as you were willing to check if serialization support was important. (I personally do not understand in which cases we need this to be serializable. It looks to me this attribute has been put on many NHibernate classes "just in case", without actual needs.)

Copy link
Member

@hazzik hazzik left a comment

Choose a reason for hiding this comment

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

We need to remove [Serializable] and simplify implementation.

@fredericDelaporte
Copy link
Member Author

Rebased and done.


if (Interfaces.Length == 0)
if (Interfaces.Count == 0)
Copy link
Member

Choose a reason for hiding this comment

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

Please change this to _uniqueInterfaces.Count == 0

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@hazzik hazzik merged commit c8a7327 into nhibernate:master Mar 26, 2017
@fredericDelaporte fredericDelaporte deleted the NH-3954 branch March 26, 2017 23:00
@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented Mar 27, 2017

Well, I just discover following test: Legacy.MasterDetailTests.Serialize. It does serialize a whole ISession, deserialize it and goes on using it... So if we want to maintain this possibility, cache entries should remain serializable. But I would still keep a simplified implementation of it, the hashtable can be serialized. I would just add back that [Serializable] attribute.

@hazzik
Copy link
Member

hazzik commented Mar 27, 2017

Does it fail?

@fredericDelaporte
Copy link
Member Author

No, but very likely because it does not try to serialize a session having cached some dynamic proxy.

@fredericDelaporte
Copy link
Member Author

I will try to cause this serialization test to have a cached proxy.

@hazzik
Copy link
Member

hazzik commented Mar 27, 2017

The ProxyFactory and its dependencies do not get serialized. It's all fine here.

@fredericDelaporte
Copy link
Member Author

Ok, great, thanks.

@fredericDelaporte fredericDelaporte added this to the 5.0.0 milestone Apr 1, 2017
@hazzik hazzik added the r: Fixed label Aug 3, 2017
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