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 bool _pending = false; private readonly object _collectingLock = new object(); private Task _finalizerTask; #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); public event IncorrectRefCntHandler IncorrectRefCntResolver; public bool ThrowIfUnhandleIncorrectRefCount { get; set; } = true; #endregion private Finalizer() { Enable = true; Threshold = 200; } public void Collect(bool forceDispose = true) { if (Instance._finalizerTask != null && !Instance._finalizerTask.IsCompleted) { var ts = PythonEngine.BeginAllowThreads(); Instance._finalizerTask.Wait(); PythonEngine.EndAllowThreads(ts); } else if (forceDispose) { Instance.DisposeAll(); } } public List GetCollectedObjects() { return _objQueue.Select(T => new WeakReference(T)).ToList(); } internal void AddFinalizedObject(IPyDisposable obj) { if (!Enable) { return; } if (Runtime.Py_IsInitialized() == 0) { // XXX: Memory will leak if a PyObject finalized after Python shutdown, // for avoiding that case, user should call GC.Collect manual before shutdown. return; } #if FINALIZER_CHECK lock (_queueLock) #endif { _objQueue.Enqueue(obj); } GC.ReRegisterForFinalize(obj); if (!_pending && _objQueue.Count >= Threshold) { AddPendingCollect(); } } internal static void Shutdown() { if (Runtime.Py_IsInitialized() == 0) { Instance._objQueue = new ConcurrentQueue(); return; } Instance.Collect(forceDispose: true); } private void AddPendingCollect() { if(Monitor.TryEnter(_collectingLock)) { try { if (!_pending) { _pending = true; // should already be complete but just in case _finalizerTask?.Wait(); _finalizerTask = Task.Factory.StartNew(() => { using (Py.GIL()) { Instance.DisposeAll(); _pending = false; } }); } } finally { Monitor.Exit(_collectingLock); } } } private void DisposeAll() { CollectOnce?.Invoke(this, new CollectArgs() { ObjectCount = _objQueue.Count }); #if FINALIZER_CHECK lock (_queueLock) #endif { #if FINALIZER_CHECK ValidateRefCount(); #endif IPyDisposable obj; while (_objQueue.TryDequeue(out obj)) { try { obj.Dispose(); Runtime.CheckExceptionOccurred(); } catch (Exception e) { // We should not bother the main thread ErrorHandler?.Invoke(this, new ErrorArgs() { Error = e }); } } } } #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) { IntPtr[] handles = obj.GetTrackedHandles(); foreach (var handle in handles) { if (handle == IntPtr.Zero) { continue; } 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 } }
X Tutup