[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/ra2003/pythonnet/precise-API-references/src/runtime/finalizer.cs [Back]  [Original]

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
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 ErrorArgs(Exception error)
            {
                Error = error ?? throw new ArgumentNullException(nameof(error));
            }
            public bool Handled { get; set; }
            public Exception Error { get; }
        }

        public static readonly Finalizer Instance = new Finalizer();

        public event EventHandler? BeforeCollect;
        public event EventHandler? ErrorHandler;

        const int DefaultThreshold = 200;
        [DefaultValue(DefaultThreshold)]
        public int Threshold { get; set; } = DefaultThreshold;

        [DefaultValue(true)]
        public bool Enable { get; set; } = true;

        private ConcurrentQueue _objQueue = new ConcurrentQueue();
        private int _throttled;

        #region FINALIZER_CHECK

#if FINALIZER_CHECK
        private readonly object _queueLock = new object();
        internal bool RefCountValidationEnabled { get; set; } = true;
#else
        internal bool RefCountValidationEnabled { get; set; } = false;
#endif
        // Keep these declarations for compat even no FINALIZER_CHECK
        internal class IncorrectFinalizeArgs : EventArgs
        {
            public IncorrectFinalizeArgs(IntPtr handle, IReadOnlyCollection imacted)
            {
                Handle = handle;
                ImpactedObjects = imacted;
            }
            public IntPtr Handle { get; }
            public BorrowedReference Reference => new(Handle);
            public IReadOnlyCollection ImpactedObjects { get; }
        }

        internal class IncorrectRefCountException : Exception
        {
            public IntPtr PyPtr { get; internal set; }
            string? message;
            public override string Message
            {
                get
                {
                    if (message is not null) return message;
                    var gil = PythonEngine.AcquireLock();
                    try
                    {
                        using var pyname = Runtime.PyObject_Str(new BorrowedReference(PyPtr));
                        string name = Runtime.GetManagedString(pyname.BorrowOrThrow()) ?? Util.BadStr;
                        message = $" may has a incorrect ref count";
                    }
                    finally
                    {
                        PythonEngine.ReleaseLock(gil);
                    }
                    return message;
                }
            }

            internal IncorrectRefCountException(IntPtr ptr)
            {
                PyPtr = ptr;
                
            }
        }

        internal delegate bool IncorrectRefCntHandler(object sender, IncorrectFinalizeArgs e);
        #pragma warning disable 414
        internal event IncorrectRefCntHandler? IncorrectRefCntResolver = null;
        #pragma warning restore 414
        internal bool ThrowIfUnhandleIncorrectRefCount { get; set; } = true;

        #endregion

        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()
        {
            BeforeCollect?.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;

                        IntPtr copyForException = obj;
                        Runtime.XDecref(StolenReference.Take(ref obj));
                        try
                        {
                            Runtime.CheckExceptionOccurred();
                        }
                        catch (Exception e)
                        {
                            var errorArgs = new ErrorArgs(e);

                            ErrorHandler?.Invoke(this, errorArgs);

                            if (!errorArgs.Handled)
                            {
                                throw new FinalizationException(
                                    "Python object finalization failed",
                                    disposable: copyForException, innerException: e);
                            }
                        }
                    }
                }
                finally
                {
                    // Python requires finalizers to preserve exception:
                    // https://docs.python.org/3/extending/newtypes.html#finalization-and-de-allocation
                    Runtime.PyErr_Restore(errType.StealNullable(), errVal.StealNullable(), traceback.StealNullable());
                }
            }
        }

#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 Handle { get; }

        /// 
        /// Gets the object, whose finalization failed.
        ///
        /// If this function crashes, you can also try ,
        /// which does not attempt to increase the object reference count.
        /// 
        public PyObject GetObject() => new(new BorrowedReference(this.Handle));
        /// 
        /// Gets the object, whose finalization failed without incrementing
        /// its reference count. This should only ever be called during debugging.
        /// When the result is disposed or finalized, the program will crash.
        /// 
        public PyObject DebugGetObject()
        {
            IntPtr dangerousNoIncRefCopy = this.Handle;
            return new(StolenReference.Take(ref dangerousNoIncRefCopy));
        }

        public FinalizationException(string message, IntPtr disposable, Exception innerException)
            : base(message, innerException)
        {
            if (disposable == IntPtr.Zero) throw new ArgumentNullException(nameof(disposable));
            this.Handle = disposable;
        }
    }
}

Web Proxy Viewer  |  New URL  |  Original Page