2
2
using Python . Runtime ;
3
3
using System ;
4
4
using System . Collections . Generic ;
5
- using System . ComponentModel ;
6
5
using System . Diagnostics ;
7
6
using System . Linq ;
7
+ using System . Runtime . CompilerServices ;
8
8
using System . Threading ;
9
9
10
10
namespace Python . EmbeddingTest
@@ -28,26 +28,14 @@ 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
+ GC . Collect ( ) ;
34
+ GC . WaitForPendingFinalizers ( ) ;
47
35
}
48
36
49
37
[ Test ]
50
- [ Ignore ( "Ignore temporarily ") ]
38
+ [ Obsolete ( "GC tests are not guaranteed ") ]
51
39
public void CollectBasicObject ( )
52
40
{
53
41
Assert . IsTrue ( Finalizer . Instance . Enable ) ;
@@ -64,11 +52,7 @@ public void CollectBasicObject()
64
52
Assert . IsFalse ( called , "The event handler was called before it was installed" ) ;
65
53
Finalizer . Instance . CollectOnce += handler ;
66
54
67
- WeakReference shortWeak ;
68
- WeakReference longWeak ;
69
- {
70
- MakeAGarbage ( out shortWeak , out longWeak ) ;
71
- }
55
+ IntPtr pyObj = MakeAGarbage ( out var shortWeak , out var longWeak ) ;
72
56
FullGCCollect ( ) ;
73
57
// The object has been resurrected
74
58
Warn . If (
@@ -86,7 +70,7 @@ public void CollectBasicObject()
86
70
var garbage = Finalizer . Instance . GetCollectedObjects ( ) ;
87
71
Assert . NotZero ( garbage . Count , "There should still be garbage around" ) ;
88
72
Warn . Unless (
89
- garbage . Any ( T => ReferenceEquals ( T . Target , longWeak . Target ) ) ,
73
+ garbage . Contains ( pyObj ) ,
90
74
$ "The { nameof ( longWeak ) } reference doesn't show up in the garbage list",
91
75
garbage
92
76
) ;
@@ -104,33 +88,45 @@ public void CollectBasicObject()
104
88
}
105
89
106
90
[ Test ]
107
- [ Ignore ( "Ignore temporarily ") ]
91
+ [ Obsolete ( "GC tests are not guaranteed ") ]
108
92
public void CollectOnShutdown ( )
109
93
{
110
94
IntPtr op = MakeAGarbage ( out var shortWeak , out var longWeak ) ;
111
- int hash = shortWeak . Target . GetHashCode ( ) ;
112
- List < WeakReference > garbage ;
113
- if ( ! FullGCCollect ( ) )
114
- {
115
- Assert . IsTrue ( WaitForCollected ( op , hash , 10000 ) ) ;
116
- }
95
+ FullGCCollect ( ) ;
117
96
Assert . IsFalse ( shortWeak . IsAlive ) ;
118
- garbage = Finalizer . Instance . GetCollectedObjects ( ) ;
97
+ List < IntPtr > garbage = Finalizer . Instance . GetCollectedObjects ( ) ;
119
98
Assert . IsNotEmpty ( garbage , "The garbage object should be collected" ) ;
120
- Assert . IsTrue ( garbage . Any ( r => ReferenceEquals ( r . Target , longWeak . Target ) ) ,
99
+ Assert . IsTrue ( garbage . Contains ( op ) ,
121
100
"Garbage should contains the collected object" ) ;
122
101
123
102
PythonEngine . Shutdown ( ) ;
124
103
garbage = Finalizer . Instance . GetCollectedObjects ( ) ;
125
104
Assert . IsEmpty ( garbage ) ;
126
105
}
127
106
107
+ [ MethodImpl ( MethodImplOptions . NoInlining | MethodImplOptions . NoOptimization ) ] // ensure lack of references to obj
108
+ [ Obsolete ( "GC tests are not guaranteed" ) ]
128
109
private static IntPtr MakeAGarbage ( out WeakReference shortWeak , out WeakReference longWeak )
129
110
{
130
- PyLong obj = new PyLong ( 1024 ) ;
131
- shortWeak = new WeakReference ( obj ) ;
132
- longWeak = new WeakReference ( obj , true ) ;
133
- return obj . Handle ;
111
+ IntPtr handle = IntPtr . Zero ;
112
+ WeakReference @short = null , @long = null ;
113
+ // must create Python object in the thread where we have GIL
114
+ IntPtr val = PyLong . FromLong ( 1024 ) ;
115
+ // must create temp object in a different thread to ensure it is not present
116
+ // when conservatively scanning stack for GC roots.
117
+ // see https://xamarin.github.io/bugzilla-archives/17/17593/bug.html
118
+ var garbageGen = new Thread ( ( ) =>
119
+ {
120
+ var obj = new PyObject ( val , skipCollect : true ) ;
121
+ @short = new WeakReference ( obj ) ;
122
+ @long = new WeakReference ( obj , true ) ;
123
+ handle = obj . Handle ;
124
+ } ) ;
125
+ garbageGen . Start ( ) ;
126
+ Assert . IsTrue ( garbageGen . Join ( TimeSpan . FromSeconds ( 5 ) ) , "Garbage creation timed out" ) ;
127
+ shortWeak = @short ;
128
+ longWeak = @long ;
129
+ return handle ;
134
130
}
135
131
136
132
private static long CompareWithFinalizerOn ( PyObject pyCollect , bool enbale )
@@ -191,62 +187,6 @@ public void SimpleTestMemory()
191
187
}
192
188
}
193
189
194
- class MyPyObject : PyObject
195
- {
196
- public MyPyObject ( IntPtr op ) : base ( op )
197
- {
198
- }
199
-
200
- protected override void Dispose ( bool disposing )
201
- {
202
- base . Dispose ( disposing ) ;
203
- GC . SuppressFinalize ( this ) ;
204
- throw new Exception ( "MyPyObject" ) ;
205
- }
206
- internal static void CreateMyPyObject ( IntPtr op )
207
- {
208
- Runtime . Runtime . XIncref ( op ) ;
209
- new MyPyObject ( op ) ;
210
- }
211
- }
212
-
213
- [ Test ]
214
- public void ErrorHandling ( )
215
- {
216
- bool called = false ;
217
- var errorMessage = "" ;
218
- EventHandler < Finalizer . ErrorArgs > handleFunc = ( sender , args ) =>
219
- {
220
- called = true ;
221
- errorMessage = args . Error . Message ;
222
- } ;
223
- Finalizer . Instance . Threshold = 1 ;
224
- Finalizer . Instance . ErrorHandler += handleFunc ;
225
- try
226
- {
227
- WeakReference shortWeak ;
228
- WeakReference longWeak ;
229
- {
230
- MakeAGarbage ( out shortWeak , out longWeak ) ;
231
- var obj = ( PyLong ) longWeak . Target ;
232
- IntPtr handle = obj . Handle ;
233
- shortWeak = null ;
234
- longWeak = null ;
235
- MyPyObject . CreateMyPyObject ( handle ) ;
236
- obj . Dispose ( ) ;
237
- obj = null ;
238
- }
239
- FullGCCollect ( ) ;
240
- Finalizer . Instance . Collect ( ) ;
241
- Assert . IsTrue ( called ) ;
242
- }
243
- finally
244
- {
245
- Finalizer . Instance . ErrorHandler -= handleFunc ;
246
- }
247
- Assert . AreEqual ( errorMessage , "MyPyObject" ) ;
248
- }
249
-
250
190
[ Test ]
251
191
public void ValidateRefCount ( )
252
192
{
@@ -279,36 +219,13 @@ public void ValidateRefCount()
279
219
}
280
220
}
281
221
222
+ [ MethodImpl ( MethodImplOptions . NoInlining | MethodImplOptions . NoOptimization ) ] // ensure lack of references to s1 and s2
282
223
private static IntPtr CreateStringGarbage ( )
283
224
{
284
225
PyString s1 = new PyString ( "test_string" ) ;
285
226
// s2 steal a reference from s1
286
227
PyString s2 = new PyString ( s1 . Handle ) ;
287
228
return s1 . Handle ;
288
229
}
289
-
290
- private static bool WaitForCollected ( IntPtr op , int hash , int milliseconds )
291
- {
292
- var stopwatch = Stopwatch . StartNew ( ) ;
293
- do
294
- {
295
- var garbage = Finalizer . Instance . GetCollectedObjects ( ) ;
296
- foreach ( var item in garbage )
297
- {
298
- // The validation is not 100% precise,
299
- // but it's rare that two conditions satisfied but they're still not the same object.
300
- if ( item . Target . GetHashCode ( ) != hash )
301
- {
302
- continue ;
303
- }
304
- var obj = ( IPyDisposable ) item . Target ;
305
- if ( obj . GetTrackedHandles ( ) . Contains ( op ) )
306
- {
307
- return true ;
308
- }
309
- }
310
- } while ( stopwatch . ElapsedMilliseconds < milliseconds ) ;
311
- return false ;
312
- }
313
230
}
314
231
}
0 commit comments