diff --git a/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs b/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs index f2a433ef1..057242bae 100644 --- a/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs +++ b/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs @@ -17,6 +17,10 @@ namespace SixLabors.ImageSharp.Diagnostics { internal static readonly InteralMemoryDiagnostics Default = new(); private static AsyncLocal localInstance = null; + + // the async local end up out of scope during finalizers so putting into thte internal class is useless + private static UndisposedAllocationDelegate undisposedAllocation; + private static int undisposedAllocationSubscriptionCounter; private static readonly object SyncRoot = new(); /// @@ -26,8 +30,23 @@ namespace SixLabors.ImageSharp.Diagnostics /// public static event UndisposedAllocationDelegate UndisposedAllocation { - add => Current.UndisposedAllocation += value; - remove => Current.UndisposedAllocation -= value; + add + { + lock (SyncRoot) + { + undisposedAllocationSubscriptionCounter++; + undisposedAllocation += value; + } + } + + remove + { + lock (SyncRoot) + { + undisposedAllocation -= value; + undisposedAllocationSubscriptionCounter--; + } + } } internal static InteralMemoryDiagnostics Current @@ -61,81 +80,46 @@ namespace SixLabors.ImageSharp.Diagnostics /// public static int TotalUndisposedAllocationCount => Current.TotalUndisposedAllocationCount; - internal static bool UndisposedAllocationSubscribed => Current.UndisposedAllocationSubscribed; + internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0; internal static void IncrementTotalUndisposedAllocationCount() => Current.IncrementTotalUndisposedAllocationCount(); internal static void DecrementTotalUndisposedAllocationCount() => Current.DecrementTotalUndisposedAllocationCount(); internal static void RaiseUndisposedMemoryResource(string allocationStackTrace) - => Current.RaiseUndisposedMemoryResource(allocationStackTrace); + { + if (undisposedAllocation is null) + { + return; + } + + // Schedule on the ThreadPool, to avoid user callback messing up the finalizer thread. +#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER + ThreadPool.QueueUserWorkItem( + stackTrace => undisposedAllocation?.Invoke(stackTrace), + allocationStackTrace, + preferLocal: false); +#else + ThreadPool.QueueUserWorkItem( + stackTrace => undisposedAllocation?.Invoke((string)stackTrace), + allocationStackTrace); +#endif + } internal class InteralMemoryDiagnostics { private int totalUndisposedAllocationCount; - private UndisposedAllocationDelegate undisposedAllocation; - private int undisposedAllocationSubscriptionCounter; - private readonly object syncRoot = new(); - - /// - /// Fires when an ImageSharp object's undisposed memory resource leaks to the finalizer. - /// The event brings significant overhead, and is intended to be used for troubleshooting only. - /// For production diagnostics, use . - /// - public event UndisposedAllocationDelegate UndisposedAllocation - { - add - { - lock (this.syncRoot) - { - this.undisposedAllocationSubscriptionCounter++; - this.undisposedAllocation += value; - } - } - - remove - { - lock (this.syncRoot) - { - this.undisposedAllocation -= value; - this.undisposedAllocationSubscriptionCounter--; - } - } - } - /// /// Gets a value indicating the total number of memory resource objects leaked to the finalizer. /// public int TotalUndisposedAllocationCount => this.totalUndisposedAllocationCount; - internal bool UndisposedAllocationSubscribed => Volatile.Read(ref this.undisposedAllocationSubscriptionCounter) > 0; - internal void IncrementTotalUndisposedAllocationCount() => Interlocked.Increment(ref this.totalUndisposedAllocationCount); internal void DecrementTotalUndisposedAllocationCount() => Interlocked.Decrement(ref this.totalUndisposedAllocationCount); - - internal void RaiseUndisposedMemoryResource(string allocationStackTrace) - { - if (this.undisposedAllocation is null) - { - return; - } - - // Schedule on the ThreadPool, to avoid user callback messing up the finalizer thread. -#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER - ThreadPool.QueueUserWorkItem( - stackTrace => this.undisposedAllocation?.Invoke(stackTrace), - allocationStackTrace, - preferLocal: false); -#else - ThreadPool.QueueUserWorkItem( - stackTrace => this.undisposedAllocation?.Invoke((string)stackTrace), - allocationStackTrace); -#endif - } } } }