Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit be48e96

Browse files
saferndanmoseley
authored andcommitted
Break hangs on HashSet when a loop is formed on entries due to a concurrent operation (#28225)
* Break hangs on HashSet when a loop is formed on entries due to a concurrent operation * PR Feedback, copy _slots to local ref * Get a copy of -slots after IncreaseCapacity changes it
1 parent d823138 commit be48e96

File tree

2 files changed

+75
-26
lines changed

2 files changed

+75
-26
lines changed

src/System.Collections/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@
7979
<data name="Argument_AddingDuplicate" xml:space="preserve">
8080
<value>An item with the same key has already been added. Key: {0}</value>
8181
</data>
82+
<data name="InvalidOperation_ConcurrentOperationsNotSupported" xml:space="preserve">
83+
<value>Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.</value>
84+
</data>
8285
<data name="InvalidOperation_EmptyQueue" xml:space="preserve">
8386
<value>Queue empty.</value>
8487
</data>

src/System.Collections/src/System/Collections/Generic/HashSet.cs

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -257,14 +257,23 @@ public bool Contains(T item)
257257
{
258258
if (_buckets != null)
259259
{
260+
int collisionCount = 0;
260261
int hashCode = InternalGetHashCode(item);
262+
Slot[] slots = _slots;
261263
// see note at "HashSet" level describing why "- 1" appears in for loop
262-
for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].next)
264+
for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = slots[i].next)
263265
{
264-
if (_slots[i].hashCode == hashCode && _comparer.Equals(_slots[i].value, item))
266+
if (slots[i].hashCode == hashCode && _comparer.Equals(slots[i].value, item))
265267
{
266268
return true;
267269
}
270+
271+
if (collisionCount >= slots.Length)
272+
{
273+
// The chain of entries forms a loop, which means a concurrent update has happened.
274+
throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported);
275+
}
276+
collisionCount++;
268277
}
269278
}
270279
// either _buckets is null or wasn't found
@@ -293,26 +302,28 @@ public bool Remove(T item)
293302
int hashCode = InternalGetHashCode(item);
294303
int bucket = hashCode % _buckets.Length;
295304
int last = -1;
296-
for (int i = _buckets[bucket] - 1; i >= 0; last = i, i = _slots[i].next)
305+
int collisionCount = 0;
306+
Slot[] slots = _slots;
307+
for (int i = _buckets[bucket] - 1; i >= 0; last = i, i = slots[i].next)
297308
{
298-
if (_slots[i].hashCode == hashCode && _comparer.Equals(_slots[i].value, item))
309+
if (slots[i].hashCode == hashCode && _comparer.Equals(slots[i].value, item))
299310
{
300311
if (last < 0)
301312
{
302313
// first iteration; update buckets
303-
_buckets[bucket] = _slots[i].next + 1;
314+
_buckets[bucket] = slots[i].next + 1;
304315
}
305316
else
306317
{
307318
// subsequent iterations; update 'next' pointers
308-
_slots[last].next = _slots[i].next;
319+
slots[last].next = slots[i].next;
309320
}
310-
_slots[i].hashCode = -1;
321+
slots[i].hashCode = -1;
311322
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
312323
{
313-
_slots[i].value = default(T);
324+
slots[i].value = default(T);
314325
}
315-
_slots[i].next = _freeList;
326+
slots[i].next = _freeList;
316327

317328
_count--;
318329
_version++;
@@ -327,6 +338,13 @@ public bool Remove(T item)
327338
}
328339
return true;
329340
}
341+
342+
if (collisionCount >= slots.Length)
343+
{
344+
// The chain of entries forms a loop, which means a concurrent update has happened.
345+
throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported);
346+
}
347+
collisionCount++;
330348
}
331349
}
332350
// either _buckets is null or wasn't found
@@ -1196,35 +1214,44 @@ private bool AddIfNotPresent(T value)
11961214

11971215
int hashCode = InternalGetHashCode(value);
11981216
int bucket = hashCode % _buckets.Length;
1199-
1200-
for (int i = _buckets[bucket] - 1; i >= 0; i = _slots[i].next)
1217+
int collisionCount = 0;
1218+
Slot[] slots = _slots;
1219+
for (int i = _buckets[bucket] - 1; i >= 0; i = slots[i].next)
12011220
{
1202-
if (_slots[i].hashCode == hashCode && _comparer.Equals(_slots[i].value, value))
1221+
if (slots[i].hashCode == hashCode && _comparer.Equals(slots[i].value, value))
12031222
{
12041223
return false;
12051224
}
1225+
1226+
if (collisionCount >= slots.Length)
1227+
{
1228+
// The chain of entries forms a loop, which means a concurrent update has happened.
1229+
throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported);
1230+
}
1231+
collisionCount++;
12061232
}
12071233

12081234
int index;
12091235
if (_freeList >= 0)
12101236
{
12111237
index = _freeList;
1212-
_freeList = _slots[index].next;
1238+
_freeList = slots[index].next;
12131239
}
12141240
else
12151241
{
1216-
if (_lastIndex == _slots.Length)
1242+
if (_lastIndex == slots.Length)
12171243
{
12181244
IncreaseCapacity();
12191245
// this will change during resize
1246+
slots = _slots;
12201247
bucket = hashCode % _buckets.Length;
12211248
}
12221249
index = _lastIndex;
12231250
_lastIndex++;
12241251
}
1225-
_slots[index].hashCode = hashCode;
1226-
_slots[index].value = value;
1227-
_slots[index].next = _buckets[bucket] - 1;
1252+
slots[index].hashCode = hashCode;
1253+
slots[index].value = value;
1254+
slots[index].next = _buckets[bucket] - 1;
12281255
_buckets[bucket] = index + 1;
12291256
_count++;
12301257
_version++;
@@ -1376,13 +1403,22 @@ private int InternalIndexOf(T item)
13761403
{
13771404
Debug.Assert(_buckets != null, "_buckets was null; callers should check first");
13781405

1406+
int collisionCount = 0;
13791407
int hashCode = InternalGetHashCode(item);
1380-
for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].next)
1408+
Slot[] slots = _slots;
1409+
for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = slots[i].next)
13811410
{
1382-
if ((_slots[i].hashCode) == hashCode && _comparer.Equals(_slots[i].value, item))
1411+
if ((slots[i].hashCode) == hashCode && _comparer.Equals(slots[i].value, item))
13831412
{
13841413
return i;
13851414
}
1415+
1416+
if (collisionCount >= slots.Length)
1417+
{
1418+
// The chain of entries forms a loop, which means a concurrent update has happened.
1419+
throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported);
1420+
}
1421+
collisionCount++;
13861422
}
13871423
// wasn't found
13881424
return -1;
@@ -1500,34 +1536,44 @@ private bool AddOrGetLocation(T value, out int location)
15001536

15011537
int hashCode = InternalGetHashCode(value);
15021538
int bucket = hashCode % _buckets.Length;
1503-
for (int i = _buckets[bucket] - 1; i >= 0; i = _slots[i].next)
1539+
int collisionCount = 0;
1540+
Slot[] slots = _slots;
1541+
for (int i = _buckets[bucket] - 1; i >= 0; i = slots[i].next)
15041542
{
1505-
if (_slots[i].hashCode == hashCode && _comparer.Equals(_slots[i].value, value))
1543+
if (slots[i].hashCode == hashCode && _comparer.Equals(slots[i].value, value))
15061544
{
15071545
location = i;
15081546
return false; //already present
15091547
}
1548+
1549+
if (collisionCount >= slots.Length)
1550+
{
1551+
// The chain of entries forms a loop, which means a concurrent update has happened.
1552+
throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported);
1553+
}
1554+
collisionCount++;
15101555
}
15111556
int index;
15121557
if (_freeList >= 0)
15131558
{
15141559
index = _freeList;
1515-
_freeList = _slots[index].next;
1560+
_freeList = slots[index].next;
15161561
}
15171562
else
15181563
{
1519-
if (_lastIndex == _slots.Length)
1564+
if (_lastIndex == slots.Length)
15201565
{
15211566
IncreaseCapacity();
15221567
// this will change during resize
1568+
slots = _slots;
15231569
bucket = hashCode % _buckets.Length;
15241570
}
15251571
index = _lastIndex;
15261572
_lastIndex++;
15271573
}
1528-
_slots[index].hashCode = hashCode;
1529-
_slots[index].value = value;
1530-
_slots[index].next = _buckets[bucket] - 1;
1574+
slots[index].hashCode = hashCode;
1575+
slots[index].value = value;
1576+
slots[index].next = _buckets[bucket] - 1;
15311577
_buckets[bucket] = index + 1;
15321578
_count++;
15331579
_version++;

0 commit comments

Comments
 (0)