Skip to content

Commit f162ede

Browse files
fixup! Recompute hashcodes on deserialization
Simplify it
1 parent 9b6495b commit f162ede

File tree

5 files changed

+81
-222
lines changed

5 files changed

+81
-222
lines changed

src/NHibernate/Cache/FilterKey.cs

Lines changed: 28 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4-
using System.Runtime.Serialization;
54
using NHibernate.Engine;
65
using NHibernate.Impl;
76
using NHibernate.Type;
@@ -10,81 +9,65 @@
109
namespace NHibernate.Cache
1110
{
1211
[Serializable]
13-
public class FilterKey : IDeserializationCallback
12+
public class FilterKey
1413
{
1514
private readonly string _filterName;
1615

1716
// Sets and dictionaries are populated last during deserialization, causing them to be potentially empty
18-
// during the deserialization callback. This causes them to be unreliable when used in hashcode computations.
19-
// So better serialize them as other structures and use those other structures in hashcode computation.
20-
[NonSerialized]
21-
private Dictionary<string, TypedValue> _filterParameters = new Dictionary<string, TypedValue>();
22-
private IEnumerable<KeyValuePair<string, TypedValue>> _serializedFilterParameters;
17+
// during the deserialization callback. This causes them to be unreliable when used in hashcode or equals
18+
// computations. These computations occur during the deserialization callback for example when another
19+
// serialized set or dictionary contain an instance of this class. (QueryKey was having such a dictionary
20+
// or set.)
21+
// So better serialize them as other structures, so long for Equals implementation which actually needs a
22+
// dictionary.
23+
private readonly KeyValuePair<string, TypedValue>[] _filterParameters;
2324

2425
// Since v5.2
2526
[Obsolete("Use overload taking a FilterImpl")]
2627
public FilterKey(string name, IEnumerable<KeyValuePair<string, object>> @params, IDictionary<string, IType> types)
2728
{
2829
_filterName = name;
29-
foreach (KeyValuePair<string, object> me in @params)
30-
{
31-
IType type = types[me.Key];
32-
_filterParameters[me.Key] = new TypedValue(type, me.Value);
33-
}
30+
_filterParameters = @params.Select(
31+
p => new KeyValuePair<string, TypedValue>(
32+
p.Key,
33+
new TypedValue(types[p.Key], p.Value))).ToArray();
3434
}
3535

3636
public FilterKey(FilterImpl filter)
3737
{
3838
var types = filter.FilterDefinition.ParameterTypes;
3939
_filterName = filter.Name;
40-
foreach (var me in filter.Parameters)
41-
{
42-
var type = types[me.Key];
43-
_filterParameters[me.Key] = new TypedValue(type, me.Value, filter.GetParameterSpan(me.Key) != null);
44-
}
40+
_filterParameters = filter.Parameters.Select(
41+
p => new KeyValuePair<string, TypedValue>(
42+
p.Key,
43+
new TypedValue(
44+
types[p.Key],
45+
p.Value,
46+
filter.GetParameterSpan(p.Key) != null))).ToArray();
4547
}
4648

4749
public override int GetHashCode()
4850
{
4951
var result = 13;
5052
result = 37 * result + _filterName.GetHashCode();
5153

52-
// On deserialization, _filterParameters may be not yet populated. Use _serializedFilterParameters
53-
// if it is defined.
54-
var filterParameters = _serializedFilterParameters ?? _filterParameters;
55-
result = 37 * result + CollectionHelper.GetHashCode(filterParameters, NamedParameterComparer.Instance);
54+
result = 37 * result + CollectionHelper.GetHashCode(_filterParameters, NamedParameterComparer.Instance);
5655
return result;
5756
}
5857

5958
public override bool Equals(object other)
6059
{
6160
var that = other as FilterKey;
62-
if (that == null)
63-
{
64-
return false;
65-
}
66-
if (!that._filterName.Equals(_filterName))
61+
if (that == null || !that._filterName.Equals(_filterName))
6762
return false;
6863

69-
// On deserialization, _filterParameters may be not yet populated. Use _serializedFilterParameters
70-
// if it is defined.
71-
var filterParameters = _filterParameters;
72-
if (_serializedFilterParameters != null)
73-
{
74-
filterParameters = new Dictionary<string, TypedValue>(_serializedFilterParameters.Count());
75-
foreach (var param in _serializedFilterParameters)
76-
filterParameters.Add(param.Key, param.Value);
77-
}
78-
var thatFilterParameters = that._filterParameters;
79-
if (that._serializedFilterParameters != null)
80-
{
81-
thatFilterParameters = new Dictionary<string, TypedValue>(that._serializedFilterParameters.Count());
82-
foreach (var param in that._serializedFilterParameters)
83-
thatFilterParameters.Add(param.Key, param.Value);
84-
}
85-
if (!CollectionHelper.DictionaryEquals<string, TypedValue>(thatFilterParameters, filterParameters))
86-
return false;
87-
return true;
64+
// BagEquals is less efficient than a DictionaryEquals, but serializing dictionaries causes issues on
65+
// deserialization if GetHashCode or Equals are called in its deserialization callback. And building
66+
// dictionaries on the fly will in most cases be worst than BagEquals, unless re-coding its short-circuits.
67+
return CollectionHelper.BagEquals(
68+
_filterParameters,
69+
that._filterParameters,
70+
NamedParameterComparer.Instance);
8871
}
8972

9073
public override string ToString()
@@ -105,21 +88,5 @@ public static ISet<FilterKey> CreateFilterKeys(IDictionary<string, IFilter> enab
10588
}
10689
return result;
10790
}
108-
109-
[OnSerializing]
110-
private void OnSerializing(StreamingContext context)
111-
{
112-
_serializedFilterParameters = _filterParameters.ToArray();
113-
}
114-
115-
/// <inheritdoc />
116-
public void OnDeserialization(object sender)
117-
{
118-
_filterParameters = new Dictionary<string, TypedValue>(_serializedFilterParameters.Count());
119-
foreach (var param in _serializedFilterParameters)
120-
_filterParameters.Add(param.Key, param.Value);
121-
122-
_serializedFilterParameters = null;
123-
}
12491
}
12592
}

src/NHibernate/Cache/QueryKey.cs

Lines changed: 21 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,13 @@ public class QueryKey : IDeserializationCallback
2323
private readonly int _maxRows = RowSelection.NoValue;
2424

2525
// Sets and dictionaries are populated last during deserialization, causing them to be potentially empty
26-
// during the deserialization callback. This causes them to be unreliable when used in hashcode computations.
27-
// So better also serialize them as other structures and use those other structures in hashcode computation.
28-
// We nonetheless need to also serialize them, because they are externally supplied and may use a custom
29-
// comparer, implementation, ...
30-
private readonly IDictionary<string, TypedValue> _namedParameters;
31-
private readonly ISet<FilterKey> _filters;
32-
private IEnumerable<KeyValuePair<string, TypedValue>> _serializedNamedParameters;
33-
private IEnumerable<FilterKey> _serializedFilters;
26+
// during the deserialization callback. This causes them to be unreliable when used in hashcode or equals
27+
// computations. These computations occur during the deserialization callback for example when another
28+
// serialized set or dictionary contain an instance of this class.
29+
// So better serialize them as other structures, so long for Equals implementation which actually needs a
30+
// dictionary and set.
31+
private readonly KeyValuePair<string, TypedValue>[] _namedParameters;
32+
private readonly FilterKey[] _filters;
3433

3534
private readonly CacheableResultTransformer _customTransformer;
3635
// hashcode may vary among processes, they cannot be stored and have to be re-computed after deserialization
@@ -67,8 +66,9 @@ public QueryKey(ISessionFactoryImplementor factory, SqlString queryString, Query
6766
_firstRow = RowSelection.NoValue;
6867
_maxRows = RowSelection.NoValue;
6968
}
70-
_namedParameters = queryParameters.NamedParameters;
71-
_filters = filters;
69+
70+
_namedParameters = queryParameters.NamedParameters?.ToArray();
71+
_filters = filters?.ToArray();
7272
_customTransformer = customTransformer;
7373

7474
_hashCode = ComputeHashCode();
@@ -140,35 +140,14 @@ public override bool Equals(object other)
140140
}
141141
}
142142

143-
// On deserialization, _namedParameters and _filters may be not yet populated. Use their _serialization
144-
// counterparts if they are defined.
145-
if (_serializedFilters != null || that._serializedFilters != null)
146-
{
147-
var filters = _serializedFilters ?? _filters;
148-
var thatFilters = that._serializedFilters ?? that._filters;
149-
// BagEquals is less efficient than a SetEquals, but if the sets are using a custom comparer, it
150-
// will cause less issues than rebuilding sets without the custom comparer.
151-
if (!CollectionHelper.BagEquals(filters, thatFilters))
152-
return false;
153-
}
154-
else if (!CollectionHelper.SetEquals(_filters, that._filters))
155-
{
143+
// BagEquals is less efficient than a SetEquals or DictionaryEquals, but serializing dictionaries causes
144+
// issues on deserialization if GetHashCode or Equals are called in its deserialization callback. And
145+
// building sets or dictionaries on the fly will in most cases be worst than BagEquals, unless re-coding
146+
// its short-circuits.
147+
if (!CollectionHelper.BagEquals(_filters, that._filters))
156148
return false;
157-
}
158-
159-
if (_serializedNamedParameters != null || that._serializedNamedParameters != null)
160-
{
161-
var namedParameters = _serializedNamedParameters ?? _namedParameters;
162-
var thatNamedParameters = that._serializedNamedParameters ?? that._namedParameters;
163-
// BagEquals is less efficient than a DictionaryEquals, but if the dictionaries are using a custom
164-
// comparer, it will cause less issues than rebuilding the dictionaries without the custom comparer.
165-
if (!CollectionHelper.BagEquals(namedParameters, thatNamedParameters, NamedParameterComparer.Instance))
166-
return false;
167-
}
168-
else if (!CollectionHelper.DictionaryEquals(_namedParameters, that._namedParameters))
169-
{
149+
if (!CollectionHelper.BagEquals(_namedParameters, that._namedParameters, NamedParameterComparer.Instance))
170150
return false;
171-
}
172151

173152
if (!CollectionHelper.SequenceEquals<int>(_multiQueriesFirstRows, that._multiQueriesFirstRows))
174153
{
@@ -194,16 +173,6 @@ public override int GetHashCode()
194173
public void OnDeserialization(object sender)
195174
{
196175
_hashCode = ComputeHashCode();
197-
198-
_serializedNamedParameters = null;
199-
_serializedFilters = null;
200-
}
201-
202-
[OnSerializing]
203-
private void OnSerializing(StreamingContext context)
204-
{
205-
_serializedNamedParameters = _namedParameters?.ToArray();
206-
_serializedFilters = _filters?.ToArray();
207176
}
208177

209178
public int ComputeHashCode()
@@ -214,10 +183,9 @@ public int ComputeHashCode()
214183
result = 37 * result + _firstRow.GetHashCode();
215184
result = 37 * result + _maxRows.GetHashCode();
216185

217-
// On deserialization, _namedParameters may be not yet populated. Use _serializedNamedParameters
218-
// if it is defined.
219-
var namedParameters = _serializedNamedParameters ?? _namedParameters;
220-
result = 37 * result + (namedParameters == null ? 0 : CollectionHelper.GetHashCode(namedParameters, NamedParameterComparer.Instance));
186+
result = 37 * result + (_namedParameters == null
187+
? 0
188+
: CollectionHelper.GetHashCode(_namedParameters, NamedParameterComparer.Instance));
221189

222190
for (int i = 0; i < _types.Length; i++)
223191
{
@@ -244,12 +212,9 @@ public int ComputeHashCode()
244212
}
245213
}
246214

247-
// On deserialization, _filters may be not yet populated. Use _serializedFilters
248-
// if it is defined.
249-
var filters = _serializedFilters ?? _filters;
250-
if (filters != null)
215+
if (_filters != null)
251216
{
252-
result = 37 * result + CollectionHelper.GetHashCode(filters);
217+
result = 37 * result + CollectionHelper.GetHashCode(_filters);
253218
}
254219

255220
result = 37 * result + (_customTransformer == null ? 0 : _customTransformer.GetHashCode());

src/NHibernate/Engine/AssociationKey.cs

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Runtime.Serialization;
32

43
namespace NHibernate.Engine
54
{
@@ -9,20 +8,15 @@ namespace NHibernate.Engine
98
/// is null during loading.
109
/// </summary>
1110
[Serializable]
12-
internal sealed class AssociationKey : IDeserializationCallback
11+
internal sealed class AssociationKey
1312
{
1413
private readonly EntityKey ownerKey;
1514
private readonly string propertyName;
16-
// hashcode may vary among processes, they cannot be stored and have to be re-computed after deserialization
17-
[NonSerialized]
18-
private int? _hashCode;
1915

2016
public AssociationKey(EntityKey ownerKey, string propertyName)
2117
{
2218
this.ownerKey = ownerKey;
2319
this.propertyName = propertyName;
24-
25-
_hashCode = GenerateHashCode();
2620
}
2721

2822
public override bool Equals(object that)
@@ -43,21 +37,6 @@ public override bool Equals(object that)
4337
}
4438

4539
public override int GetHashCode()
46-
{
47-
// If the object is put in a set or dictionary during deserialization, the hashcode will not yet be
48-
// computed. Compute the hashcode on the fly. So long as this happens only during deserialization, there
49-
// will be no thread safety issues. For the hashcode to be always defined after deserialization, the
50-
// deserialization callback is used.
51-
return _hashCode ?? GenerateHashCode();
52-
}
53-
54-
/// <inheritdoc />
55-
public void OnDeserialization(object sender)
56-
{
57-
_hashCode = GenerateHashCode();
58-
}
59-
60-
private int GenerateHashCode()
6140
{
6241
return ownerKey.GetHashCode() ^ propertyName.GetHashCode() ^ ownerKey.EntityName.GetHashCode();
6342
}

0 commit comments

Comments
 (0)