Skip to content

Commit 3bcaadb

Browse files
authored
Stored the initial capacity of the ConcurrentDictionary for correctly sizing the backing array after clearing the collection. (#108065)
* Stored the initial capacity of the ConcurrentDictionary for correctly sizing the backing array after clearing the collection. * Stored the capacity in the ctor. * Used the stored capacity in Clear(). Fixes #107016 * Added a test to check the capacity logic of the ConcurrentDictionary.
1 parent 0372b50 commit 3bcaadb

File tree

2 files changed

+40
-1
lines changed

2 files changed

+40
-1
lines changed

src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public class ConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDi
4242
/// extra branch when using a custom comparer with a reference type key.
4343
/// </remarks>
4444
private readonly bool _comparerIsDefaultForClasses;
45+
/// <summary>The initial size of the _buckets array.</summary>
46+
/// <remarks>
47+
/// We store this to retain the initially specified growing behavior of the _buckets array even after clearing the collection.
48+
/// </remarks>
49+
private readonly int _initialCapacity;
4550

4651
/// <summary>The default capacity, i.e. the initial # of buckets.</summary>
4752
/// <remarks>
@@ -220,6 +225,7 @@ internal ConcurrentDictionary(int concurrencyLevel, int capacity, bool growLockA
220225

221226
_tables = new Tables(buckets, locks, countPerLock, comparer);
222227
_growLockArray = growLockArray;
228+
_initialCapacity = capacity;
223229
_budget = buckets.Length / locks.Length;
224230
}
225231

@@ -716,7 +722,7 @@ public void Clear()
716722
}
717723

718724
Tables tables = _tables;
719-
var newTables = new Tables(new VolatileNode[HashHelpers.GetPrime(DefaultCapacity)], tables._locks, new int[tables._countPerLock.Length], tables._comparer);
725+
var newTables = new Tables(new VolatileNode[HashHelpers.GetPrime(_initialCapacity)], tables._locks, new int[tables._countPerLock.Length], tables._comparer);
720726
_tables = newTables;
721727
_budget = Math.Max(1, newTables._buckets.Length / newTables._locks.Length);
722728
}

src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,25 @@ public static void TestClear()
954954
Assert.True(dictionary.IsEmpty, "TestClear: FAILED. IsEmpty returned false after Clear");
955955
}
956956

957+
[Theory]
958+
[InlineData(3)]
959+
[InlineData(1_162_687)]
960+
public static void TestCapacity(int capacity)
961+
{
962+
int itemsCount = capacity + 100;
963+
var dictionary = new ConcurrentDictionary<int, int>(1, capacity);
964+
Assert.Equal(capacity, GetCapacity(dictionary));
965+
966+
for (int i = 0; i < itemsCount; i++)
967+
dictionary.TryAdd(i, i);
968+
969+
Assert.Equal(itemsCount, dictionary.Count);
970+
Assert.InRange(GetCapacity(dictionary), capacity + 1, int.MaxValue);
971+
972+
dictionary.Clear();
973+
Assert.Equal(capacity, GetCapacity(dictionary));
974+
}
975+
957976
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
958977
public static void TestTryUpdate()
959978
{
@@ -1231,6 +1250,20 @@ public void Dictionary_NotCorruptedByNullReturningComparer()
12311250
}
12321251

12331252
#region Helper Classes and Methods
1253+
1254+
private static int GetCapacity(ConcurrentDictionary<int, int> dictionary)
1255+
{
1256+
var tables = typeof(ConcurrentDictionary<int, int>)
1257+
.GetField("_tables", BindingFlags.Instance | BindingFlags.NonPublic)!
1258+
.GetValue(dictionary);
1259+
1260+
var buckets = (ICollection)tables.GetType()
1261+
.GetField("_buckets", BindingFlags.Instance | BindingFlags.NonPublic)!
1262+
.GetValue(tables);
1263+
1264+
return buckets.Count;
1265+
}
1266+
12341267
private sealed class CreateThrowsComparer : IEqualityComparer<string>, IAlternateEqualityComparer<ReadOnlySpan<char>, string>
12351268
{
12361269
public bool Equals(string? x, string? y) => EqualityComparer<string>.Default.Equals(x, y);

0 commit comments

Comments
 (0)