|
|
@@ -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; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |