| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -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 | |
|---|---|---|---|
@@ -52,11 +52,7 @@ public void CollectBasicObject() | |||
| 52 | 52 | Assert.IsFalse(called, "The event handler was called before it was installed"); | |
| 53 | 53 | Finalizer.Instance.CollectOnce += handler; | |
| 54 | 54 | ||
| 55 | - WeakReference shortWeak; | ||
| 56 | - WeakReference longWeak; | ||
| 57 | - { | ||
| 58 | - MakeAGarbage(out shortWeak, out longWeak); | ||
| 59 | - } | ||
| 55 | + IntPtr pyObj = MakeAGarbage(out var shortWeak, out var longWeak); | ||
| 60 | 56 | FullGCCollect(); | |
| 61 | 57 | // The object has been resurrected | |
| 62 | 58 | Warn.If( | |
@@ -74,7 +70,7 @@ public void CollectBasicObject() | |||
| 74 | 70 | var garbage = Finalizer.Instance.GetCollectedObjects(); | |
| 75 | 71 | Assert.NotZero(garbage.Count, "There should still be garbage around"); | |
| 76 | 72 | Warn.Unless( | |
| 77 | - garbage.Any(T => ReferenceEquals(T.Target, longWeak.Target)), | ||
| 73 | + garbage.Contains(pyObj), | ||
| 78 | 74 | $"The {nameof(longWeak)} reference doesn't show up in the garbage list", | |
| 79 | 75 | garbage | |
| 80 | 76 | ); | |
@@ -98,9 +94,9 @@ public void CollectOnShutdown() | |||
| 98 | 94 | IntPtr op = MakeAGarbage(out var shortWeak, out var longWeak); | |
| 99 | 95 | FullGCCollect(); | |
| 100 | 96 | Assert.IsFalse(shortWeak.IsAlive); | |
| 101 | - List<WeakReference> garbage = Finalizer.Instance.GetCollectedObjects(); | ||
| 97 | + List<IntPtr> garbage = Finalizer.Instance.GetCollectedObjects(); | ||
| 102 | 98 | Assert.IsNotEmpty(garbage, "The garbage object should be collected"); | |
| 103 | - Assert.IsTrue(garbage.Any(r => ReferenceEquals(r.Target, longWeak.Target)), | ||
| 99 | + Assert.IsTrue(garbage.Contains(op), | ||
| 104 | 100 | "Garbage should contains the collected object"); | |
| 105 | 101 | ||
| 106 | 102 | PythonEngine.Shutdown(); | |
@@ -191,63 +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 | - [Obsolete("GC tests are not guaranteed")] | ||
| 215 | - public void ErrorHandling() | ||
| 216 | - { | ||
| 217 | - bool called = false; | ||
| 218 | - var errorMessage = ""; | ||
| 219 | - EventHandler<Finalizer.ErrorArgs> handleFunc = (sender, args) => | ||
| 220 | - { | ||
| 221 | - called = true; | ||
| 222 | - errorMessage = args.Error.Message; | ||
| 223 | - }; | ||
| 224 | - Finalizer.Instance.Threshold = 1; | ||
| 225 | - Finalizer.Instance.ErrorHandler += handleFunc; | ||
| 226 | - try | ||
| 227 | - { | ||
| 228 | - WeakReference shortWeak; | ||
| 229 | - WeakReference longWeak; | ||
| 230 | - { | ||
| 231 | - MakeAGarbage(out shortWeak, out longWeak); | ||
| 232 | - var obj = (PyObject)longWeak.Target; | ||
| 233 | - IntPtr handle = obj.Handle; | ||
| 234 | - shortWeak = null; | ||
| 235 | - longWeak = null; | ||
| 236 | - MyPyObject.CreateMyPyObject(handle); | ||
| 237 | - obj.Dispose(); | ||
| 238 | - obj = null; | ||
| 239 | - } | ||
| 240 | - FullGCCollect(); | ||
| 241 | - Finalizer.Instance.Collect(); | ||
| 242 | - Assert.IsTrue(called); | ||
| 243 | - } | ||
| 244 | - finally | ||
| 245 | - { | ||
| 246 | - Finalizer.Instance.ErrorHandler -= handleFunc; | ||
| 247 | - } | ||
| 248 | - Assert.AreEqual(errorMessage, "MyPyObject"); | ||
| 249 | - } | ||
| 250 | - | ||
| 251 | 190 | [Test] | |
| 252 | 191 | public void ValidateRefCount() | |
| 253 | 192 | { | |
| 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 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -27,7 +27,7 @@ public class ErrorArgs : EventArgs | |||
| 27 | 27 | public int Threshold { get; set; } | |
| 28 | 28 | public bool Enable { get; set; } | |
| 29 | 29 | ||
| 30 | - private ConcurrentQueue<IPyDisposable> _objQueue = new ConcurrentQueue<IPyDisposable>(); | ||
| 30 | + private ConcurrentQueue<IntPtr> _objQueue = new ConcurrentQueue<IntPtr>(); | ||
| 31 | 31 | private int _throttled; | |
| 32 | 32 | ||
| 33 | 33 | #region FINALIZER_CHECK | |
@@ -42,7 +42,7 @@ public class ErrorArgs : EventArgs | |||
| 42 | 42 | public class IncorrectFinalizeArgs : EventArgs | |
| 43 | 43 | { | |
| 44 | 44 | public IntPtr Handle { get; internal set; } | |
| 45 | - public ICollection<IPyDisposable> ImpactedObjects { get; internal set; } | ||
| 45 | + public ICollection<IntPtr> ImpactedObjects { get; internal set; } | ||
| 46 | 46 | } | |
| 47 | 47 | ||
| 48 | 48 | public class IncorrectRefCountException : Exception | |
@@ -73,8 +73,6 @@ private Finalizer() | |||
| 73 | 73 | Threshold = 200; | |
| 74 | 74 | } | |
| 75 | 75 | ||
| 76 | - [Obsolete("forceDispose parameter is unused. All objects are disposed regardless.")] | ||
| 77 | - public void Collect(bool forceDispose) => this.DisposeAll(); | ||
| 78 | 76 | public void Collect() => this.DisposeAll(); | |
| 79 | 77 | ||
| 80 | 78 | internal void ThrottledCollect() | |
@@ -85,14 +83,14 @@ internal void ThrottledCollect() | |||
| 85 | 83 | this.Collect(); | |
| 86 | 84 | } | |
| 87 | 85 | ||
| 88 | - public List<WeakReference> GetCollectedObjects() | ||
| 86 | + internal List<IntPtr> GetCollectedObjects() | ||
| 89 | 87 | { | |
| 90 | - return _objQueue.Select(T => new WeakReference(T)).ToList(); | ||
| 88 | + return _objQueue.ToList(); | ||
| 91 | 89 | } | |
| 92 | 90 | ||
| 93 | - internal void AddFinalizedObject(IPyDisposable obj) | ||
| 91 | + internal void AddFinalizedObject(ref IntPtr obj) | ||
| 94 | 92 | { | |
| 95 | - if (!Enable) | ||
| 93 | + if (!Enable || obj == IntPtr.Zero) | ||
| 96 | 94 | { | |
| 97 | 95 | return; | |
| 98 | 96 | } | |
@@ -103,6 +101,7 @@ internal void AddFinalizedObject(IPyDisposable obj) | |||
| 103 | 101 | { | |
| 104 | 102 | this._objQueue.Enqueue(obj); | |
| 105 | 103 | } | |
| 104 | + obj = IntPtr.Zero; | ||
| 106 | 105 | } | |
| 107 | 106 | ||
| 108 | 107 | internal static void Shutdown() | |
@@ -123,29 +122,44 @@ private void DisposeAll() | |||
| 123 | 122 | #if FINALIZER_CHECK | |
| 124 | 123 | ValidateRefCount(); | |
| 125 | 124 | #endif | |
| 126 | - IPyDisposable obj; | ||
| 127 | - while (_objQueue.TryDequeue(out obj)) | ||
| 125 | + IntPtr obj; | ||
| 126 | + Runtime.PyErr_Fetch(out var errType, out var errVal, out var traceback); | ||
| 127 | + | ||
| 128 | + try | ||
| 128 | 129 | { | |
| 129 | - try | ||
| 130 | - { | ||
| 131 | - obj.Dispose(); | ||
| 132 | - } | ||
| 133 | - catch (Exception e) | ||
| 130 | + while (!_objQueue.IsEmpty) | ||
| 134 | 131 | { | |
| 135 | - var handler = ErrorHandler; | ||
| 136 | - if (handler is null) | ||
| 132 | + if (!_objQueue.TryDequeue(out obj)) | ||
| 133 | + continue; | ||
| 134 | + | ||
| 135 | + Runtime.XDecref(obj); | ||
| 136 | + try | ||
| 137 | 137 | { | |
| 138 | - throw new FinalizationException( | ||
| 139 | - "Python object finalization failed", | ||
| 140 | - disposable: obj, innerException: e); | ||
| 138 | + Runtime.CheckExceptionOccurred(); | ||
| 141 | 139 | } | |
| 142 | - | ||
| 143 | - handler.Invoke(this, new ErrorArgs() | ||
| 140 | + catch (Exception e) | ||
| 144 | 141 | { | |
| 145 | - Error = e | ||
| 146 | - }); | ||
| 142 | + var handler = ErrorHandler; | ||
| 143 | + if (handler is null) | ||
| 144 | + { | ||
| 145 | + throw new FinalizationException( | ||
| 146 | + "Python object finalization failed", | ||
| 147 | + disposable: obj, innerException: e); | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + handler.Invoke(this, new ErrorArgs() | ||
| 151 | + { | ||
| 152 | + Error = e | ||
| 153 | + }); | ||
| 154 | + } | ||
| 147 | 155 | } | |
| 148 | 156 | } | |
| 157 | + finally | ||
| 158 | + { | ||
| 159 | + // Python requires finalizers to preserve exception: | ||
| 160 | + // https://docs.python.org/3/extending/newtypes.html#finalization-and-de-allocation | ||
| 161 | + Runtime.PyErr_Restore(errType, errVal, traceback); | ||
| 162 | + } | ||
| 149 | 163 | } | |
| 150 | 164 | } | |
| 151 | 165 | ||
@@ -158,33 +172,26 @@ private void ValidateRefCount() | |||
| 158 | 172 | } | |
| 159 | 173 | var counter = new Dictionary<IntPtr, long>(); | |
| 160 | 174 | var holdRefs = new Dictionary<IntPtr, long>(); | |
| 161 | - var indexer = new Dictionary<IntPtr, List<IPyDisposable>>(); | ||
| 175 | + var indexer = new Dictionary<IntPtr, List<IntPtr>>(); | ||
| 162 | 176 | foreach (var obj in _objQueue) | |
| 163 | 177 | { | |
| 164 | - IntPtr[] handles = obj.GetTrackedHandles(); | ||
| 165 | - foreach (var handle in handles) | ||
| 178 | + var handle = obj; | ||
| 179 | + if (!counter.ContainsKey(handle)) | ||
| 166 | 180 | { | |
| 167 | - if (handle == IntPtr.Zero) | ||
| 168 | - { | ||
| 169 | - continue; | ||
| 170 | - } | ||
| 171 | - if (!counter.ContainsKey(handle)) | ||
| 172 | - { | ||
| 173 | - counter[handle] = 0; | ||
| 174 | - } | ||
| 175 | - counter[handle]++; | ||
| 176 | - if (!holdRefs.ContainsKey(handle)) | ||
| 177 | - { | ||
| 178 | - holdRefs[handle] = Runtime.Refcount(handle); | ||
| 179 | - } | ||
| 180 | - List<IPyDisposable> objs; | ||
| 181 | - if (!indexer.TryGetValue(handle, out objs)) | ||
| 182 | - { | ||
| 183 | - objs = new List<IPyDisposable>(); | ||
| 184 | - indexer.Add(handle, objs); | ||
| 185 | - } | ||
| 186 | - objs.Add(obj); | ||
| 181 | + counter[handle] = 0; | ||
| 182 | + } | ||
| 183 | + counter[handle]++; | ||
| 184 | + if (!holdRefs.ContainsKey(handle)) | ||
| 185 | + { | ||
| 186 | + holdRefs[handle] = Runtime.Refcount(handle); | ||
| 187 | + } | ||
| 188 | + List<IntPtr> objs; | ||
| 189 | + if (!indexer.TryGetValue(handle, out objs)) | ||
| 190 | + { | ||
| 191 | + objs = new List<IntPtr>(); | ||
| 192 | + indexer.Add(handle, objs); | ||
| 187 | 193 | } | |
| 194 | + objs.Add(obj); | ||
| 188 | 195 | } | |
| 189 | 196 | foreach (var pair in counter) | |
| 190 | 197 | { | |
@@ -227,12 +234,13 @@ private void ValidateRefCount() | |||
| 227 | 234 | ||
| 228 | 235 | public class FinalizationException : Exception | |
| 229 | 236 | { | |
| 230 | - public IPyDisposable Disposable { get; } | ||
| 237 | + public IntPtr PythonObject { get; } | ||
| 231 | 238 | ||
| 232 | - public FinalizationException(string message, IPyDisposable disposable, Exception innerException) | ||
| 239 | + public FinalizationException(string message, IntPtr disposable, Exception innerException) | ||
| 233 | 240 | : base(message, innerException) | |
| 234 | 241 | { | |
| 235 | - this.Disposable = disposable ?? throw new ArgumentNullException(nameof(disposable)); | ||
| 242 | + if (disposable == IntPtr.Zero) throw new ArgumentNullException(nameof(disposable)); | ||
| 243 | + this.PythonObject = disposable; | ||
| 236 | 244 | } | |
| 237 | 245 | } | |
| 238 | 246 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,7 +7,7 @@ | |||
| 7 | 7 | ||
| 8 | 8 | namespace Python.Runtime | |
| 9 | 9 | { | |
| 10 | - public sealed class PyBuffer : IPyDisposable | ||
| 10 | + public sealed class PyBuffer : IDisposable | ||
| 11 | 11 | { | |
| 12 | 12 | private PyObject _exporter; | |
| 13 | 13 | private Py_buffer _view; | |
@@ -236,7 +236,7 @@ private void Dispose(bool disposing) | |||
| 236 | 236 | { | |
| 237 | 237 | return; | |
| 238 | 238 | } | |
| 239 | - Finalizer.Instance.AddFinalizedObject(this); | ||
| 239 | + Finalizer.Instance.AddFinalizedObject(ref _view.obj); | ||
| 240 | 240 | } | |
| 241 | 241 | ||
| 242 | 242 | /// <summary> | |
@@ -248,10 +248,5 @@ public void Dispose() | |||
| 248 | 248 | Dispose(true); | |
| 249 | 249 | GC.SuppressFinalize(this); | |
| 250 | 250 | } | |
| 251 | - | ||
| 252 | - public IntPtr[] GetTrackedHandles() | ||
| 253 | - { | ||
| 254 | - return new IntPtr[] { _view.obj }; | ||
| 255 | - } | ||
| 256 | 251 | } | |
| 257 | 252 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments