@@ -15,10 +15,8 @@ namespace SixLabors.ImageSharp.Diagnostics
1515 /// </summary>
1616 public static class MemoryDiagnostics
1717 {
18- private static int totalUndisposedAllocationCount ;
19-
20- private static UndisposedAllocationDelegate undisposedAllocation ;
21- private static int undisposedAllocationSubscriptionCounter ;
18+ internal static readonly InteralMemoryDiagnostics Default = new ( ) ;
19+ private static AsyncLocal < InteralMemoryDiagnostics > localInstance = null ;
2220 private static readonly object SyncRoot = new ( ) ;
2321
2422 /// <summary>
@@ -28,56 +26,116 @@ public static class MemoryDiagnostics
2826 /// </summary>
2927 public static event UndisposedAllocationDelegate UndisposedAllocation
3028 {
31- add
29+ add => Current . UndisposedAllocation += value ;
30+ remove => Current . UndisposedAllocation -= value ;
31+ }
32+
33+ internal static InteralMemoryDiagnostics Current
34+ {
35+ get
3236 {
33- lock ( SyncRoot )
37+ if ( localInstance != null && localInstance . Value != null )
3438 {
35- undisposedAllocationSubscriptionCounter ++ ;
36- undisposedAllocation += value ;
39+ return localInstance . Value ;
3740 }
41+
42+ return Default ;
3843 }
3944
40- remove
45+ set
4146 {
42- lock ( SyncRoot )
47+ if ( localInstance == null )
4348 {
44- undisposedAllocation -= value ;
45- undisposedAllocationSubscriptionCounter -- ;
49+ lock ( SyncRoot )
50+ {
51+ localInstance ??= new AsyncLocal < InteralMemoryDiagnostics > ( ) ;
52+ }
4653 }
54+
55+ localInstance . Value = value ;
4756 }
4857 }
4958
5059 /// <summary>
5160 /// Gets a value indicating the total number of memory resource objects leaked to the finalizer.
5261 /// </summary>
53- public static int TotalUndisposedAllocationCount => totalUndisposedAllocationCount ;
62+ public static int TotalUndisposedAllocationCount => Current . TotalUndisposedAllocationCount ;
5463
55- internal static bool UndisposedAllocationSubscribed => Volatile . Read ( ref undisposedAllocationSubscriptionCounter ) > 0 ;
64+ internal static bool UndisposedAllocationSubscribed => Current . UndisposedAllocationSubscribed ;
5665
57- internal static void IncrementTotalUndisposedAllocationCount ( ) =>
58- Interlocked . Increment ( ref totalUndisposedAllocationCount ) ;
66+ internal static void IncrementTotalUndisposedAllocationCount ( ) => Current . IncrementTotalUndisposedAllocationCount ( ) ;
5967
60- internal static void DecrementTotalUndisposedAllocationCount ( ) =>
61- Interlocked . Decrement ( ref totalUndisposedAllocationCount ) ;
68+ internal static void DecrementTotalUndisposedAllocationCount ( ) => Current . DecrementTotalUndisposedAllocationCount ( ) ;
6269
6370 internal static void RaiseUndisposedMemoryResource ( string allocationStackTrace )
71+ => Current . RaiseUndisposedMemoryResource ( allocationStackTrace ) ;
72+
73+ internal class InteralMemoryDiagnostics
6474 {
65- if ( undisposedAllocation is null )
75+ private int totalUndisposedAllocationCount ;
76+
77+ private UndisposedAllocationDelegate undisposedAllocation ;
78+ private int undisposedAllocationSubscriptionCounter ;
79+ private readonly object syncRoot = new ( ) ;
80+
81+ /// <summary>
82+ /// Fires when an ImageSharp object's undisposed memory resource leaks to the finalizer.
83+ /// The event brings significant overhead, and is intended to be used for troubleshooting only.
84+ /// For production diagnostics, use <see cref="TotalUndisposedAllocationCount"/>.
85+ /// </summary>
86+ public event UndisposedAllocationDelegate UndisposedAllocation
6687 {
67- return ;
88+ add
89+ {
90+ lock ( this . syncRoot )
91+ {
92+ this . undisposedAllocationSubscriptionCounter ++ ;
93+ this . undisposedAllocation += value ;
94+ }
95+ }
96+
97+ remove
98+ {
99+ lock ( this . syncRoot )
100+ {
101+ this . undisposedAllocation -= value ;
102+ this . undisposedAllocationSubscriptionCounter -- ;
103+ }
104+ }
68105 }
69106
70- // Schedule on the ThreadPool, to avoid user callback messing up the finalizer thread.
107+ /// <summary>
108+ /// Gets a value indicating the total number of memory resource objects leaked to the finalizer.
109+ /// </summary>
110+ public int TotalUndisposedAllocationCount => this . totalUndisposedAllocationCount ;
111+
112+ internal bool UndisposedAllocationSubscribed => Volatile . Read ( ref this . undisposedAllocationSubscriptionCounter ) > 0 ;
113+
114+ internal void IncrementTotalUndisposedAllocationCount ( ) =>
115+ Interlocked . Increment ( ref this . totalUndisposedAllocationCount ) ;
116+
117+ internal void DecrementTotalUndisposedAllocationCount ( ) =>
118+ Interlocked . Decrement ( ref this . totalUndisposedAllocationCount ) ;
119+
120+ internal void RaiseUndisposedMemoryResource ( string allocationStackTrace )
121+ {
122+ if ( this . undisposedAllocation is null )
123+ {
124+ return ;
125+ }
126+
127+ // Schedule on the ThreadPool, to avoid user callback messing up the finalizer thread.
71128#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER
72- ThreadPool . QueueUserWorkItem (
73- stackTrace => undisposedAllocation ? . Invoke ( stackTrace ) ,
74- allocationStackTrace ,
75- preferLocal : false ) ;
129+ ThreadPool . QueueUserWorkItem (
130+ stackTrace => this . undisposedAllocation ? . Invoke ( stackTrace ) ,
131+ allocationStackTrace ,
132+ preferLocal : false ) ;
76133#else
77134 ThreadPool . QueueUserWorkItem (
78- stackTrace => undisposedAllocation ? . Invoke ( ( string ) stackTrace ) ,
135+ stackTrace => this . undisposedAllocation ? . Invoke ( ( string ) stackTrace ) ,
79136 allocationStackTrace ) ;
80137#endif
138+ }
81139 }
82140 }
83141}
0 commit comments