diff --git a/Projects/Server/Buffers/STArrayPool.cs b/Projects/Server/Buffers/STArrayPool.cs index d53d650ee4..25dd6a7f47 100644 --- a/Projects/Server/Buffers/STArrayPool.cs +++ b/Projects/Server/Buffers/STArrayPool.cs @@ -15,6 +15,16 @@ namespace Server.Buffers; */ public class STArrayPool : ArrayPool { +#if DEBUG_ARRAYPOOL + private class RentReturnStatus + { + public string StackTrace { get; set; } + public bool IsRented { get; set; } + } + + private static readonly ConditionalWeakTable _rentedArrays = new(); +#endif + private const int StackArraySize = 32; private const int BucketCount = 27; // SelectBucketIndex(1024 * 1024 * 1024 + 1) private static readonly STArrayPool _shared = new(); @@ -39,6 +49,12 @@ public override T[] Rent(int minimumLength) if (buffer is not null) { cachedBuckets[bucketIndex].Array = null; +#if DEBUG_ARRAYPOOL + _rentedArrays.AddOrUpdate( + buffer, + new RentReturnStatus { IsRented = true } + ); +#endif return buffer; } } @@ -52,6 +68,12 @@ public override T[] Rent(int minimumLength) buffer = b.TryPop(); if (buffer is not null) { +#if DEBUG_ARRAYPOOL + _rentedArrays.AddOrUpdate( + buffer, + new RentReturnStatus { IsRented = true } + ); +#endif return buffer; } } @@ -70,8 +92,16 @@ public override T[] Rent(int minimumLength) throw new ArgumentOutOfRangeException(nameof(minimumLength)); } - buffer = GC.AllocateUninitializedArray(minimumLength); - return buffer; + var array = GC.AllocateUninitializedArray(minimumLength); + +#if DEBUG_ARRAYPOOL + _rentedArrays.AddOrUpdate( + array, + new RentReturnStatus { IsRented = true, StackTrace = Environment.StackTrace } + ); +#endif + + return array; } public override void Return(T[]? array, bool clearArray = false) @@ -91,10 +121,26 @@ public override void Return(T[]? array, bool clearArray = false) Array.Clear(array); } +#if DEBUG_ARRAYPOOL + if (array.Length != GetMaxSizeForBucket(bucketIndex) || !_rentedArrays.TryGetValue(array, out var status)) + { + throw new ArgumentException("Buffer is not from the pool", nameof(array)); + } + + if (!status!.IsRented) + { + throw new InvalidOperationException($"Array has already been returned.\nOriginal StackTrace:{status.StackTrace}\n"); + } + + // Mark it as returned + status.IsRented = false; + status.StackTrace = Environment.StackTrace; +#else if (array.Length != GetMaxSizeForBucket(bucketIndex)) { throw new ArgumentException("Buffer is not from the pool", nameof(array)); } +#endif ref var bucketArray = ref cacheBuckets[bucketIndex]; var prev = bucketArray.Array; diff --git a/Projects/Server/PropertyList/ObjectPropertyList.cs b/Projects/Server/PropertyList/ObjectPropertyList.cs index ae3cf5ae92..5a8a94e975 100644 --- a/Projects/Server/PropertyList/ObjectPropertyList.cs +++ b/Projects/Server/PropertyList/ObjectPropertyList.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * + * Copyright 2019-2024 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ObjectPropertyList.cs * * * @@ -79,8 +79,9 @@ public void Reset() _stringNumbersIndex = 0; Header = 0; HeaderArgs = null; - STArrayPool.Shared.Return(_arrayToReturnToPool); _pos = 0; + + Dispose(); } private void Flush() @@ -499,13 +500,19 @@ private void GrowCore(uint requiredMinCapacity) public void Dispose() { - STArrayPool.Shared.Return(_arrayToReturnToPool); - _arrayToReturnToPool = null; + if (_arrayToReturnToPool != null) + { + STArrayPool.Shared.Return(_arrayToReturnToPool); + _arrayToReturnToPool = null; + } } ~ObjectPropertyList() { - STArrayPool.Shared.Return(_arrayToReturnToPool); - _arrayToReturnToPool = null; + if (_arrayToReturnToPool != null) + { + STArrayPool.Shared.Return(_arrayToReturnToPool); + _arrayToReturnToPool = null; + } } } diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs index 4a984012d6..61545077d5 100644 --- a/Projects/Server/Utilities/Utility.cs +++ b/Projects/Server/Utilities/Utility.cs @@ -1460,4 +1460,12 @@ public static int C32216(this int c32) return (r << 10) | (g << 5) | b; } + + public static void AddOrUpdate(this ConditionalWeakTable table, TKey key, TValue value) + where TKey : class + where TValue : class + { + table.Remove(key); + table.Add(key, value); + } }