diff options
author | Koundinya Veluri <kouvel@users.noreply.github.com> | 2017-09-08 17:14:09 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-08 17:14:09 -0700 |
commit | 3fc4180f8e0bf71e09a54f0f95ec7ec6a4f492f2 (patch) | |
tree | b6bb9f02ef9754c8b56d24f3ed9c53b1ef89be8d /src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs | |
parent | 3505d67c279cdb1b8231b53350a340ad34151c78 (diff) | |
download | coreclr-3fc4180f8e0bf71e09a54f0f95ec7ec6a4f492f2.tar.gz coreclr-3fc4180f8e0bf71e09a54f0f95ec7ec6a4f492f2.tar.bz2 coreclr-3fc4180f8e0bf71e09a54f0f95ec7ec6a4f492f2.zip |
Port some concurrent collection implementations with latest fixes from CoreFX into CoreCLR copies (#12939)
Diffstat (limited to 'src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs')
-rw-r--r-- | src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs | 606 |
1 files changed, 300 insertions, 306 deletions
diff --git a/src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs b/src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs index e8e99885c8..4111c5964e 100644 --- a/src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs +++ b/src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -// /*============================================================ ** +** Class: ConcurrentDictionary ** ** ** Purpose: A scalable dictionary for concurrent access @@ -12,18 +12,13 @@ ** ===========================================================*/ -using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Contracts; -using System.Runtime.InteropServices; -using System.Runtime.Serialization; -using System.Text; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading; -using System.Security; namespace System.Collections.Concurrent { @@ -46,91 +41,78 @@ namespace System.Collections.Concurrent /// Wrapping the three tables in a single object allows us to atomically /// replace all tables at once. /// </summary> - private class Tables + private sealed class Tables { - internal readonly Node[] m_buckets; // A singly-linked list for each bucket. - internal readonly object[] m_locks; // A set of locks, each guarding a section of the table. - internal volatile int[] m_countPerLock; // The number of elements guarded by each lock. - internal readonly IEqualityComparer<TKey> m_comparer; // Key equality comparer + internal readonly Node[] _buckets; // A singly-linked list for each bucket. + internal readonly object[] _locks; // A set of locks, each guarding a section of the table. + internal volatile int[] _countPerLock; // The number of elements guarded by each lock. - internal Tables(Node[] buckets, object[] locks, int[] countPerLock, IEqualityComparer<TKey> comparer) + internal Tables(Node[] buckets, object[] locks, int[] countPerLock) { - m_buckets = buckets; - m_locks = locks; - m_countPerLock = countPerLock; - m_comparer = comparer; + _buckets = buckets; + _locks = locks; + _countPerLock = countPerLock; } } - private volatile Tables m_tables; // Internal tables of the dictionary - // NOTE: this is only used for compat reasons to serialize the comparer. - // This should not be accessed from anywhere else outside of the serialization methods. - internal IEqualityComparer<TKey> m_comparer; - private readonly bool m_growLockArray; // Whether to dynamically increase the size of the striped lock - - // How many times we resized becaused of collisions. - // This is used to make sure we don't resize the dictionary because of multi-threaded Add() calls - // that generate collisions. Whenever a GrowTable() should be the only place that changes this - private int m_keyRehashCount; - - private int m_budget; // The maximum number of elements per lock before a resize operation is triggered - - // The default concurrency level is DEFAULT_CONCURRENCY_MULTIPLIER * #CPUs. The higher the - // DEFAULT_CONCURRENCY_MULTIPLIER, the more concurrent writes can take place without interference - // and blocking, but also the more expensive operations that require all locks become (e.g. table - // resizing, ToArray, Count, etc). According to brief benchmarks that we ran, 4 seems like a good - // compromise. - private const int DEFAULT_CONCURRENCY_MULTIPLIER = 4; + private volatile Tables _tables; // Internal tables of the dictionary + private IEqualityComparer<TKey> _comparer; // Key equality comparer + private readonly bool _growLockArray; // Whether to dynamically increase the size of the striped lock + private int _budget; // The maximum number of elements per lock before a resize operation is triggered // The default capacity, i.e. the initial # of buckets. When choosing this value, we are making // a trade-off between the size of a very small dictionary, and the number of resizes when // constructing a large dictionary. Also, the capacity should not be divisible by a small prime. - private const int DEFAULT_CAPACITY = 31; + private const int DefaultCapacity = 31; // The maximum size of the striped lock that will not be exceeded when locks are automatically // added as the dictionary grows. However, the user is allowed to exceed this limit by passing - // a concurrency level larger than MAX_LOCK_NUMBER into the constructor. - private const int MAX_LOCK_NUMBER = 1024; + // a concurrency level larger than MaxLockNumber into the constructor. + private const int MaxLockNumber = 1024; // Whether TValue is a type that can be written atomically (i.e., with no danger of torn reads) private static readonly bool s_isValueWriteAtomic = IsValueWriteAtomic(); - /// <summary> /// Determines whether type TValue can be written atomically /// </summary> private static bool IsValueWriteAtomic() { - Type valueType = typeof(TValue); - if (valueType.IsEnum) - { - valueType = Enum.GetUnderlyingType(valueType); - } - // // Section 12.6.6 of ECMA CLI explains which types can be read and written atomically without // the risk of tearing. // // See http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf // - bool isAtomic = - (valueType.IsClass) - || valueType == typeof(Boolean) - || valueType == typeof(Char) - || valueType == typeof(Byte) - || valueType == typeof(SByte) - || valueType == typeof(Int16) - || valueType == typeof(UInt16) - || valueType == typeof(Int32) - || valueType == typeof(UInt32) - || valueType == typeof(Single); - - if (!isAtomic && IntPtr.Size == 8) + Type valueType = typeof(TValue); + if (!valueType.IsValueType) + { + return true; + } + if (valueType.IsEnum) { - isAtomic |= valueType == typeof(Double) || valueType == typeof(Int64); + valueType = Enum.GetUnderlyingType(valueType); } - return isAtomic; + switch (Type.GetTypeCode(valueType)) + { + case TypeCode.Boolean: + case TypeCode.Byte: + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.SByte: + case TypeCode.Single: + case TypeCode.UInt16: + case TypeCode.UInt32: + return true; + case TypeCode.Int64: + case TypeCode.Double: + case TypeCode.UInt64: + return IntPtr.Size == 8; + default: + return false; + } } /// <summary> @@ -139,7 +121,7 @@ namespace System.Collections.Concurrent /// class that is empty, has the default concurrency level, has the default initial capacity, and /// uses the default comparer for the key type. /// </summary> - public ConcurrentDictionary() : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, EqualityComparer<TKey>.Default) { } + public ConcurrentDictionary() : this(DefaultConcurrencyLevel, DefaultCapacity, true, null) { } internal ConcurrentDictionary(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer<TKey> comparer) { @@ -151,7 +133,6 @@ namespace System.Collections.Concurrent { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ConcurrentDictionary_CapacityMustNotBeNegative); } - if (comparer == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparer); // The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard // any buckets. @@ -168,13 +149,13 @@ namespace System.Collections.Concurrent int[] countPerLock = new int[locks.Length]; Node[] buckets = new Node[capacity]; - m_tables = new Tables(buckets, locks, countPerLock, comparer); + _tables = new Tables(buckets, locks, countPerLock); - m_growLockArray = growLockArray; - m_budget = buckets.Length / locks.Length; + _comparer = comparer ?? EqualityComparer<TKey>.Default; + _growLockArray = growLockArray; + _budget = buckets.Length / locks.Length; } - /// <summary> /// Attempts to add the specified key and value to the <see cref="ConcurrentDictionary{TKey, /// TValue}"/>. @@ -191,9 +172,9 @@ namespace System.Collections.Concurrent /// contains too many elements.</exception> public bool TryAdd(TKey key, TValue value) { - if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + if (key == null) ThrowKeyNullException(); TValue dummy; - return TryAddInternal(key, value, false, true, out dummy); + return TryAddInternal(key, _comparer.GetHashCode(key), value, false, true, out dummy); } /// <summary> @@ -208,7 +189,7 @@ namespace System.Collections.Concurrent /// (Nothing in Visual Basic).</exception> public bool ContainsKey(TKey key) { - if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + if (key == null) ThrowKeyNullException(); TValue throwAwayValue; return TryGetValue(key, out throwAwayValue); @@ -228,7 +209,7 @@ namespace System.Collections.Concurrent /// (Nothing in Visual Basic).</exception> public bool TryRemove(TKey key, out TValue value) { - if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + if (key == null) ThrowKeyNullException(); return TryRemoveInternal(key, out value, false, default(TValue)); } @@ -246,34 +227,33 @@ namespace System.Collections.Concurrent [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] private bool TryRemoveInternal(TKey key, out TValue value, bool matchValue, TValue oldValue) { + int hashcode = _comparer.GetHashCode(key); while (true) { - Tables tables = m_tables; - - IEqualityComparer<TKey> comparer = tables.m_comparer; + Tables tables = _tables; int bucketNo, lockNo; - GetBucketAndLockNo(comparer.GetHashCode(key), out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length); + GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables._buckets.Length, tables._locks.Length); - lock (tables.m_locks[lockNo]) + lock (tables._locks[lockNo]) { // If the table just got resized, we may not be holding the right lock, and must retry. - // This should be a rare occurence. - if (tables != m_tables) + // This should be a rare occurrence. + if (tables != _tables) { continue; } Node prev = null; - for (Node curr = tables.m_buckets[bucketNo]; curr != null; curr = curr.m_next) + for (Node curr = tables._buckets[bucketNo]; curr != null; curr = curr._next) { - Assert((prev == null && curr == tables.m_buckets[bucketNo]) || prev.m_next == curr); + Debug.Assert((prev == null && curr == tables._buckets[bucketNo]) || prev._next == curr); - if (comparer.Equals(curr.m_key, key)) + if (hashcode == curr._hashcode && _comparer.Equals(curr._key, key)) { if (matchValue) { - bool valuesMatch = EqualityComparer<TValue>.Default.Equals(oldValue, curr.m_value); + bool valuesMatch = EqualityComparer<TValue>.Default.Equals(oldValue, curr._value); if (!valuesMatch) { value = default(TValue); @@ -283,15 +263,15 @@ namespace System.Collections.Concurrent if (prev == null) { - Volatile.Write<Node>(ref tables.m_buckets[bucketNo], curr.m_next); + Volatile.Write<Node>(ref tables._buckets[bucketNo], curr._next); } else { - prev.m_next = curr.m_next; + prev._next = curr._next; } - value = curr.m_value; - tables.m_countPerLock[lockNo]--; + value = curr._value; + tables._countPerLock[lockNo]--; return true; } prev = curr; @@ -319,27 +299,32 @@ namespace System.Collections.Concurrent [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] public bool TryGetValue(TKey key, out TValue value) { - if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + if (key == null) ThrowKeyNullException(); + return TryGetValueInternal(key, _comparer.GetHashCode(key), out value); + } - int bucketNo, lockNoUnused; + private bool TryGetValueInternal(TKey key, int hashcode, out TValue value) + { + Debug.Assert(_comparer.GetHashCode(key) == hashcode); + + // We must capture the _buckets field in a local variable. It is set to a new table on each table resize. + Tables tables = _tables; - // We must capture the m_buckets field in a local variable. It is set to a new table on each table resize. - Tables tables = m_tables; - IEqualityComparer<TKey> comparer = tables.m_comparer; - GetBucketAndLockNo(comparer.GetHashCode(key), out bucketNo, out lockNoUnused, tables.m_buckets.Length, tables.m_locks.Length); + int bucketNo = GetBucket(hashcode, tables._buckets.Length); // We can get away w/out a lock here. - // The Volatile.Read ensures that the load of the fields of 'n' doesn't move before the load from buckets[i]. - Node n = Volatile.Read<Node>(ref tables.m_buckets[bucketNo]); + // The Volatile.Read ensures that we have a copy of the reference to tables._buckets[bucketNo]. + // This protects us from reading fields ('_hashcode', '_key', '_value' and '_next') of different instances. + Node n = Volatile.Read<Node>(ref tables._buckets[bucketNo]); while (n != null) { - if (comparer.Equals(n.m_key, key)) + if (hashcode == n._hashcode && _comparer.Equals(n._key, key)) { - value = n.m_value; + value = n._value; return true; } - n = n.m_next; + n = n._next; } value = default(TValue); @@ -356,9 +341,9 @@ namespace System.Collections.Concurrent { AcquireAllLocks(ref locksAcquired); - Tables newTables = new Tables(new Node[DEFAULT_CAPACITY], m_tables.m_locks, new int[m_tables.m_countPerLock.Length], m_tables.m_comparer); - m_tables = newTables; - m_budget = Math.Max(1, newTables.m_buckets.Length / newTables.m_locks.Length); + Tables newTables = new Tables(new Node[DefaultCapacity], _tables._locks, new int[_tables._countPerLock.Length]); + _tables = newTables; + _budget = Math.Max(1, newTables._buckets.Length / newTables._locks.Length); } finally { @@ -400,9 +385,9 @@ namespace System.Collections.Concurrent int count = 0; - for (int i = 0; i < m_tables.m_locks.Length && count >= 0; i++) + for (int i = 0; i < _tables._locks.Length && count >= 0; i++) { - count += m_tables.m_countPerLock[i]; + count += _tables._countPerLock[i]; } if (array.Length - count < index || count < 0) //"count" itself or "count + index" can overflow @@ -434,14 +419,18 @@ namespace System.Collections.Concurrent int count = 0; checked { - for (int i = 0; i < m_tables.m_locks.Length; i++) + for (int i = 0; i < _tables._locks.Length; i++) { - count += m_tables.m_countPerLock[i]; + count += _tables._countPerLock[i]; } } - KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[count]; + if (count == 0) + { + return Array.Empty<KeyValuePair<TKey, TValue>>(); + } + KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[count]; CopyToPairs(array, 0); return array; } @@ -454,16 +443,16 @@ namespace System.Collections.Concurrent /// <summary> /// Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. /// - /// Important: the caller must hold all locks in m_locks before calling CopyToPairs. + /// Important: the caller must hold all locks in _locks before calling CopyToPairs. /// </summary> private void CopyToPairs(KeyValuePair<TKey, TValue>[] array, int index) { - Node[] buckets = m_tables.m_buckets; + Node[] buckets = _tables._buckets; for (int i = 0; i < buckets.Length; i++) { - for (Node current = buckets[i]; current != null; current = current.m_next) + for (Node current = buckets[i]; current != null; current = current._next) { - array[index] = new KeyValuePair<TKey, TValue>(current.m_key, current.m_value); + array[index] = new KeyValuePair<TKey, TValue>(current._key, current._value); index++; //this should never flow, CopyToPairs is only called when there's no overflow risk } } @@ -472,16 +461,16 @@ namespace System.Collections.Concurrent /// <summary> /// Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. /// - /// Important: the caller must hold all locks in m_locks before calling CopyToEntries. + /// Important: the caller must hold all locks in _locks before calling CopyToEntries. /// </summary> private void CopyToEntries(DictionaryEntry[] array, int index) { - Node[] buckets = m_tables.m_buckets; + Node[] buckets = _tables._buckets; for (int i = 0; i < buckets.Length; i++) { - for (Node current = buckets[i]; current != null; current = current.m_next) + for (Node current = buckets[i]; current != null; current = current._next) { - array[index] = new DictionaryEntry(current.m_key, current.m_value); + array[index] = new DictionaryEntry(current._key, current._value); index++; //this should never flow, CopyToEntries is only called when there's no overflow risk } } @@ -490,16 +479,16 @@ namespace System.Collections.Concurrent /// <summary> /// Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. /// - /// Important: the caller must hold all locks in m_locks before calling CopyToObjects. + /// Important: the caller must hold all locks in _locks before calling CopyToObjects. /// </summary> private void CopyToObjects(object[] array, int index) { - Node[] buckets = m_tables.m_buckets; + Node[] buckets = _tables._buckets; for (int i = 0; i < buckets.Length; i++) { - for (Node current = buckets[i]; current != null; current = current.m_next) + for (Node current = buckets[i]; current != null; current = current._next) { - array[index] = new KeyValuePair<TKey, TValue>(current.m_key, current.m_value); + array[index] = new KeyValuePair<TKey, TValue>(current._key, current._value); index++; //this should never flow, CopyToObjects is only called when there's no overflow risk } } @@ -516,17 +505,18 @@ namespace System.Collections.Concurrent /// </remarks> public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { - Node[] buckets = m_tables.m_buckets; + Node[] buckets = _tables._buckets; for (int i = 0; i < buckets.Length; i++) { - // The Volatile.Read ensures that the load of the fields of 'current' doesn't move before the load from buckets[i]. + // The Volatile.Read ensures that we have a copy of the reference to buckets[i]. + // This protects us from reading fields ('_key', '_value' and '_next') of different instances. Node current = Volatile.Read<Node>(ref buckets[i]); while (current != null) { - yield return new KeyValuePair<TKey, TValue>(current.m_key, current.m_value); - current = current.m_next; + yield return new KeyValuePair<TKey, TValue>(current._key, current._value); + current = current._next; } } } @@ -537,39 +527,37 @@ namespace System.Collections.Concurrent /// If key doesn't exist, we always add value and return true; /// </summary> [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] - private bool TryAddInternal(TKey key, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue) + private bool TryAddInternal(TKey key, int hashcode, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue) { + Debug.Assert(_comparer.GetHashCode(key) == hashcode); + while (true) { int bucketNo, lockNo; - int hashcode; - Tables tables = m_tables; - IEqualityComparer<TKey> comparer = tables.m_comparer; - hashcode = comparer.GetHashCode(key); - GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length); + Tables tables = _tables; + GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables._buckets.Length, tables._locks.Length); bool resizeDesired = false; bool lockTaken = false; - try { if (acquireLock) - Monitor.Enter(tables.m_locks[lockNo], ref lockTaken); + Monitor.Enter(tables._locks[lockNo], ref lockTaken); // If the table just got resized, we may not be holding the right lock, and must retry. - // This should be a rare occurence. - if (tables != m_tables) + // This should be a rare occurrence. + if (tables != _tables) { continue; } // Try to find this key in the bucket Node prev = null; - for (Node node = tables.m_buckets[bucketNo]; node != null; node = node.m_next) + for (Node node = tables._buckets[bucketNo]; node != null; node = node._next) { - Assert((prev == null && node == tables.m_buckets[bucketNo]) || prev.m_next == node); - if (comparer.Equals(node.m_key, key)) + Debug.Assert((prev == null && node == tables._buckets[bucketNo]) || prev._next == node); + if (hashcode == node._hashcode && _comparer.Equals(node._key, key)) { // The key was found in the dictionary. If updates are allowed, update the value for that key. // We need to create a new node for the update, in order to support TValue types that cannot @@ -578,25 +566,25 @@ namespace System.Collections.Concurrent { if (s_isValueWriteAtomic) { - node.m_value = value; + node._value = value; } else { - Node newNode = new Node(node.m_key, value, hashcode, node.m_next); + Node newNode = new Node(node._key, value, hashcode, node._next); if (prev == null) { - tables.m_buckets[bucketNo] = newNode; + Volatile.Write(ref tables._buckets[bucketNo], newNode); } else { - prev.m_next = newNode; + prev._next = newNode; } } resultingValue = value; } else { - resultingValue = node.m_value; + resultingValue = node._value; } return false; } @@ -604,10 +592,10 @@ namespace System.Collections.Concurrent } // The key was not found in the bucket. Insert the key-value pair. - Volatile.Write<Node>(ref tables.m_buckets[bucketNo], new Node(key, value, hashcode, tables.m_buckets[bucketNo])); + Volatile.Write<Node>(ref tables._buckets[bucketNo], new Node(key, value, hashcode, tables._buckets[bucketNo])); checked { - tables.m_countPerLock[lockNo]++; + tables._countPerLock[lockNo]++; } // @@ -615,7 +603,7 @@ namespace System.Collections.Concurrent // It is also possible that GrowTable will increase the budget but won't resize the bucket table. // That happens if the bucket table is found to be poorly utilized due to a bad hash function. // - if (tables.m_countPerLock[lockNo] > m_budget) + if (tables._countPerLock[lockNo] > _budget) { resizeDesired = true; } @@ -623,7 +611,7 @@ namespace System.Collections.Concurrent finally { if (lockTaken) - Monitor.Exit(tables.m_locks[lockNo]); + Monitor.Exit(tables._locks[lockNo]); } // @@ -636,7 +624,7 @@ namespace System.Collections.Concurrent // if (resizeDesired) { - GrowTable(tables, tables.m_comparer, false, m_keyRehashCount); + GrowTable(tables); } resultingValue = value; @@ -650,7 +638,7 @@ namespace System.Collections.Concurrent /// <param name="key">The key of the value to get or set.</param> /// <value>The value associated with the specified key. If the specified key is not found, a get /// operation throws a - /// <see cref="T:Sytem.Collections.Generic.KeyNotFoundException"/>, and a set operation creates a new + /// <see cref="T:System.Collections.Generic.KeyNotFoundException"/>, and a set operation creates a new /// element with the specified key.</value> /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> @@ -664,18 +652,34 @@ namespace System.Collections.Concurrent TValue value; if (!TryGetValue(key, out value)) { - ThrowHelper.ThrowKeyNotFoundException(); + ThrowKeyNotFoundException(); } return value; } set { - if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + if (key == null) ThrowKeyNullException(); TValue dummy; - TryAddInternal(key, value, true, true, out dummy); + TryAddInternal(key, _comparer.GetHashCode(key), value, true, true, out dummy); } } + // These exception throwing sites have been extracted into their own NoInlining methods + // as these are uncommonly needed and when inlined are observed to prevent the inlining + // of important methods like TryGetValue and ContainsKey. + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowKeyNotFoundException() + { + throw new KeyNotFoundException(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowKeyNullException() + { + throw new ArgumentNullException("key"); + } + /// <summary> /// Gets the number of key/value pairs contained in the <see /// cref="ConcurrentDictionary{TKey,TValue}"/>. @@ -692,31 +696,46 @@ namespace System.Collections.Concurrent [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "ConcurrencyCop just doesn't know about these locks")] get { - int count = 0; - int acquiredLocks = 0; try { // Acquire all locks AcquireAllLocks(ref acquiredLocks); - // Compute the count, we allow overflow - for (int i = 0; i < m_tables.m_countPerLock.Length; i++) - { - count += m_tables.m_countPerLock[i]; - } + return GetCountInternal(); } finally { // Release locks that have been acquired earlier ReleaseLocks(0, acquiredLocks); } - - return count; } } + /// <summary> + /// Gets the number of key/value pairs contained in the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/>. Should only be used after all locks + /// have been acquired. + /// </summary> + /// <exception cref="T:System.OverflowException">The dictionary contains too many + /// elements.</exception> + /// <value>The number of key/value pairs contained in the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/>.</value> + /// <remarks>Count has snapshot semantics and represents the number of items in the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/> + /// at the moment when Count was accessed.</remarks> + private int GetCountInternal() + { + int count = 0; + + // Compute the count, we allow overflow + for (int i = 0; i < _tables._countPerLock.Length; i++) + { + count += _tables._countPerLock[i]; + } + return count; + } /// <summary> /// Gets a value that indicates whether the <see cref="ConcurrentDictionary{TKey,TValue}"/> is empty. @@ -734,9 +753,9 @@ namespace System.Collections.Concurrent // Acquire all locks AcquireAllLocks(ref acquiredLocks); - for (int i = 0; i < m_tables.m_countPerLock.Length; i++) + for (int i = 0; i < _tables._countPerLock.Length; i++) { - if (m_tables.m_countPerLock[i] != 0) + if (_tables._countPerLock[i] != 0) { return false; } @@ -771,7 +790,7 @@ namespace System.Collections.Concurrent { if (!TryAdd(key, value)) { - ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_KeyAlreadyExisted); + ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_KeyAlreadyExisted, ExceptionArgument.key); } } @@ -904,7 +923,8 @@ namespace System.Collections.Concurrent /// name="keyValuePair"/> is a null reference (Nothing in Visual Basic).</exception> bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> keyValuePair) { - if (keyValuePair.Key == null) ThrowHelper.ThrowArgumentNullException(ExceptionResource.ConcurrentDictionary_ItemKeyIsNull); + if (keyValuePair.Key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keyValuePair, ExceptionResource.ConcurrentDictionary_ItemKeyIsNull); + TValue throwAwayValue; return TryRemoveInternal(keyValuePair.Key, out throwAwayValue, true, keyValuePair.Value); } @@ -950,17 +970,18 @@ namespace System.Collections.Concurrent /// </exception> void IDictionary.Add(object key, object value) { - if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); - if (!(key is TKey)) ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfKeyIncorrect); + if (key == null) ThrowKeyNullException(); + if (!(key is TKey)) ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfKeyIncorrect, ExceptionArgument.key); - TValue typedValue = default(TValue); + TValue typedValue; try { typedValue = (TValue)value; } catch (InvalidCastException) { - ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfValueIncorrect); + ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfValueIncorrect, ExceptionArgument.value); + return; } ((IDictionary<TKey, TValue>)this).Add((TKey)key, typedValue); @@ -978,9 +999,9 @@ namespace System.Collections.Concurrent /// (Nothing in Visual Basic).</exception> bool IDictionary.Contains(object key) { - if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + if (key == null) ThrowKeyNullException(); - return (key is TKey) && ((ConcurrentDictionary<TKey, TValue>)this).ContainsKey((TKey)key); + return (key is TKey) && this.ContainsKey((TKey)key); } /// <summary>Provides an <see cref="T:System.Collections.Generics.IDictionaryEnumerator"/> for the @@ -1038,12 +1059,12 @@ namespace System.Collections.Concurrent /// (Nothing in Visual Basic).</exception> void IDictionary.Remove(object key) { - if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + if (key == null) ThrowKeyNullException(); TValue throwAwayValue; if (key is TKey) { - this.TryRemove((TKey)key, out throwAwayValue); + TryRemove((TKey)key, out throwAwayValue); } } @@ -1080,10 +1101,10 @@ namespace System.Collections.Concurrent { get { - if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + if (key == null) ThrowKeyNullException(); TValue value; - if (key is TKey && this.TryGetValue((TKey)key, out value)) + if (key is TKey && TryGetValue((TKey)key, out value)) { return value; } @@ -1092,10 +1113,10 @@ namespace System.Collections.Concurrent } set { - if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + if (key == null) ThrowKeyNullException(); - if (!(key is TKey)) ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfKeyIncorrect); - if (!(value is TValue)) ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfValueIncorrect); + if (!(key is TKey)) ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfKeyIncorrect, ExceptionArgument.key); + if (!(value is TValue)) ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfValueIncorrect, ExceptionArgument.value); ((ConcurrentDictionary<TKey, TValue>)this)[(TKey)key] = (TValue)value; } @@ -1133,13 +1154,13 @@ namespace System.Collections.Concurrent try { AcquireAllLocks(ref locksAcquired); - Tables tables = m_tables; + Tables tables = _tables; int count = 0; - for (int i = 0; i < tables.m_locks.Length && count >= 0; i++) + for (int i = 0; i < tables._locks.Length && count >= 0; i++) { - count += tables.m_countPerLock[i]; + count += tables._countPerLock[i]; } if (array.Length - count < index || count < 0) //"count" itself or "count + index" can overflow @@ -1213,62 +1234,49 @@ namespace System.Collections.Concurrent /// <summary> /// Replaces the bucket table with a larger one. To prevent multiple threads from resizing the - /// table as a result of a race condition, the Tables instance that holds the table of buckets deemed too + /// table as a result of races, the Tables instance that holds the table of buckets deemed too /// small is passed in as an argument to GrowTable(). GrowTable() obtains a lock, and then checks - /// the Tables instance has been replaced in the meantime or not. - /// The <paramref name="rehashCount"/> will be used to ensure that we don't do two subsequent resizes - /// because of a collision + /// the Tables instance has been replaced in the meantime or not. /// </summary> - private void GrowTable(Tables tables, IEqualityComparer<TKey> newComparer, bool regenerateHashKeys, int rehashCount) + private void GrowTable(Tables tables) { + const int MaxArrayLength = 0X7FEFFFFF; int locksAcquired = 0; try { - // The thread that first obtains m_locks[0] will be the one doing the resize operation + // The thread that first obtains _locks[0] will be the one doing the resize operation AcquireLocks(0, 1, ref locksAcquired); - if (regenerateHashKeys && rehashCount == m_keyRehashCount) + // Make sure nobody resized the table while we were waiting for lock 0: + if (tables != _tables) { - // This method is called with regenerateHashKeys==true when we detected - // more than HashHelpers.HashCollisionThreshold collisions when adding a new element. - // In that case we are in the process of switching to another (randomized) comparer - // and we have to re-hash all the keys in the table. - // We are only going to do this if we did not just rehash the entire table while waiting for the lock - tables = m_tables; + // We assume that since the table reference is different, it was already resized (or the budget + // was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons, + // we will have to revisit this logic. + return; } - else + + // Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow. + long approxCount = 0; + for (int i = 0; i < tables._countPerLock.Length; i++) { - // If we don't require a regeneration of hash keys we want to make sure we don't do work when - // we don't have to - if (tables != m_tables) - { - // We assume that since the table reference is different, it was already resized (or the budget - // was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons, - // we will have to revisit this logic. - return; - } + approxCount += tables._countPerLock[i]; + } - // Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow. - long approxCount = 0; - for (int i = 0; i < tables.m_countPerLock.Length; i++) + // + // If the bucket array is too empty, double the budget instead of resizing the table + // + if (approxCount < tables._buckets.Length / 4) + { + _budget = 2 * _budget; + if (_budget < 0) { - approxCount += tables.m_countPerLock[i]; + _budget = int.MaxValue; } + return; + } - // - // If the bucket array is too empty, double the budget instead of resizing the table - // - if (approxCount < tables.m_buckets.Length / 4) - { - m_budget = 2 * m_budget; - if (m_budget < 0) - { - m_budget = int.MaxValue; - } - return; - } - } // Compute the new table size. We find the smallest integer larger than twice the previous table size, and not divisible by // 2,3,5 or 7. We can consider a different table-sizing policy in the future. int newLength = 0; @@ -1278,7 +1286,7 @@ namespace System.Collections.Concurrent checked { // Double the size of the buckets table and add one, so that we have an odd integer. - newLength = tables.m_buckets.Length * 2 + 1; + newLength = tables._buckets.Length * 2 + 1; // Now, we only need to check odd integers, and find the first that is not divisible // by 3, 5 or 7. @@ -1287,9 +1295,9 @@ namespace System.Collections.Concurrent newLength += 2; } - Assert(newLength % 2 != 0); + Debug.Assert(newLength % 2 != 0); - if (newLength > Array.MaxArrayLength) + if (newLength > MaxArrayLength) { maximizeTableSize = true; } @@ -1302,28 +1310,27 @@ namespace System.Collections.Concurrent if (maximizeTableSize) { - newLength = Array.MaxArrayLength; + newLength = MaxArrayLength; // We want to make sure that GrowTable will not be called again, since table is at the maximum size. // To achieve that, we set the budget to int.MaxValue. // // (There is one special case that would allow GrowTable() to be called in the future: // calling Clear() on the ConcurrentDictionary will shrink the table and lower the budget.) - m_budget = int.MaxValue; + _budget = int.MaxValue; } // Now acquire all other locks for the table - AcquireLocks(1, tables.m_locks.Length, ref locksAcquired); + AcquireLocks(1, tables._locks.Length, ref locksAcquired); - object[] newLocks = tables.m_locks; + object[] newLocks = tables._locks; // Add more locks - if (m_growLockArray && tables.m_locks.Length < MAX_LOCK_NUMBER) + if (_growLockArray && tables._locks.Length < MaxLockNumber) { - newLocks = new object[tables.m_locks.Length * 2]; - Array.Copy(tables.m_locks, newLocks, tables.m_locks.Length); - - for (int i = tables.m_locks.Length; i < newLocks.Length; i++) + newLocks = new object[tables._locks.Length * 2]; + Array.Copy(tables._locks, 0, newLocks, 0, tables._locks.Length); + for (int i = tables._locks.Length; i < newLocks.Length; i++) { newLocks[i] = new object(); } @@ -1333,24 +1340,16 @@ namespace System.Collections.Concurrent int[] newCountPerLock = new int[newLocks.Length]; // Copy all data into a new table, creating new nodes for all elements - for (int i = 0; i < tables.m_buckets.Length; i++) + for (int i = 0; i < tables._buckets.Length; i++) { - Node current = tables.m_buckets[i]; + Node current = tables._buckets[i]; while (current != null) { - Node next = current.m_next; + Node next = current._next; int newBucketNo, newLockNo; - int nodeHashCode = current.m_hashcode; - - if (regenerateHashKeys) - { - // Recompute the hash from the key - nodeHashCode = newComparer.GetHashCode(current.m_key); - } + GetBucketAndLockNo(current._hashcode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length); - GetBucketAndLockNo(nodeHashCode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length); - - newBuckets[newBucketNo] = new Node(current.m_key, current.m_value, nodeHashCode, newBuckets[newBucketNo]); + newBuckets[newBucketNo] = new Node(current._key, current._value, current._hashcode, newBuckets[newBucketNo]); checked { @@ -1361,22 +1360,11 @@ namespace System.Collections.Concurrent } } - // If this resize regenerated the hashkeys, increment the count - if (regenerateHashKeys) - { - // We use unchecked here because we don't want to throw an exception if - // an overflow happens - unchecked - { - m_keyRehashCount++; - } - } - // Adjust the budget - m_budget = Math.Max(1, newBuckets.Length / newLocks.Length); + _budget = Math.Max(1, newBuckets.Length / newLocks.Length); // Replace tables with the new versions - m_tables = new Tables(newBuckets, newLocks, newCountPerLock, newComparer); + _tables = new Tables(newBuckets, newLocks, newCountPerLock); } finally { @@ -1386,16 +1374,25 @@ namespace System.Collections.Concurrent } /// <summary> + /// Computes the bucket for a particular key. + /// </summary> + private static int GetBucket(int hashcode, int bucketCount) + { + int bucketNo = (hashcode & 0x7fffffff) % bucketCount; + Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount); + return bucketNo; + } + + /// <summary> /// Computes the bucket and lock number for a particular key. /// </summary> - private void GetBucketAndLockNo( - int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) + private static void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) { bucketNo = (hashcode & 0x7fffffff) % bucketCount; lockNo = bucketNo % lockCount; - Assert(bucketNo >= 0 && bucketNo < bucketCount); - Assert(lockNo >= 0 && lockNo < lockCount); + Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount); + Debug.Assert(lockNo >= 0 && lockNo < lockCount); } /// <summary> @@ -1403,7 +1400,7 @@ namespace System.Collections.Concurrent /// </summary> private static int DefaultConcurrencyLevel { - get { return DEFAULT_CONCURRENCY_MULTIPLIER * PlatformHelper.ProcessorCount; } + get { return PlatformHelper.ProcessorCount; } } /// <summary> @@ -1416,10 +1413,10 @@ namespace System.Collections.Concurrent // First, acquire lock 0 AcquireLocks(0, 1, ref locksAcquired); - // Now that we have lock 0, the m_locks array will not change (i.e., grow), - // and so we can safely read m_locks.Length. - AcquireLocks(1, m_tables.m_locks.Length, ref locksAcquired); - Assert(locksAcquired == m_tables.m_locks.Length); + // Now that we have lock 0, the _locks array will not change (i.e., grow), + // and so we can safely read _locks.Length. + AcquireLocks(1, _tables._locks.Length, ref locksAcquired); + Debug.Assert(locksAcquired == _tables._locks.Length); } /// <summary> @@ -1429,8 +1426,8 @@ namespace System.Collections.Concurrent /// </summary> private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired) { - Assert(fromInclusive <= toExclusive); - object[] locks = m_tables.m_locks; + Debug.Assert(fromInclusive <= toExclusive); + object[] locks = _tables._locks; for (int i = fromInclusive; i < toExclusive; i++) { @@ -1455,11 +1452,11 @@ namespace System.Collections.Concurrent [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] private void ReleaseLocks(int fromInclusive, int toExclusive) { - Assert(fromInclusive <= toExclusive); + Debug.Assert(fromInclusive <= toExclusive); for (int i = fromInclusive; i < toExclusive; i++) { - Monitor.Exit(m_tables.m_locks[i]); + Monitor.Exit(_tables._locks[i]); } } @@ -1473,15 +1470,18 @@ namespace System.Collections.Concurrent try { AcquireAllLocks(ref locksAcquired); - List<TKey> keys = new List<TKey>(); - for (int i = 0; i < m_tables.m_buckets.Length; i++) + int count = GetCountInternal(); + if (count < 0) ThrowHelper.ThrowOutOfMemoryException(); + + List<TKey> keys = new List<TKey>(count); + for (int i = 0; i < _tables._buckets.Length; i++) { - Node current = m_tables.m_buckets[i]; + Node current = _tables._buckets[i]; while (current != null) { - keys.Add(current.m_key); - current = current.m_next; + keys.Add(current._key); + current = current._next; } } @@ -1503,15 +1503,18 @@ namespace System.Collections.Concurrent try { AcquireAllLocks(ref locksAcquired); - List<TValue> values = new List<TValue>(); - for (int i = 0; i < m_tables.m_buckets.Length; i++) + int count = GetCountInternal(); + if (count < 0) ThrowHelper.ThrowOutOfMemoryException(); + + List<TValue> values = new List<TValue>(count); + for (int i = 0; i < _tables._buckets.Length; i++) { - Node current = m_tables.m_buckets[i]; + Node current = _tables._buckets[i]; while (current != null) { - values.Add(current.m_value); - current = current.m_next; + values.Add(current._value); + current = current._next; } } @@ -1524,30 +1527,21 @@ namespace System.Collections.Concurrent } /// <summary> - /// A helper method for asserts. - /// </summary> - [Conditional("DEBUG")] - private void Assert(bool condition) - { - Debug.Assert(condition); - } - - /// <summary> /// A node in a singly-linked list representing a particular hash table bucket. /// </summary> - private class Node + private sealed class Node { - internal TKey m_key; - internal TValue m_value; - internal volatile Node m_next; - internal int m_hashcode; + internal readonly TKey _key; + internal TValue _value; + internal volatile Node _next; + internal readonly int _hashcode; internal Node(TKey key, TValue value, int hashcode, Node next) { - m_key = key; - m_value = value; - m_next = next; - m_hashcode = hashcode; + _key = key; + _value = value; + _next = next; + _hashcode = hashcode; } } @@ -1555,43 +1549,43 @@ namespace System.Collections.Concurrent /// A private class to represent enumeration over the dictionary that implements the /// IDictionaryEnumerator interface. /// </summary> - private class DictionaryEnumerator : IDictionaryEnumerator + private sealed class DictionaryEnumerator : IDictionaryEnumerator { - private IEnumerator<KeyValuePair<TKey, TValue>> m_enumerator; // Enumerator over the dictionary. + IEnumerator<KeyValuePair<TKey, TValue>> _enumerator; // Enumerator over the dictionary. internal DictionaryEnumerator(ConcurrentDictionary<TKey, TValue> dictionary) { - m_enumerator = dictionary.GetEnumerator(); + _enumerator = dictionary.GetEnumerator(); } public DictionaryEntry Entry { - get { return new DictionaryEntry(m_enumerator.Current.Key, m_enumerator.Current.Value); } + get { return new DictionaryEntry(_enumerator.Current.Key, _enumerator.Current.Value); } } public object Key { - get { return m_enumerator.Current.Key; } + get { return _enumerator.Current.Key; } } public object Value { - get { return m_enumerator.Current.Value; } + get { return _enumerator.Current.Value; } } public object Current { - get { return this.Entry; } + get { return Entry; } } public bool MoveNext() { - return m_enumerator.MoveNext(); + return _enumerator.MoveNext(); } public void Reset() { - m_enumerator.Reset(); + _enumerator.Reset(); } } } |