Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitcee8e17

Browse files
committed
Add ref count check for helping discover the bugs of decref too much
1 parenteee3683 commitcee8e17

File tree

7 files changed

+232
-25
lines changed

7 files changed

+232
-25
lines changed

‎src/embed_tests/TestFinalizer.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ namespace Python.EmbeddingTest
88
{
99
publicclassTestFinalizer
1010
{
11-
privatestring_PYTHONMALLOC=string.Empty;
1211
privateint_oldThreshold;
1312

1413
[SetUp]
@@ -195,5 +194,46 @@ public void ErrorHandling()
195194
Finalizer.Instance.ErrorHandler-=handleFunc;
196195
}
197196
}
197+
198+
[Test]
199+
publicvoidValidateRefCount()
200+
{
201+
if(!Finalizer.Instance.RefCountValidationEnabled)
202+
{
203+
Assert.Pass("Only run with FINALIZER_CHECK");
204+
}
205+
IntPtrptr=IntPtr.Zero;
206+
boolcalled=false;
207+
Finalizer.IncorrectRefCntHandlerhandler=(s,e)=>
208+
{
209+
called=true;
210+
Assert.AreEqual(ptr,e.Handle);
211+
Assert.AreEqual(2,e.ImpactedObjects.Count);
212+
// Fix for this test, don't do this on general environment
213+
Runtime.Runtime.XIncref(e.Handle);
214+
returnfalse;
215+
};
216+
Finalizer.Instance.IncorrectRefCntResovler+=handler;
217+
try
218+
{
219+
ptr=CreateStringGarbage();
220+
FullGCCollect();
221+
Assert.Throws<Finalizer.IncorrectRefCountException>(()=>Finalizer.Instance.Collect());
222+
Assert.IsTrue(called);
223+
}
224+
finally
225+
{
226+
Finalizer.Instance.IncorrectRefCntResovler-=handler;
227+
}
228+
}
229+
230+
privatestaticIntPtrCreateStringGarbage()
231+
{
232+
PyStrings1=newPyString("test_string");
233+
// s2 steal a reference from s1
234+
PyStrings2=newPyString(s1.Handle);
235+
returns1.Handle;
236+
}
237+
198238
}
199239
}

‎src/runtime/Python.Runtime.15.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@
6868
<DefineConstantsCondition="'$(CustomDefineConstants)' == ''">$(DefineConstants);PYTHON3;$(Python3Version);$(PythonMonoDefineConstants)</DefineConstants>
6969
</PropertyGroup>
7070
<PropertyGroupCondition=" '$(Configuration)' == 'DebugMono'">
71-
<DefineConstantsCondition="'$(CustomDefineConstants)' == ''">$(DefineConstants);PYTHON2;$(Python2Version);$(PythonMonoDefineConstants);TRACE;DEBUG</DefineConstants>
71+
<DefineConstantsCondition="'$(CustomDefineConstants)' == ''">$(DefineConstants);PYTHON2;$(Python2Version);$(PythonMonoDefineConstants);FINALIZER_CHECK;TRACE;DEBUG</DefineConstants>
7272
</PropertyGroup>
7373
<PropertyGroupCondition=" '$(Configuration)' == 'DebugMonoPY3'">
74-
<DefineConstantsCondition="'$(CustomDefineConstants)' == ''">$(DefineConstants);PYTHON3;$(Python3Version);$(PythonMonoDefineConstants);TRACE;DEBUG</DefineConstants>
74+
<DefineConstantsCondition="'$(CustomDefineConstants)' == ''">$(DefineConstants);PYTHON3;$(Python3Version);$(PythonMonoDefineConstants);FINALIZER_CHECK;TRACE;DEBUG</DefineConstants>
7575
</PropertyGroup>
7676
<PropertyGroupCondition=" '$(Configuration)' == 'ReleaseWin'">
7777
<DefineConstantsCondition="'$(CustomDefineConstants)' == ''">$(DefineConstants);PYTHON2;$(Python2Version);$(PythonWinDefineConstants)</DefineConstants>
@@ -80,10 +80,10 @@
8080
<DefineConstantsCondition="'$(CustomDefineConstants)' == ''">$(DefineConstants);PYTHON3;$(Python3Version);$(PythonWinDefineConstants)</DefineConstants>
8181
</PropertyGroup>
8282
<PropertyGroupCondition=" '$(Configuration)' == 'DebugWin'">
83-
<DefineConstantsCondition="'$(CustomDefineConstants)' == ''">$(DefineConstants);PYTHON2;$(Python2Version);$(PythonWinDefineConstants);TRACE;DEBUG</DefineConstants>
83+
<DefineConstantsCondition="'$(CustomDefineConstants)' == ''">$(DefineConstants);PYTHON2;$(Python2Version);$(PythonWinDefineConstants);FINALIZER_CHECK;TRACE;DEBUG</DefineConstants>
8484
</PropertyGroup>
8585
<PropertyGroupCondition=" '$(Configuration)' == 'DebugWinPY3'">
86-
<DefineConstantsCondition="'$(CustomDefineConstants)' == ''">$(DefineConstants);PYTHON3;$(Python3Version);$(PythonWinDefineConstants);TRACE;DEBUG</DefineConstants>
86+
<DefineConstantsCondition="'$(CustomDefineConstants)' == ''">$(DefineConstants);PYTHON3;$(Python3Version);$(PythonWinDefineConstants);FINALIZER_CHECK;TRACE;DEBUG</DefineConstants>
8787
</PropertyGroup>
8888

8989
<ItemGroupCondition=" '$(PythonInteropFile)' != ''">

‎src/runtime/delegatemanager.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ A possible alternate strategy would be to create custom subclasses
181181
too "special" for this to work. It would be more work, so for now
182182
the 80/20 rule applies :) */
183183

184-
publicclassDispatcher:IDisposable
184+
publicclassDispatcher:IPyDisposable
185185
{
186186
publicIntPtrtarget;
187187
publicTypedtype;
@@ -276,6 +276,11 @@ public object TrueDispatch(ArrayList args)
276276
Runtime.XDecref(op);
277277
returnresult;
278278
}
279+
280+
publicIntPtr[]GetTrackedHandles()
281+
{
282+
returnnewIntPtr[]{target};
283+
}
279284
}
280285

281286

‎src/runtime/finalizer.cs

Lines changed: 141 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,48 @@ struct PendingArgs
3838
privatedelegateintPendingCall(IntPtrarg);
3939
privatereadonlyPendingCall_collectAction;
4040

41-
privateConcurrentQueue<IDisposable>_objQueue=newConcurrentQueue<IDisposable>();
41+
privateConcurrentQueue<IPyDisposable>_objQueue=newConcurrentQueue<IPyDisposable>();
4242
privatebool_pending=false;
4343
privatereadonlyobject_collectingLock=newobject();
4444
privateIntPtr_pendingArgs;
4545

46+
#region FINALIZER_CHECK
47+
48+
#ifFINALIZER_CHECK
49+
privatereadonlyobject_queueLock=newobject();
50+
publicboolRefCountValidationEnabled{get;set;}=true;
51+
#else
52+
publicreadonlyboolRefCountValidationEnabled=false;
53+
#endif
54+
// Keep these declarations for compat even no FINALIZER_CHECK
55+
publicclassIncorrectFinalizeArgs:EventArgs
56+
{
57+
publicIntPtrHandle{get;internalset;}
58+
publicICollection<IPyDisposable>ImpactedObjects{get;internalset;}
59+
}
60+
61+
publicclassIncorrectRefCountException:Exception
62+
{
63+
publicIntPtrPyPtr{get;internalset;}
64+
privatestring_message;
65+
publicoverridestringMessage=>_message;
66+
67+
publicIncorrectRefCountException(IntPtrptr)
68+
{
69+
PyPtr=ptr;
70+
IntPtrpyname=Runtime.PyObject_Unicode(PyPtr);
71+
stringname=Runtime.GetManagedString(pyname);
72+
Runtime.XDecref(pyname);
73+
_message=$"{name} may has a incorrect ref count";
74+
}
75+
}
76+
77+
publicdelegateboolIncorrectRefCntHandler(objectsender,IncorrectFinalizeArgse);
78+
publiceventIncorrectRefCntHandlerIncorrectRefCntResovler;
79+
publicboolThrowIfUnhandleIncorrectRefCount{get;set;}=true;
80+
81+
#endregion
82+
4683
privateFinalizer()
4784
{
4885
Enable=true;
@@ -72,7 +109,7 @@ public List<WeakReference> GetCollectedObjects()
72109
return_objQueue.Select(T=>newWeakReference(T)).ToList();
73110
}
74111

75-
internalvoidAddFinalizedObject(IDisposableobj)
112+
internalvoidAddFinalizedObject(IPyDisposableobj)
76113
{
77114
if(!Enable)
78115
{
@@ -84,7 +121,12 @@ internal void AddFinalizedObject(IDisposable obj)
84121
// for avoiding that case, user should call GC.Collect manual before shutdown.
85122
return;
86123
}
87-
_objQueue.Enqueue(obj);
124+
#ifFINALIZER_CHECK
125+
lock(_queueLock)
126+
#endif
127+
{
128+
_objQueue.Enqueue(obj);
129+
}
88130
GC.ReRegisterForFinalize(obj);
89131
if(_objQueue.Count>=Threshold)
90132
{
@@ -96,7 +138,7 @@ internal static void Shutdown()
96138
{
97139
if(Runtime.Py_IsInitialized()==0)
98140
{
99-
Instance._objQueue=newConcurrentQueue<IDisposable>();
141+
Instance._objQueue=newConcurrentQueue<IPyDisposable>();
100142
return;
101143
}
102144
Instance.DisposeAll();
@@ -175,21 +217,29 @@ private void DisposeAll()
175217
{
176218
ObjectCount=_objQueue.Count
177219
});
178-
IDisposableobj;
179-
while(_objQueue.TryDequeue(outobj))
220+
#ifFINALIZER_CHECK
221+
lock(_queueLock)
222+
#endif
180223
{
181-
try
182-
{
183-
obj.Dispose();
184-
Runtime.CheckExceptionOccurred();
185-
}
186-
catch(Exceptione)
224+
#ifFINALIZER_CHECK
225+
ValidateRefCount();
226+
#endif
227+
IPyDisposableobj;
228+
while(_objQueue.TryDequeue(outobj))
187229
{
188-
// We should not bother the main thread
189-
ErrorHandler?.Invoke(this,newErrorArgs()
230+
try
231+
{
232+
obj.Dispose();
233+
Runtime.CheckExceptionOccurred();
234+
}
235+
catch(Exceptione)
190236
{
191-
Error=e
192-
});
237+
// We should not bother the main thread
238+
ErrorHandler?.Invoke(this,newErrorArgs()
239+
{
240+
Error=e
241+
});
242+
}
193243
}
194244
}
195245
}
@@ -202,5 +252,80 @@ private void ResetPending()
202252
_pendingArgs=IntPtr.Zero;
203253
}
204254
}
255+
256+
#ifFINALIZER_CHECK
257+
privatevoidValidateRefCount()
258+
{
259+
if(!RefCountValidationEnabled)
260+
{
261+
return;
262+
}
263+
varcounter=newDictionary<IntPtr,long>();
264+
varholdRefs=newDictionary<IntPtr,long>();
265+
varindexer=newDictionary<IntPtr,List<IPyDisposable>>();
266+
foreach(varobjin_objQueue)
267+
{
268+
IntPtr[]handles=obj.GetTrackedHandles();
269+
foreach(varhandleinhandles)
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,outobjs))
286+
{
287+
objs=newList<IPyDisposable>();
288+
indexer.Add(handle,objs);
289+
}
290+
objs.Add(obj);
291+
}
292+
}
293+
foreach(varpairincounter)
294+
{
295+
IntPtrhandle=pair.Key;
296+
longcnt=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+
varargs=newIncorrectFinalizeArgs()
302+
{
303+
Handle=handle,
304+
ImpactedObjects=indexer[handle]
305+
};
306+
boolhandled=false;
307+
if(IncorrectRefCntResovler!=null)
308+
{
309+
varfuncList=IncorrectRefCntResovler.GetInvocationList();
310+
foreach(IncorrectRefCntHandlerfuncinfuncList)
311+
{
312+
if(func(this,args))
313+
{
314+
handled=true;
315+
break;
316+
}
317+
}
318+
}
319+
if(!handled&&ThrowIfUnhandleIncorrectRefCount)
320+
{
321+
thrownewIncorrectRefCountException(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
205330
}
206331
}

‎src/runtime/pyobject.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
11
usingSystem;
22
usingSystem.Collections;
33
usingSystem.Collections.Generic;
4+
usingSystem.Diagnostics;
45
usingSystem.Dynamic;
56
usingSystem.Linq.Expressions;
67

78
namespacePython.Runtime
89
{
10+
publicinterfaceIPyDisposable:IDisposable
11+
{
12+
IntPtr[]GetTrackedHandles();
13+
}
14+
915
/// <summary>
1016
/// Represents a generic Python object. The methods of this class are
1117
/// generally equivalent to the Python "abstract object API". See
1218
/// PY2: https://docs.python.org/2/c-api/object.html
1319
/// PY3: https://docs.python.org/3/c-api/object.html
1420
/// for details.
1521
/// </summary>
16-
publicclassPyObject:DynamicObject,IEnumerable,IDisposable
22+
publicclassPyObject:DynamicObject,IEnumerable,IPyDisposable
1723
{
24+
#ifTRACE_ALLOC
25+
/// <summary>
26+
/// Trace stack for PyObject's construction
27+
/// </summary>
28+
publicStackTraceTraceback{get;privateset;}
29+
#endif
30+
1831
protectedinternalIntPtrobj=IntPtr.Zero;
1932
privatebooldisposed=false;
2033
privatebool_finalized=false;
@@ -31,19 +44,29 @@ public class PyObject : DynamicObject, IEnumerable, IDisposable
3144
publicPyObject(IntPtrptr)
3245
{
3346
obj=ptr;
47+
#ifTRACE_ALLOC
48+
Traceback=newStackTrace(1);
49+
#endif
3450
}
3551

3652
// Protected default constructor to allow subclasses to manage
3753
// initialization in different ways as appropriate.
3854

3955
protectedPyObject()
4056
{
57+
#ifTRACE_ALLOC
58+
Traceback=newStackTrace(1);
59+
#endif
4160
}
4261

4362
// Ensure that encapsulated Python object is decref'ed appropriately
4463
// when the managed wrapper is garbage-collected.
4564
~PyObject()
4665
{
66+
if(obj==IntPtr.Zero)
67+
{
68+
return;
69+
}
4770
if(_finalized||disposed)
4871
{
4972
return;
@@ -158,6 +181,10 @@ public void Dispose()
158181
GC.SuppressFinalize(this);
159182
}
160183

184+
publicIntPtr[]GetTrackedHandles()
185+
{
186+
returnnewIntPtr[]{obj};
187+
}
161188

162189
/// <summary>
163190
/// GetPythonType Method

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp