X Tutup
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Python.Runtime { public class Finalizer { public class CollectArgs : EventArgs { public int ObjectCount { get; set; } } public class ErrorArgs : EventArgs { public Exception Error { get; set; } } public static readonly Finalizer Instance = new Finalizer(); public event EventHandler CollectOnce; public event EventHandler ErrorHandler; public int Threshold { get; set; } public bool Enable { get; set; } private ConcurrentQueue _objQueue = new ConcurrentQueue(); private int _throttled; #region FINALIZER_CHECK #if FINALIZER_CHECK private readonly object _queueLock = new object(); public bool RefCountValidationEnabled { get; set; } = true; #else public readonly bool RefCountValidationEnabled = false; #endif // Keep these declarations for compat even no FINALIZER_CHECK public class IncorrectFinalizeArgs : EventArgs { public IntPtr Handle { get; internal set; } public ICollection ImpactedObjects { get; internal set; } } public class IncorrectRefCountException : Exception { public IntPtr PyPtr { get; internal set; } private string _message; public override string Message => _message; public IncorrectRefCountException(IntPtr ptr) { PyPtr = ptr; IntPtr pyname = Runtime.PyObject_Unicode(PyPtr); string name = Runtime.GetManagedString(pyname); Runtime.XDecref(pyname); _message = $"<{name}> may has a incorrect ref count"; } } public delegate bool IncorrectRefCntHandler(object sender, IncorrectFinalizeArgs e); #pragma warning disable 414 public event IncorrectRefCntHandler IncorrectRefCntResolver = null; #pragma warning restore 414 public bool ThrowIfUnhandleIncorrectRefCount { get; set; } = true; #endregion private Finalizer() { Enable = true; Threshold = 200; } public void Collect() => this.DisposeAll(); internal void ThrottledCollect() { _throttled = unchecked(this._throttled + 1); if (!Enable || _throttled < Threshold) return; _throttled = 0; this.Collect(); } internal List GetCollectedObjects() { return _objQueue.ToList(); } internal void AddFinalizedObject(ref IntPtr obj) { if (!Enable || obj == IntPtr.Zero) { return; } #if FINALIZER_CHECK lock (_queueLock) #endif { this._objQueue.Enqueue(obj); } obj = IntPtr.Zero; } internal static void Shutdown() { Instance.DisposeAll(); } private void DisposeAll() { CollectOnce?.Invoke(this, new CollectArgs() { ObjectCount = _objQueue.Count }); #if FINALIZER_CHECK lock (_queueLock) #endif { #if FINALIZER_CHECK ValidateRefCount(); #endif IntPtr obj; Runtime.PyErr_Fetch(out var errType, out var errVal, out var traceback); try { while (!_objQueue.IsEmpty) { if (!_objQueue.TryDequeue(out obj)) continue; Runtime.XDecref(obj); try { Runtime.CheckExceptionOccurred(); } catch (Exception e) { var handler = ErrorHandler; if (handler is null) { throw new FinalizationException( "Python object finalization failed", disposable: obj, innerException: e); } handler.Invoke(this, new ErrorArgs() { Error = e }); } } } finally { // Python requires finalizers to preserve exception: // https://docs.python.org/3/extending/newtypes.html#finalization-and-de-allocation Runtime.PyErr_Restore(errType, errVal, traceback); } } } #if FINALIZER_CHECK private void ValidateRefCount() { if (!RefCountValidationEnabled) { return; } var counter = new Dictionary(); var holdRefs = new Dictionary(); var indexer = new Dictionary>(); foreach (var obj in _objQueue) { var handle = obj; if (!counter.ContainsKey(handle)) { counter[handle] = 0; } counter[handle]++; if (!holdRefs.ContainsKey(handle)) { holdRefs[handle] = Runtime.Refcount(handle); } List objs; if (!indexer.TryGetValue(handle, out objs)) { objs = new List(); indexer.Add(handle, objs); } objs.Add(obj); } foreach (var pair in counter) { IntPtr handle = pair.Key; long cnt = pair.Value; // Tracked handle's ref count is larger than the object's holds // it may take an unspecified behaviour if it decref in Dispose if (cnt > holdRefs[handle]) { var args = new IncorrectFinalizeArgs() { Handle = handle, ImpactedObjects = indexer[handle] }; bool handled = false; if (IncorrectRefCntResolver != null) { var funcList = IncorrectRefCntResolver.GetInvocationList(); foreach (IncorrectRefCntHandler func in funcList) { if (func(this, args)) { handled = true; break; } } } if (!handled && ThrowIfUnhandleIncorrectRefCount) { throw new IncorrectRefCountException(handle); } } // Make sure no other references for PyObjects after this method indexer[handle].Clear(); } indexer.Clear(); } #endif } public class FinalizationException : Exception { public IntPtr PythonObject { get; } public FinalizationException(string message, IntPtr disposable, Exception innerException) : base(message, innerException) { if (disposable == IntPtr.Zero) throw new ArgumentNullException(nameof(disposable)); this.PythonObject = disposable; } } }
X Tutup