-
Notifications
You must be signed in to change notification settings - Fork 925
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
Conversation
if (_uniqueInterfaces == null) | ||
_uniqueInterfaces = new HashSet<System.Type>(Interfaces); | ||
if (that._uniqueInterfaces == null) | ||
that._uniqueInterfaces = new HashSet<System.Type>(Interfaces); |
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 shall be
that._uniqueInterfaces = new HashSet<System.Type>(that.Interfaces);
(note that.
in front of Interfaces
)
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.
Damn, I should not code that late...
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.
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.
4c13e52
to
81abf6c
Compare
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. |
The TeamCity failure occured for MySql build, new test failure in 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? |
81abf6c
to
08996f4
Compare
I have made changes to tests: added direct tests on |
08996f4
to
86d116b
Compare
Updated: was re-implementing set equality, used .Net built-in method instead. And re-based. |
86d116b
to
9d81858
Compare
Rebased for resolving a conflict on test project. |
9d81858
to
383c1ee
Compare
if (that._uniqueInterfaces == null) | ||
that._uniqueInterfaces = new HashSet<System.Type>(that.Interfaces); | ||
|
||
return _uniqueInterfaces.SetEquals(that._uniqueInterfaces); |
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.
SetEquals
does not require the argument to be ISet<>
, so the second if (if (that._uniqueInterfaces == null)
) is not required
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? |
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.
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; } |
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.
As we are allowed to break everything in NH5, Please make this IReadOnlyCollection<System.Type>
without setter.
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.
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; } |
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.
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); |
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 think, what we need is presort Interfaces
(eg. interfaces.Distinct().OrderBy(x=>x.FullName).ToArray()
).
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.
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.
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.
Order mismatch can (?) happen in the calculation of hashCode
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.
Ok, no it can not, as hashCode is calculated using just xor (which is strange as there will be lots of collisions)
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.
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.)
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 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.
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.
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 || |
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 think hashCode
check is redundant here, but it's ok to leave it as is.
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 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.
About this serialization, I was wondering:
Is there a known reason for having this class serializable? It may be something inherited from the LinFu library but maybe useless for NHibernate. |
14896de
to
8e3b52a
Compare
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 |
8e3b52a
to
c9ef58d
Compare
Hum, team city build failed. Maybe using C#7 features is too early. I removed the "throw expression". |
Yep, no C#7 until #569 merged |
c9ef58d
to
82108c8
Compare
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.) |
226fef9
to
c3ce0f7
Compare
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 need to remove [Serializable]
and simplify implementation.
c3ce0f7
to
aa6e51f
Compare
Rebased and done. |
|
||
if (Interfaces.Length == 0) | ||
if (Interfaces.Count == 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.
Please change this to _uniqueInterfaces.Count == 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.
Done.
aa6e51f
to
c8a7327
Compare
Well, I just discover following test: |
Does it fail? |
No, but very likely because it does not try to serialize a session having cached some dynamic proxy. |
I will try to cause this serialization test to have a cached proxy. |
The ProxyFactory and its dependencies do not get serialized. It's all fine here. |
Ok, great, thanks. |
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.