/* * Process Hacker - * disposable object base functionality * * Copyright (C) 2009 wj32 * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ /* If enabled, the object system will keep statistics. */ #define ENABLE_STATISTICS /* If enabled, the finalizers on objects can be enabled and disabled. * If disabled, the finalizers on objects can only be disabled. */ //#define EXTENDED_FINALIZER /* If enabled, the object system will keep a list of live objects. */ //#define DEBUG_ENABLE_LIVE_LIST using System; using System.ComponentModel; using System.Threading; namespace ProcessHacker.Common.Objects { /// /// Provides methods for managing a disposable object or resource. /// /// /// /// Each disposable object starts with a reference count of one /// when it is created. The object is not owned by the creator; /// rather, it is owned by the GC (garbage collector). If the user /// does not dispose the object, the finalizer will be called by /// the GC, the reference count will be decremented and the object /// will be freed. If the user chooses to call Dispose, the reference /// count will be decremented and the object will be freed. The /// object is no longer owned by the GC and the finalizer will be /// suppressed. Any further calls to Dispose will have no effect. /// /// /// If the user chooses to use reference counting, the object /// functions normally with the GC. If the object's reference count /// is incremented after it is created and becomes 2, it will be /// decremented when it is finalized or disposed. Only after the /// object is dereferenced will the reference count become 0 and /// the object will be freed. /// /// public abstract class BaseObject : IDisposable, IRefCounted { private const int ObjectOwned = 0x1; private const int ObjectOwnedByGc = 0x2; private const int ObjectDisposed = 0x4; private const int ObjectRefCountShift = 3; private const int ObjectRefCountMask = 0x1fffffff; private const int ObjectRefCountIncrement = 0x8; private static int _createdCount = 0; private static int _freedCount = 0; private static int _disposedCount = 0; private static int _finalizedCount = 0; private static int _referencedCount = 0; private static int _dereferencedCount = 0; #if DEBUG && DEBUG_ENABLE_LIVE_LIST private static System.Collections.Generic.List> _liveList = new System.Collections.Generic.List>(); #endif /// /// Gets the number of disposable, owned objects that have been created. /// public static int CreatedCount { get { return _createdCount; } } /// /// Gets the number of disposable objects that have been freed. /// public static int FreedCount { get { return _freedCount; } } /// /// Gets the number of disposable objects that have been Disposed with managed = true. /// public static int DisposedCount { get { return _disposedCount; } } /// /// Gets the number of disposable objects that have been Disposed with managed = false. /// public static int FinalizedCount { get { return _finalizedCount; } } /// /// Gets the number of times disposable objects have been referenced. /// public static int ReferencedCount { get { return _referencedCount; } } /// /// Gets the number of times disposable objects have been dereferenced. /// public static int DereferencedCount { get { return _dereferencedCount; } } #if DEBUG && DEBUG_ENABLE_LIVE_LIST public static void CleanLiveList() { var list = new System.Collections.Generic.List>(); foreach (var r in _liveList) { if (r.Target != null) list.Add(r); } _liveList = list; } #endif public static T SwapRef(ref T reference, T newObj) where T : class, IRefCounted { T oldObj; // Swap the reference. oldObj = Interlocked.Exchange(ref reference, newObj); // Reference the new object. if (newObj != null) newObj.Reference(); // Dereference the old object. if (oldObj != null) oldObj.Dereference(); return oldObj; } #if DEBUG /// /// A stack trace collected when the object is created. /// private string _creationStackTrace; #endif /// /// An Int32 containing various fields. /// private int _value; #if EXTENDED_FINALIZER /// /// Whether the finalizer will run. /// private int _finalizerRegistered = 1; #endif /// /// Initializes a disposable object. /// public BaseObject() : this(true) { } /// /// Initializes a disposable object. /// /// Whether the resource is owned. public BaseObject(bool owned) { _value = ObjectOwned + ObjectOwnedByGc + ObjectRefCountIncrement; // Don't need to finalize the object if it doesn't need to be disposed. if (!owned) { #if EXTENDED_FINALIZER this.DisableFinalizer(); #else GC.SuppressFinalize(this); #endif _value = 0; } #if ENABLE_STATISTICS if (owned) Interlocked.Increment(ref _createdCount); #endif #if DEBUG _creationStackTrace = Environment.StackTrace; #if DEBUG_ENABLE_LIVE_LIST _liveList.Add(new WeakReference(this)); #endif #endif } /// /// Ensures that the GC does not own the object. /// ~BaseObject() { // Get rid of GC ownership if still present. this.Dispose(false); #if ENABLE_STATISTICS Interlocked.Increment(ref _finalizedCount); // Dispose just incremented this value, but it // shouldn't have been incremented. Interlocked.Decrement(ref _disposedCount); #endif } /// /// Ensures that the GC does not own the object. /// public void Dispose() { this.Dispose(true); } /// /// Ensures that the GC does not own the object. /// /// Whether to dispose managed resources. public void Dispose(bool managed) { int value; if ((_value & ObjectOwned) == 0) return; // Only proceed if the object is owned by the GC, and // clear the owned by GC flag (all atomically). do { value = _value; if ((value & ObjectOwnedByGc) == 0) return; } while (Interlocked.CompareExchange( ref _value, value - ObjectOwnedByGc, value ) != value); // Decrement the reference count. this.Dereference(managed); // Disable the finalizer. if (managed) { #if EXTENDED_FINALIZER this.DisableFinalizer(); #else GC.SuppressFinalize(this); #endif } #if ENABLE_STATISTICS // Stats. Interlocked.Increment(ref _disposedCount); // The dereferenced count should count the number of times // the user has called Dereference, so decrement it // because we just called it. Interlocked.Decrement(ref _dereferencedCount); #endif } /// /// Queues the object for disposal in the current delayed release pool. /// [EditorBrowsable(EditorBrowsableState.Never)] public void DisposeDelayed() { DelayedReleasePool.CurrentPool.AddDispose(this); } /// /// Disposes the resources of the object. This method must not be /// called directly; instead, override this method in a derived class. /// /// Whether or not to dispose managed objects. protected abstract void DisposeObject(bool disposing); /// /// Gets whether the object has been freed. /// public bool Disposed { get { return (_value & ObjectDisposed) != 0; } } /// /// Gets whether the object will be freed. /// public bool Owned { get { return (_value & ObjectOwned) != 0; } } /// /// Gets whether the object is owned by the garbage collector. /// public bool OwnedByGc { get { return (_value & ObjectOwnedByGc) != 0; } } /// /// Gets the current reference count of the object. /// /// /// This information is for debugging purposes ONLY. DO NOT /// base memory management logic upon this value. /// public int ReferenceCount { get { return (_value >> ObjectRefCountShift) & ObjectRefCountMask; } } #if EXTENDED_FINALIZER /// /// Disables the finalizer if it is not already disabled. /// private void DisableFinalizer() { int oldFinalizerRegistered; oldFinalizerRegistered = Interlocked.CompareExchange(ref _finalizerRegistered, 0, 1); if (oldFinalizerRegistered == 1) { GC.SuppressFinalize(this); } } #endif /// /// Declares that the object should no longer be owned. /// protected void DisableOwnership(bool dispose) { int value; if (dispose) this.Dispose(); #if EXTENDED_FINALIZER this.DisableFinalizer(); #else GC.SuppressFinalize(this); #endif do { value = _value; } while (Interlocked.CompareExchange( ref _value, value & ~ObjectOwned, value ) != value); #if ENABLE_STATISTICS // If the object didn't get disposed, pretend the object // never got created. if (!dispose) Interlocked.Decrement(ref _createdCount); #endif } /// /// Decrements the reference count of the object. /// /// The old reference count. /// /// /// DO NOT call Dereference if you have not called Reference. /// Call Dispose instead. /// /// /// If you are calling Dereference from a finalizer, call /// Dereference(false). /// /// public int Dereference() { return this.Dereference(true); } /// /// Decrements the reference count of the object. /// /// Whether to dispose managed resources. /// The new reference count. /// /// If you are calling this method from a finalizer, set /// to false. /// public int Dereference(bool managed) { return this.Dereference(1, managed); } /// /// Decreases the reference count of the object. /// /// The number of times to dereference the object. /// The new reference count. public int Dereference(int count) { return this.Dereference(count, true); } /// /// Decreases the reference count of the object. /// /// The number of times to dereference the object. /// Whether to dispose managed resources. /// The new reference count. public int Dereference(int count, bool managed) { int value; int newRefCount; // Initial parameter validation. if (count == 0) return this.ReferenceCount; if (count < 0) throw new ArgumentException("Cannot dereference a negative number of times."); if ((_value & ObjectOwned) == 0) return 0; #if ENABLE_STATISTICS // Statistics. Interlocked.Add(ref _dereferencedCount, count); #endif // Decrease the reference count. value = Interlocked.Add(ref _value, -ObjectRefCountIncrement * count); newRefCount = (value >> ObjectRefCountShift) & ObjectRefCountMask; // Should not ever happen. if (newRefCount < 0) throw new InvalidOperationException("Reference count cannot be negative."); // Dispose the object if the reference count is 0. if (newRefCount == 0) { // If the dispose object method throws an exception, nothing bad // should happen if it does not invalidate any state. this.DisposeObject(managed); // Set the disposed flag. do { value = _value; } while (Interlocked.CompareExchange( ref _value, value | ObjectDisposed, value ) != value); #if ENABLE_STATISTICS Interlocked.Increment(ref _freedCount); #endif } return newRefCount; } /// /// Queues the object for dereferencing in the current delayed release pool. /// [EditorBrowsable(EditorBrowsableState.Never)] public void DereferenceDelayed() { DelayedReleasePool.CurrentPool.AddDereference(this); } #if EXTENDED_FINALIZER /// /// Enables the finalizer if it is not already enabled. /// private void EnableFinalizer() { int oldFinalizerRegistered; oldFinalizerRegistered = Interlocked.CompareExchange(ref _finalizerRegistered, 1, 0); if (oldFinalizerRegistered == 0) { GC.ReRegisterForFinalize(this); } } #endif /// /// Increments the reference count of the object. /// /// The new reference count. /// /// /// You must call Dereference once (when you are finished with the /// object) to match each call to Reference. Do not call Dispose. /// /// public int Reference() { return this.Reference(1); } /// /// Increases the reference count of the object. /// /// The number of times to reference the object. /// The new reference count. public int Reference(int count) { int value; // Don't do anything if the object isn't owned. if ((_value & ObjectOwned) == 0) return 0; // Parameter validation. if (count == 0) return this.ReferenceCount; if (count < 0) throw new ArgumentException("Cannot reference a negative number of times."); #if ENABLE_STATISTICS Interlocked.Add(ref _referencedCount, count); #endif value = Interlocked.Add(ref _value, ObjectRefCountIncrement * count); return (value >> ObjectRefCountShift) & ObjectRefCountMask; } } }