@@ -28,25 +28,16 @@ public void TearDown()
2828 PythonEngine . Shutdown ( ) ;
2929 }
3030
31- private static bool FullGCCollect ( )
31+ private static void FullGCCollect ( )
3232 {
33- GC . Collect ( GC . MaxGeneration , GCCollectionMode . Forced ) ;
34- try
35- {
36- return GC . WaitForFullGCComplete ( ) == GCNotificationStatus . Succeeded ;
37- }
38- catch ( NotImplementedException )
39- {
40- // Some clr runtime didn't implement GC.WaitForFullGCComplete yet.
41- return false ;
42- }
43- finally
44- {
45- GC . WaitForPendingFinalizers ( ) ;
46- }
33+ Thread . MemoryBarrier ( ) ;
34+ GC . Collect ( ) ;
35+ GC . WaitForPendingFinalizers ( ) ;
36+ Thread . MemoryBarrier ( ) ;
4737 }
4838
4939 [ Test ]
40+ [ Obsolete ( "GC tests are not guaranteed" ) ]
5041 public void CollectBasicObject ( )
5142 {
5243 Assert . IsTrue ( Finalizer . Instance . Enable ) ;
@@ -103,17 +94,13 @@ public void CollectBasicObject()
10394 }
10495
10596 [ Test ]
97+ [ Obsolete ( "GC tests are not guaranteed" ) ]
10698 public void CollectOnShutdown ( )
10799 {
108100 IntPtr op = MakeAGarbage ( out var shortWeak , out var longWeak ) ;
109- int hash = shortWeak . Target . GetHashCode ( ) ;
110- List < WeakReference > garbage ;
111- if ( ! FullGCCollect ( ) )
112- {
113- Assert . IsTrue ( WaitForCollected ( op , hash , 10000 ) ) ;
114- }
101+ FullGCCollect ( ) ;
115102 Assert . IsFalse ( shortWeak . IsAlive ) ;
116- garbage = Finalizer . Instance . GetCollectedObjects ( ) ;
103+ List < WeakReference > garbage = Finalizer . Instance . GetCollectedObjects ( ) ;
117104 Assert . IsNotEmpty ( garbage , "The garbage object should be collected" ) ;
118105 Assert . IsTrue ( garbage . Any ( r => ReferenceEquals ( r . Target , longWeak . Target ) ) ,
119106 "Garbage should contains the collected object" ) ;
@@ -123,13 +110,29 @@ public void CollectOnShutdown()
123110 Assert . IsEmpty ( garbage ) ;
124111 }
125112
126- [ MethodImpl ( MethodImplOptions . NoInlining ) ] // ensure lack of references to obj
113+ [ MethodImpl ( MethodImplOptions . NoInlining | MethodImplOptions . NoOptimization ) ] // ensure lack of references to obj
114+ [ Obsolete ( "GC tests are not guaranteed" ) ]
127115 private static IntPtr MakeAGarbage ( out WeakReference shortWeak , out WeakReference longWeak )
128116 {
129- PyLong obj = new PyLong ( 1024 ) ;
130- shortWeak = new WeakReference ( obj ) ;
131- longWeak = new WeakReference ( obj , true ) ;
132- return obj . Handle ;
117+ IntPtr handle = IntPtr . Zero ;
118+ WeakReference @short = null , @long = null ;
119+ // must create Python object in the thread where we have GIL
120+ IntPtr val = PyLong . FromLong ( 1024 ) ;
121+ // must create temp object in a different thread to ensure it is not present
122+ // when conservatively scanning stack for GC roots.
123+ // see https://xamarin.github.io/bugzilla-archives/17/17593/bug.html
124+ var garbageGen = new Thread ( ( ) =>
125+ {
126+ var obj = new PyObject ( val , skipCollect : true ) ;
127+ @short = new WeakReference ( obj ) ;
128+ @long = new WeakReference ( obj , true ) ;
129+ handle = obj . Handle ;
130+ } ) ;
131+ garbageGen . Start ( ) ;
132+ Assert . IsTrue ( garbageGen . Join ( TimeSpan . FromSeconds ( 5 ) ) , "Garbage creation timed out" ) ;
133+ shortWeak = @short ;
134+ longWeak = @long ;
135+ return handle ;
133136 }
134137
135138 private static long CompareWithFinalizerOn ( PyObject pyCollect , bool enbale )
@@ -210,6 +213,7 @@ internal static void CreateMyPyObject(IntPtr op)
210213 }
211214
212215 [ Test ]
216+ [ Obsolete ( "GC tests are not guaranteed" ) ]
213217 public void ErrorHandling ( )
214218 {
215219 bool called = false ;
@@ -227,7 +231,7 @@ public void ErrorHandling()
227231 WeakReference longWeak ;
228232 {
229233 MakeAGarbage ( out shortWeak , out longWeak ) ;
230- var obj = ( PyLong ) longWeak . Target ;
234+ var obj = ( PyObject ) longWeak . Target ;
231235 IntPtr handle = obj . Handle ;
232236 shortWeak = null ;
233237 longWeak = null ;
@@ -278,36 +282,13 @@ public void ValidateRefCount()
278282 }
279283 }
280284
285+ [ MethodImpl ( MethodImplOptions . NoInlining | MethodImplOptions . NoOptimization ) ] // ensure lack of references to s1 and s2
281286 private static IntPtr CreateStringGarbage ( )
282287 {
283288 PyString s1 = new PyString ( "test_string" ) ;
284289 // s2 steal a reference from s1
285290 PyString s2 = new PyString ( s1 . Handle ) ;
286291 return s1 . Handle ;
287292 }
288-
289- private static bool WaitForCollected ( IntPtr op , int hash , int milliseconds )
290- {
291- var stopwatch = Stopwatch . StartNew ( ) ;
292- do
293- {
294- var garbage = Finalizer . Instance . GetCollectedObjects ( ) ;
295- foreach ( var item in garbage )
296- {
297- // The validation is not 100% precise,
298- // but it's rare that two conditions satisfied but they're still not the same object.
299- if ( item . Target . GetHashCode ( ) != hash )
300- {
301- continue ;
302- }
303- var obj = ( IPyDisposable ) item . Target ;
304- if ( obj . GetTrackedHandles ( ) . Contains ( op ) )
305- {
306- return true ;
307- }
308- }
309- } while ( stopwatch . ElapsedMilliseconds < milliseconds ) ;
310- return false ;
311- }
312293 }
313294}
0 commit comments