| @@ -0,0 +1,475 @@ | |||||
| using System; | |||||
| using System.Collections; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.ObjectModel; | |||||
| using System.Diagnostics; | |||||
| using System.Threading; | |||||
| namespace Discord | |||||
| { | |||||
| //Based on https://github.com/dotnet/corefx/blob/master/src/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs | |||||
| //Copyright (c) .NET Foundation and Contributors | |||||
| [DebuggerDisplay("Count = {Count}")] | |||||
| internal class ConcurrentHashSet<T> : IEnumerable<T> | |||||
| { | |||||
| private sealed class Tables | |||||
| { | |||||
| internal readonly Node[] _buckets; | |||||
| internal readonly object[] _locks; | |||||
| internal volatile int[] _countPerLock; | |||||
| internal Tables(Node[] buckets, object[] locks, int[] countPerLock) | |||||
| { | |||||
| _buckets = buckets; | |||||
| _locks = locks; | |||||
| _countPerLock = countPerLock; | |||||
| } | |||||
| } | |||||
| private sealed class Node | |||||
| { | |||||
| internal readonly T _value; | |||||
| internal volatile Node _next; | |||||
| internal readonly int _hashcode; | |||||
| internal Node(T key, int hashcode, Node next) | |||||
| { | |||||
| _value = key; | |||||
| _next = next; | |||||
| _hashcode = hashcode; | |||||
| } | |||||
| } | |||||
| private const int DefaultCapacity = 31; | |||||
| private const int MaxLockNumber = 1024; | |||||
| private static int GetBucket(int hashcode, int bucketCount) | |||||
| { | |||||
| int bucketNo = (hashcode & 0x7fffffff) % bucketCount; | |||||
| return bucketNo; | |||||
| } | |||||
| private static void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) | |||||
| { | |||||
| bucketNo = (hashcode & 0x7fffffff) % bucketCount; | |||||
| lockNo = bucketNo % lockCount; | |||||
| } | |||||
| private static int DefaultConcurrencyLevel => PlatformHelper.ProcessorCount; | |||||
| private volatile Tables _tables; | |||||
| private readonly IEqualityComparer<T> _comparer; | |||||
| private readonly bool _growLockArray; | |||||
| private int _budget; | |||||
| public int Count | |||||
| { | |||||
| get | |||||
| { | |||||
| int count = 0; | |||||
| int acquiredLocks = 0; | |||||
| try | |||||
| { | |||||
| AcquireAllLocks(ref acquiredLocks); | |||||
| for (int i = 0; i < _tables._countPerLock.Length; i++) | |||||
| count += _tables._countPerLock[i]; | |||||
| } | |||||
| finally { ReleaseLocks(0, acquiredLocks); } | |||||
| return count; | |||||
| } | |||||
| } | |||||
| public bool IsEmpty | |||||
| { | |||||
| get | |||||
| { | |||||
| int acquiredLocks = 0; | |||||
| try | |||||
| { | |||||
| // Acquire all locks | |||||
| AcquireAllLocks(ref acquiredLocks); | |||||
| for (int i = 0; i < _tables._countPerLock.Length; i++) | |||||
| { | |||||
| if (_tables._countPerLock[i] != 0) | |||||
| return false; | |||||
| } | |||||
| } | |||||
| finally { ReleaseLocks(0, acquiredLocks); } | |||||
| return true; | |||||
| } | |||||
| } | |||||
| public ReadOnlyCollection<T> Values | |||||
| { | |||||
| get | |||||
| { | |||||
| int locksAcquired = 0; | |||||
| try | |||||
| { | |||||
| AcquireAllLocks(ref locksAcquired); | |||||
| List<T> values = new List<T>(); | |||||
| for (int i = 0; i < _tables._buckets.Length; i++) | |||||
| { | |||||
| Node current = _tables._buckets[i]; | |||||
| while (current != null) | |||||
| { | |||||
| values.Add(current._value); | |||||
| current = current._next; | |||||
| } | |||||
| } | |||||
| return new ReadOnlyCollection<T>(values); | |||||
| } | |||||
| finally { ReleaseLocks(0, locksAcquired); } | |||||
| } | |||||
| } | |||||
| public ConcurrentHashSet() | |||||
| : this(DefaultConcurrencyLevel, DefaultCapacity, true, EqualityComparer<T>.Default) { } | |||||
| public ConcurrentHashSet(int concurrencyLevel, int capacity) | |||||
| : this(concurrencyLevel, capacity, false, EqualityComparer<T>.Default) { } | |||||
| public ConcurrentHashSet(IEnumerable<T> collection) | |||||
| : this(collection, EqualityComparer<T>.Default) { } | |||||
| public ConcurrentHashSet(IEqualityComparer<T> comparer) | |||||
| : this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer) { } | |||||
| public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer) | |||||
| : this(comparer) | |||||
| { | |||||
| if (collection == null) throw new ArgumentNullException(nameof(collection)); | |||||
| InitializeFromCollection(collection); | |||||
| } | |||||
| public ConcurrentHashSet(int concurrencyLevel, IEnumerable<T> collection, IEqualityComparer<T> comparer) | |||||
| : this(concurrencyLevel, DefaultCapacity, false, comparer) | |||||
| { | |||||
| if (collection == null) throw new ArgumentNullException(nameof(collection)); | |||||
| if (comparer == null) throw new ArgumentNullException(nameof(comparer)); | |||||
| InitializeFromCollection(collection); | |||||
| } | |||||
| public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer<T> comparer) | |||||
| : this(concurrencyLevel, capacity, false, comparer) { } | |||||
| internal ConcurrentHashSet(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer<T> comparer) | |||||
| { | |||||
| if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(nameof(concurrencyLevel)); | |||||
| if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity)); | |||||
| if (comparer == null) throw new ArgumentNullException(nameof(comparer)); | |||||
| if (capacity < concurrencyLevel) | |||||
| capacity = concurrencyLevel; | |||||
| object[] locks = new object[concurrencyLevel]; | |||||
| for (int i = 0; i < locks.Length; i++) | |||||
| locks[i] = new object(); | |||||
| int[] countPerLock = new int[locks.Length]; | |||||
| Node[] buckets = new Node[capacity]; | |||||
| _tables = new Tables(buckets, locks, countPerLock); | |||||
| _comparer = comparer; | |||||
| _growLockArray = growLockArray; | |||||
| _budget = buckets.Length / locks.Length; | |||||
| } | |||||
| private void InitializeFromCollection(IEnumerable<T> collection) | |||||
| { | |||||
| foreach (var value in collection) | |||||
| { | |||||
| if (value == null) throw new ArgumentNullException("key"); | |||||
| if (!TryAddInternal(value, _comparer.GetHashCode(value), false)) | |||||
| throw new ArgumentException(); | |||||
| } | |||||
| if (_budget == 0) | |||||
| _budget = _tables._buckets.Length / _tables._locks.Length; | |||||
| } | |||||
| public bool ContainsKey(T value) | |||||
| { | |||||
| if (value == null) throw new ArgumentNullException("key"); | |||||
| return ContainsKeyInternal(value, _comparer.GetHashCode(value)); | |||||
| } | |||||
| private bool ContainsKeyInternal(T value, int hashcode) | |||||
| { | |||||
| Tables tables = _tables; | |||||
| int bucketNo = GetBucket(hashcode, tables._buckets.Length); | |||||
| Node n = Volatile.Read(ref tables._buckets[bucketNo]); | |||||
| while (n != null) | |||||
| { | |||||
| if (hashcode == n._hashcode && _comparer.Equals(n._value, value)) | |||||
| return true; | |||||
| n = n._next; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| public bool TryAdd(T value) | |||||
| { | |||||
| if (value == null) throw new ArgumentNullException("key"); | |||||
| return TryAddInternal(value, _comparer.GetHashCode(value), true); | |||||
| } | |||||
| private bool TryAddInternal(T value, int hashcode, bool acquireLock) | |||||
| { | |||||
| while (true) | |||||
| { | |||||
| int bucketNo, lockNo; | |||||
| 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._locks[lockNo], ref lockTaken); | |||||
| if (tables != _tables) | |||||
| continue; | |||||
| Node prev = null; | |||||
| for (Node node = tables._buckets[bucketNo]; node != null; node = node._next) | |||||
| { | |||||
| if (hashcode == node._hashcode && _comparer.Equals(node._value, value)) | |||||
| return false; | |||||
| prev = node; | |||||
| } | |||||
| Volatile.Write(ref tables._buckets[bucketNo], new Node(value, hashcode, tables._buckets[bucketNo])); | |||||
| checked { tables._countPerLock[lockNo]++; } | |||||
| if (tables._countPerLock[lockNo] > _budget) | |||||
| resizeDesired = true; | |||||
| } | |||||
| finally | |||||
| { | |||||
| if (lockTaken) | |||||
| Monitor.Exit(tables._locks[lockNo]); | |||||
| } | |||||
| if (resizeDesired) | |||||
| GrowTable(tables); | |||||
| return true; | |||||
| } | |||||
| } | |||||
| public bool TryRemove(T value) | |||||
| { | |||||
| if (value == null) throw new ArgumentNullException("key"); | |||||
| return TryRemoveInternal(value); | |||||
| } | |||||
| private bool TryRemoveInternal(T value) | |||||
| { | |||||
| int hashcode = _comparer.GetHashCode(value); | |||||
| while (true) | |||||
| { | |||||
| Tables tables = _tables; | |||||
| int bucketNo, lockNo; | |||||
| GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables._buckets.Length, tables._locks.Length); | |||||
| lock (tables._locks[lockNo]) | |||||
| { | |||||
| if (tables != _tables) | |||||
| continue; | |||||
| Node prev = null; | |||||
| for (Node curr = tables._buckets[bucketNo]; curr != null; curr = curr._next) | |||||
| { | |||||
| if (hashcode == curr._hashcode && _comparer.Equals(curr._value, value)) | |||||
| { | |||||
| if (prev == null) | |||||
| Volatile.Write(ref tables._buckets[bucketNo], curr._next); | |||||
| else | |||||
| prev._next = curr._next; | |||||
| value = curr._value; | |||||
| tables._countPerLock[lockNo]--; | |||||
| return true; | |||||
| } | |||||
| prev = curr; | |||||
| } | |||||
| } | |||||
| value = default(T); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| public void Clear() | |||||
| { | |||||
| int locksAcquired = 0; | |||||
| try | |||||
| { | |||||
| AcquireAllLocks(ref locksAcquired); | |||||
| 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 | |||||
| { | |||||
| ReleaseLocks(0, locksAcquired); | |||||
| } | |||||
| } | |||||
| public IEnumerator<T> GetEnumerator() | |||||
| { | |||||
| Node[] buckets = _tables._buckets; | |||||
| for (int i = 0; i < buckets.Length; i++) | |||||
| { | |||||
| Node current = Volatile.Read(ref buckets[i]); | |||||
| while (current != null) | |||||
| { | |||||
| yield return current._value; | |||||
| current = current._next; | |||||
| } | |||||
| } | |||||
| } | |||||
| IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | |||||
| private void GrowTable(Tables tables) | |||||
| { | |||||
| const int MaxArrayLength = 0X7FEFFFFF; | |||||
| int locksAcquired = 0; | |||||
| try | |||||
| { | |||||
| AcquireLocks(0, 1, ref locksAcquired); | |||||
| if (tables != _tables) | |||||
| return; | |||||
| long approxCount = 0; | |||||
| for (int i = 0; i < tables._countPerLock.Length; i++) | |||||
| approxCount += tables._countPerLock[i]; | |||||
| if (approxCount < tables._buckets.Length / 4) | |||||
| { | |||||
| _budget = 2 * _budget; | |||||
| if (_budget < 0) | |||||
| _budget = int.MaxValue; | |||||
| return; | |||||
| } | |||||
| int newLength = 0; | |||||
| bool maximizeTableSize = false; | |||||
| try | |||||
| { | |||||
| checked | |||||
| { | |||||
| newLength = tables._buckets.Length * 2 + 1; | |||||
| while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0) | |||||
| newLength += 2; | |||||
| if (newLength > MaxArrayLength) | |||||
| maximizeTableSize = true; | |||||
| } | |||||
| } | |||||
| catch (OverflowException) | |||||
| { | |||||
| maximizeTableSize = true; | |||||
| } | |||||
| if (maximizeTableSize) | |||||
| { | |||||
| newLength = MaxArrayLength; | |||||
| _budget = int.MaxValue; | |||||
| } | |||||
| AcquireLocks(1, tables._locks.Length, ref locksAcquired); | |||||
| object[] newLocks = tables._locks; | |||||
| if (_growLockArray && tables._locks.Length < MaxLockNumber) | |||||
| { | |||||
| 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(); | |||||
| } | |||||
| Node[] newBuckets = new Node[newLength]; | |||||
| int[] newCountPerLock = new int[newLocks.Length]; | |||||
| for (int i = 0; i < tables._buckets.Length; i++) | |||||
| { | |||||
| Node current = tables._buckets[i]; | |||||
| while (current != null) | |||||
| { | |||||
| Node next = current._next; | |||||
| int newBucketNo, newLockNo; | |||||
| GetBucketAndLockNo(current._hashcode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length); | |||||
| newBuckets[newBucketNo] = new Node(current._value, current._hashcode, newBuckets[newBucketNo]); | |||||
| checked { newCountPerLock[newLockNo]++; } | |||||
| current = next; | |||||
| } | |||||
| } | |||||
| _budget = Math.Max(1, newBuckets.Length / newLocks.Length); | |||||
| _tables = new Tables(newBuckets, newLocks, newCountPerLock); | |||||
| } | |||||
| finally { ReleaseLocks(0, locksAcquired); } | |||||
| } | |||||
| private void AcquireAllLocks(ref int locksAcquired) | |||||
| { | |||||
| AcquireLocks(0, 1, ref locksAcquired); | |||||
| AcquireLocks(1, _tables._locks.Length, ref locksAcquired); | |||||
| } | |||||
| private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired) | |||||
| { | |||||
| object[] locks = _tables._locks; | |||||
| for (int i = fromInclusive; i < toExclusive; i++) | |||||
| { | |||||
| bool lockTaken = false; | |||||
| try | |||||
| { | |||||
| Monitor.Enter(locks[i], ref lockTaken); | |||||
| } | |||||
| finally | |||||
| { | |||||
| if (lockTaken) | |||||
| locksAcquired++; | |||||
| } | |||||
| } | |||||
| } | |||||
| private void ReleaseLocks(int fromInclusive, int toExclusive) | |||||
| { | |||||
| for (int i = fromInclusive; i < toExclusive; i++) | |||||
| Monitor.Exit(_tables._locks[i]); | |||||
| } | |||||
| } | |||||
| //https://github.com/dotnet/corefx/blob/d0dc5fc099946adc1035b34a8b1f6042eddb0c75/src/System.Threading.Tasks.Parallel/src/System/Threading/PlatformHelper.cs | |||||
| //Copyright (c) .NET Foundation and Contributors | |||||
| internal static class PlatformHelper | |||||
| { | |||||
| private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000; | |||||
| private static volatile int s_processorCount; | |||||
| private static volatile int s_lastProcessorCountRefreshTicks; | |||||
| internal static int ProcessorCount | |||||
| { | |||||
| get | |||||
| { | |||||
| int now = Environment.TickCount; | |||||
| if (s_processorCount == 0 || (now - s_lastProcessorCountRefreshTicks) >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS) | |||||
| { | |||||
| s_processorCount = Environment.ProcessorCount; | |||||
| s_lastProcessorCountRefreshTicks = now; | |||||
| } | |||||
| return s_processorCount; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||