@@ -38,11 +38,48 @@ struct PendingArgs
38
38
private delegate int PendingCall ( IntPtr arg ) ;
39
39
private readonly PendingCall _collectAction ;
40
40
41
- private ConcurrentQueue < IDisposable > _objQueue = new ConcurrentQueue < IDisposable > ( ) ;
41
+ private ConcurrentQueue < IPyDisposable > _objQueue = new ConcurrentQueue < IPyDisposable > ( ) ;
42
42
private bool _pending = false ;
43
43
private readonly object _collectingLock = new object ( ) ;
44
44
private IntPtr _pendingArgs ;
45
45
46
+ #region FINALIZER_CHECK
47
+
48
+ #if FINALIZER_CHECK
49
+ private readonly object _queueLock = new object ( ) ;
50
+ public bool RefCountValidationEnabled { get ; set ; } = true ;
51
+ #else
52
+ public readonly bool RefCountValidationEnabled = false ;
53
+ #endif
54
+ // Keep these declarations for compat even no FINALIZER_CHECK
55
+ public class IncorrectFinalizeArgs : EventArgs
56
+ {
57
+ public IntPtr Handle { get ; internal set ; }
58
+ public ICollection < IPyDisposable > ImpactedObjects { get ; internal set ; }
59
+ }
60
+
61
+ public class IncorrectRefCountException : Exception
62
+ {
63
+ public IntPtr PyPtr { get ; internal set ; }
64
+ private string _message ;
65
+ public override string Message => _message ;
66
+
67
+ public IncorrectRefCountException ( IntPtr ptr )
68
+ {
69
+ PyPtr = ptr ;
70
+ IntPtr pyname = Runtime . PyObject_Unicode ( PyPtr ) ;
71
+ string name = Runtime . GetManagedString ( pyname ) ;
72
+ Runtime . XDecref ( pyname ) ;
73
+ _message = $ "{ name } may has a incorrect ref count";
74
+ }
75
+ }
76
+
77
+ public delegate bool IncorrectRefCntHandler ( object sender , IncorrectFinalizeArgs e ) ;
78
+ public event IncorrectRefCntHandler IncorrectRefCntResovler ;
79
+ public bool ThrowIfUnhandleIncorrectRefCount { get ; set ; } = true ;
80
+
81
+ #endregion
82
+
46
83
private Finalizer ( )
47
84
{
48
85
Enable = true ;
@@ -72,7 +109,7 @@ public List<WeakReference> GetCollectedObjects()
72
109
return _objQueue . Select ( T => new WeakReference ( T ) ) . ToList ( ) ;
73
110
}
74
111
75
- internal void AddFinalizedObject ( IDisposable obj )
112
+ internal void AddFinalizedObject ( IPyDisposable obj )
76
113
{
77
114
if ( ! Enable )
78
115
{
@@ -84,7 +121,12 @@ internal void AddFinalizedObject(IDisposable obj)
84
121
// for avoiding that case, user should call GC.Collect manual before shutdown.
85
122
return ;
86
123
}
87
- _objQueue . Enqueue ( obj ) ;
124
+ #if FINALIZER_CHECK
125
+ lock ( _queueLock )
126
+ #endif
127
+ {
128
+ _objQueue . Enqueue ( obj ) ;
129
+ }
88
130
GC . ReRegisterForFinalize ( obj ) ;
89
131
if ( _objQueue . Count >= Threshold )
90
132
{
@@ -96,7 +138,7 @@ internal static void Shutdown()
96
138
{
97
139
if ( Runtime . Py_IsInitialized ( ) == 0 )
98
140
{
99
- Instance . _objQueue = new ConcurrentQueue < IDisposable > ( ) ;
141
+ Instance . _objQueue = new ConcurrentQueue < IPyDisposable > ( ) ;
100
142
return ;
101
143
}
102
144
Instance . DisposeAll ( ) ;
@@ -175,21 +217,29 @@ private void DisposeAll()
175
217
{
176
218
ObjectCount = _objQueue . Count
177
219
} ) ;
178
- IDisposable obj ;
179
- while ( _objQueue . TryDequeue ( out obj ) )
220
+ #if FINALIZER_CHECK
221
+ lock ( _queueLock )
222
+ #endif
180
223
{
181
- try
182
- {
183
- obj . Dispose ( ) ;
184
- Runtime . CheckExceptionOccurred ( ) ;
185
- }
186
- catch ( Exception e )
224
+ #if FINALIZER_CHECK
225
+ ValidateRefCount ( ) ;
226
+ #endif
227
+ IPyDisposable obj ;
228
+ while ( _objQueue . TryDequeue ( out obj ) )
187
229
{
188
- // We should not bother the main thread
189
- ErrorHandler ? . Invoke ( this , new ErrorArgs ( )
230
+ try
231
+ {
232
+ obj . Dispose ( ) ;
233
+ Runtime . CheckExceptionOccurred ( ) ;
234
+ }
235
+ catch ( Exception e )
190
236
{
191
- Error = e
192
- } ) ;
237
+ // We should not bother the main thread
238
+ ErrorHandler ? . Invoke ( this , new ErrorArgs ( )
239
+ {
240
+ Error = e
241
+ } ) ;
242
+ }
193
243
}
194
244
}
195
245
}
@@ -202,5 +252,80 @@ private void ResetPending()
202
252
_pendingArgs = IntPtr . Zero ;
203
253
}
204
254
}
255
+
256
+ #if FINALIZER_CHECK
257
+ private void ValidateRefCount ( )
258
+ {
259
+ if ( ! RefCountValidationEnabled )
260
+ {
261
+ return ;
262
+ }
263
+ var counter = new Dictionary < IntPtr , long > ( ) ;
264
+ var holdRefs = new Dictionary < IntPtr , long > ( ) ;
265
+ var indexer = new Dictionary < IntPtr , List < IPyDisposable > > ( ) ;
266
+ foreach ( var obj in _objQueue )
267
+ {
268
+ IntPtr [ ] handles = obj . GetTrackedHandles ( ) ;
269
+ foreach ( var handle in handles )
270
+ {
271
+ if ( handle == IntPtr . Zero )
272
+ {
273
+ continue ;
274
+ }
275
+ if ( ! counter . ContainsKey ( handle ) )
276
+ {
277
+ counter [ handle ] = 0 ;
278
+ }
279
+ counter [ handle ] ++ ;
280
+ if ( ! holdRefs . ContainsKey ( handle ) )
281
+ {
282
+ holdRefs [ handle ] = Runtime . Refcount ( handle ) ;
283
+ }
284
+ List < IPyDisposable > objs ;
285
+ if ( ! indexer . TryGetValue ( handle , out objs ) )
286
+ {
287
+ objs = new List < IPyDisposable > ( ) ;
288
+ indexer . Add ( handle , objs ) ;
289
+ }
290
+ objs . Add ( obj ) ;
291
+ }
292
+ }
293
+ foreach ( var pair in counter )
294
+ {
295
+ IntPtr handle = pair . Key ;
296
+ long cnt = pair . Value ;
297
+ // Tracked handle's ref count is larger than the object's holds
298
+ // it may take an unspecified behaviour if it decref in Dispose
299
+ if ( cnt > holdRefs [ handle ] )
300
+ {
301
+ var args = new IncorrectFinalizeArgs ( )
302
+ {
303
+ Handle = handle ,
304
+ ImpactedObjects = indexer [ handle ]
305
+ } ;
306
+ bool handled = false ;
307
+ if ( IncorrectRefCntResovler != null )
308
+ {
309
+ var funcList = IncorrectRefCntResovler . GetInvocationList ( ) ;
310
+ foreach ( IncorrectRefCntHandler func in funcList )
311
+ {
312
+ if ( func ( this , args ) )
313
+ {
314
+ handled = true ;
315
+ break ;
316
+ }
317
+ }
318
+ }
319
+ if ( ! handled && ThrowIfUnhandleIncorrectRefCount )
320
+ {
321
+ throw new IncorrectRefCountException ( handle ) ;
322
+ }
323
+ }
324
+ // Make sure no other references for PyObjects after this method
325
+ indexer [ handle ] . Clear ( ) ;
326
+ }
327
+ indexer . Clear ( ) ;
328
+ }
329
+ #endif
205
330
}
206
331
}
0 commit comments