| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
10 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -191,7 +191,7 @@ from Python.EmbeddingTest.Domain import MyClass | |||
| 191 | 191 | def test_obj_call(): | |
| 192 | 192 | obj = MyClass() | |
| 193 | 193 | obj.Method() | |
| 194 | - obj.StaticMethod() | ||
| 194 | + MyClass.StaticMethod() | ||
| 195 | 195 | obj.Property = 1 | |
| 196 | 196 | obj.Field = 10 | |
| 197 | 197 | ||
@@ -288,7 +288,7 @@ void ExecTest() | |||
| 288 | 288 | ||
| 289 | 289 | GC.Collect(); | |
| 290 | 290 | GC.WaitForPendingFinalizers(); // <- this will put former `num` into Finalizer queue | |
| 291 | - Finalizer.Instance.Collect(forceDispose: true); | ||
| 291 | + Finalizer.Instance.Collect(); | ||
| 292 | 292 | // ^- this will call PyObject.Dispose, which will call XDecref on `num.Handle`, | |
| 293 | 293 | // but Python interpreter from "run" 1 is long gone, so it will corrupt memory instead. | |
| 294 | 294 | Assert.False(numRef.IsAlive); | |
@@ -333,7 +333,7 @@ void ExecTest() | |||
| 333 | 333 | PythonEngine.Initialize(); // <- "run" 2 starts | |
| 334 | 334 | GC.Collect(); | |
| 335 | 335 | GC.WaitForPendingFinalizers(); | |
| 336 | - Finalizer.Instance.Collect(forceDispose: true); | ||
| 336 | + Finalizer.Instance.Collect(); | ||
| 337 | 337 | Assert.False(objRef.IsAlive); | |
| 338 | 338 | } | |
| 339 | 339 | finally | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,9 +2,9 @@ | |||
| 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 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -137,5 +137,12 @@ public static void PrintHexBytes(byte[] bytes) | |||
| 137 | 137 | Console.WriteLine(); | |
| 138 | 138 | } | |
| 139 | 139 | } | |
| 140 | + | ||
| 141 | + [Conditional("DEBUG")] | ||
| 142 | + public static void AssertHasReferences(IntPtr obj) | ||
| 143 | + { | ||
| 144 | + long refcount = Runtime.Refcount(obj); | ||
| 145 | + Debug.Assert(refcount > 0, "Object refcount is 0 or less"); | ||
| 146 | + } | ||
| 140 | 147 | } | |
| 141 | 148 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -181,7 +181,7 @@ A possible alternate strategy would be to create custom subclasses | |||
| 181 | 181 | too "special" for this to work. It would be more work, so for now | |
| 182 | 182 | the 80/20 rule applies :) */ | |
| 183 | 183 | ||
| 184 | - public class Dispatcher : IPyDisposable | ||
| 184 | + public class Dispatcher | ||
| 185 | 185 | { | |
| 186 | 186 | public IntPtr target; | |
| 187 | 187 | public Type dtype; | |
@@ -202,7 +202,7 @@ public Dispatcher(IntPtr target, Type dtype) | |||
| 202 | 202 | return; | |
| 203 | 203 | } | |
| 204 | 204 | _finalized = true; | |
| 205 | - Finalizer.Instance.AddFinalizedObject(this); | ||
| 205 | + Finalizer.Instance.AddFinalizedObject(ref target); | ||
| 206 | 206 | } | |
| 207 | 207 | ||
| 208 | 208 | public void Dispose() | |
@@ -276,11 +276,6 @@ public object TrueDispatch(ArrayList args) | |||
| 276 | 276 | Runtime.XDecref(op); | |
| 277 | 277 | return result; | |
| 278 | 278 | } | |
| 279 | - | ||
| 280 | - public IntPtr[] GetTrackedHandles() | ||
| 281 | - { | ||
| 282 | - return new IntPtr[] { target }; | ||
| 283 | - } | ||
| 284 | 279 | } | |
| 285 | 280 | ||
| 286 | 281 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments