-
Notifications
You must be signed in to change notification settings - Fork 934
Contextual Filter Definition Cache Key Mismatch Error #1889
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
Conversation
src/NHibernate/Cache/QueryKey.cs
Outdated
@@ -127,7 +127,7 @@ public override bool Equals(object other) | |||
} | |||
} | |||
|
|||
if (!CollectionHelper.SetEquals(_filters, that._filters)) | |||
if (!CollectionHelper.SequenceEquals(_filters, that._filters)) |
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.
They do not have the same semantic at all. SequenceEquals
cares about order of elements, not SetEquals
. Order of elements is irrelevant here, SequenceEquals
must not be used.
If SetEquals
only calls GetHashCode
in your case and not Equals
for elements supposed to be equals, it means there is a flaw in the GetHashCode
implementation causing it to not yield the same value for equals elements.
So that is the FilterKey.GetHashCode
which would need a fix, not the code here.
Can you please add a test case demonstrating the issue? We would not merge a change without a test case checking something has actually been fixed 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.
There is an other issue that i cannot figure it out;
When i write,
ISet<FilterKey> key1 = new HashSet<FilterKey>(){new FilterKey(....)};
ISet<FilterKey> key2 = new HashSet<FilterKey>(){new FilterKey(....)};
bool isEqual = key1.SetEquals(key2);
It is actually equal and returns equal and it calls FilterKey.Equals
method in HashSet dot net core code.
But NHibernate QueryKey line 130 same code does not call the Equals method.
If you try to create new SessionFilter and check with cached query you could see what I try to tell you.
And GetHashCode of the _filter
and that._filter
has different HashCodes.
ContainsAllElements(otherAsSet); called at below code of dot net core
And this Contains method just calls GetHashCode why is that?
private bool ContainsAllElements(IEnumerable<T> other)
{
foreach (T element in other)
{
if (!Contains(element))
{
return false;
}
}
return true;
}
/// <summary>
/// Checks if this and other contain the same elements. This is set equality:
/// duplicates and order are ignored
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public bool SetEquals(IEnumerable<T> other)
{
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
// a set is equal to itself
if (other == this)
{
return true;
}
HashSet<T> otherAsSet = other as HashSet<T>;
// faster if other is a hashset and we're using same equality comparer
if (otherAsSet != null && AreEqualityComparersEqual(this, otherAsSet))
{
// attempt to return early: since both contain unique elements, if they have
// different counts, then they can't be equal
if (_count != otherAsSet.Count)
{
return false;
}
// already confirmed that the sets have the same number of distinct elements, so if
// one is a superset of the other then they must be equal
return ContainsAllElements(otherAsSet);
}
else
{
ICollection<T> otherAsCollection = other as ICollection<T>;
if (otherAsCollection != null)
{
// if this count is 0 but other contains at least one element, they can't be equal
if (_count == 0 && otherAsCollection.Count > 0)
{
return false;
}
}
ElementCount result = CheckUniqueAndUnfoundElements(other, true);
return (result.uniqueCount == _count && result.unfoundCount == 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.
Below code of HashSet returns false, in NH QueryKey line 130 of CollectionsHelper class.
But I did not understand of GetHashCode login at FilterKey class so different instance of Filter has different HashCodes so it did not call equals method because hashcodes are not equal.
How can I fix it, please let me.
// Checks if this hashset contains the item
public bool Contains(T item)
{
if (_buckets != null)
{
int collisionCount = 0;
int hashCode = InternalGetHashCode(item);
Slot[] slots = _slots;
// see note at "HashSet" level describing why "- 1" appears in for loop
for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = slots[i].next)
{
if (slots[i].hashCode == hashCode && _comparer.Equals(slots[i].value, item))
{
return true;
}
if (collisionCount >= slots.Length)
{
// The chain of entries forms a loop, which means a concurrent update has happened.
throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported);
}
collisionCount++;
}
}
// either _buckets is null or wasn't found
return false;
}
… and "_filterParameters" is empty when it called. As a result wrong HashCode is calculated and stored at HashSet local variable. We need to call SequenceEqual because it just calls Equals method.
@hazzik please let me an option to fix this issue. Below code CoreDistributedCache line 232 at Deserialize calls our "FilterKey GetHashCode" method and "_filterParameters" is empty when it called. And wrong HashCode is calculated and stored at HashSet local variable.
FilterKey As a Result I think best solution is SequenceEquals because it does not care HashCode it just cares Equals method and it works like charm as i said :) I Ordered filters for your tip.
|
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 helping pushing the subject forward, the only way is still to start by adding a test case.
This is the issue that your test does not effected and succeeded but in life scenario it fails, because in your test you do not deserialize Filters you just initialize them.
Then add a test which does such an operation before comparing them. There are already many tests doing some serialization/deserialization in the test project, just look for Serialize
. Then you could add your test into NHibernate.Test.CacheTest.FilterKeyFixture
.
@@ -127,7 +128,7 @@ public override bool Equals(object other) | |||
} | |||
} | |||
|
|||
if (!CollectionHelper.SetEquals(_filters, that._filters)) | |||
if (!CollectionHelper.SequenceEquals(_filters?.OrderBy(x=>x.ToString()) , that._filters?.OrderBy(x=>x.ToString()))) |
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.
That is still a workaround, and performance wise, a not efficient one.
The root cause should be fixed instead.
I am looking into this. Re-reading your statements, it seems it is an issue with |
Replaced by #1891 |
#1888