@@ -28,25 +28,16 @@ public void TearDown()
28
28
PythonEngine . Shutdown ( ) ;
29
29
}
30
30
31
- private static bool FullGCCollect ( )
31
+ private static void FullGCCollect ( )
32
32
{
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 ( ) ;
47
37
}
48
38
49
39
[ Test ]
40
+ [ Obsolete ( "GC tests are not guaranteed" ) ]
50
41
public void CollectBasicObject ( )
51
42
{
52
43
Assert . IsTrue ( Finalizer . Instance . Enable ) ;
@@ -103,17 +94,13 @@ public void CollectBasicObject()
103
94
}
104
95
105
96
[ Test ]
97
+ [ Obsolete ( "GC tests are not guaranteed" ) ]
106
98
public void CollectOnShutdown ( )
107
99
{
108
100
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 ( ) ;
115
102
Assert . IsFalse ( shortWeak . IsAlive ) ;
116
- garbage = Finalizer . Instance . GetCollectedObjects ( ) ;
103
+ List < WeakReference > garbage = Finalizer . Instance . GetCollectedObjects ( ) ;
117
104
Assert . IsNotEmpty ( garbage , "The garbage object should be collected" ) ;
118
105
Assert . IsTrue ( garbage . Any ( r=> ReferenceEquals ( r . Target , longWeak . Target ) ) ,
119
106
"Garbage should contains the collected object" ) ;
@@ -123,13 +110,29 @@ public void CollectOnShutdown()
123
110
Assert . IsEmpty ( garbage ) ;
124
111
}
125
112
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" ) ]
127
115
private static IntPtr MakeAGarbage ( out WeakReference shortWeak , out WeakReference longWeak )
128
116
{
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 ;
133
136
}
134
137
135
138
private static long CompareWithFinalizerOn ( PyObject pyCollect , bool enbale )
@@ -210,6 +213,7 @@ internal static void CreateMyPyObject(IntPtr op)
210
213
}
211
214
212
215
[ Test ]
216
+ [ Obsolete ( "GC tests are not guaranteed" ) ]
213
217
public void ErrorHandling ( )
214
218
{
215
219
bool called = false ;
@@ -227,7 +231,7 @@ public void ErrorHandling()
227
231
WeakReference longWeak ;
228
232
{
229
233
MakeAGarbage ( out shortWeak , out longWeak ) ;
230
- var obj = ( PyLong ) longWeak . Target ;
234
+ var obj = ( PyObject ) longWeak . Target ;
231
235
IntPtr handle = obj . Handle ;
232
236
shortWeak = null ;
233
237
longWeak = null ;
@@ -278,36 +282,13 @@ public void ValidateRefCount()
278
282
}
279
283
}
280
284
285
+ [ MethodImpl ( MethodImplOptions . NoInlining | MethodImplOptions . NoOptimization ) ] // ensure lack of references to s1 and s2
281
286
private static IntPtr CreateStringGarbage ( )
282
287
{
283
288
PyString s1 = new PyString ( "test_string" ) ;
284
289
// s2 steal a reference from s1
285
290
PyString s2 = new PyString ( s1 . Handle ) ;
286
291
return s1 . Handle ;
287
292
}
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
- }
312
293
}
313
294
}