/*
* 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 .
*/
#define ENABLE_STATISTICS
//#define EXTENDED_FINALIZER
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 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;
///
/// Gets the number of disposable 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; } }
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
///
/// Whether the object is owned (rather, whether this class should
/// take care of anything).
///
private bool _owned = true;
///
/// Whether the object is owned by the garbage collector (to ensure
/// calling Dispose more than once has no effect).
///
private int _ownedByGc = 1;
///
/// The reference count of the object.
///
private int _refCount = 1;
///
/// Whether the object has been freed.
///
private volatile bool _disposed = false;
#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)
{
_owned = owned;
// 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
_ownedByGc = 0;
_refCount = 0;
}
#if ENABLE_STATISTICS
Interlocked.Increment(ref _createdCount);
#endif
#if DEBUG
_creationStackTrace = Environment.StackTrace;
#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)
{
if (!_owned)
return;
Thread.BeginCriticalRegion();
try
{
int oldOwnedByGc;
// Only proceed if the object is owned by the GC. We can perform
// this operation without any locks by using CAS.
oldOwnedByGc = Interlocked.CompareExchange(ref _ownedByGc, 0, 1);
if (oldOwnedByGc == 1)
{
// 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
}
}
finally
{
Thread.EndCriticalRegion();
}
}
///
/// 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 _disposed; }
}
///
/// Gets whether the object will be freed.
///
public bool Owned
{
get { return _owned; }
}
///
/// Gets whether the object is owned by the garbage collector.
///
public bool OwnedByGc
{
get { return _ownedByGc == 1; }
}
///
/// 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 Thread.VolatileRead(ref _refCount); }
}
#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)
{
if (dispose)
this.Dispose();
#if EXTENDED_FINALIZER
this.DisableFinalizer();
#else
GC.SuppressFinalize(this);
#endif
_owned = false;
#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)
{
// Initial parameter validation.
if (count == 0)
return Interlocked.Add(ref _refCount, 0);
if (count < 0)
throw new ArgumentException("Cannot dereference a negative number of times.");
// Critical, prevent thread abortion.
Thread.BeginCriticalRegion();
try
{
if (!_owned)
return 0;
#if ENABLE_STATISTICS
// Statistics.
Interlocked.Add(ref _dereferencedCount, count);
#endif
// Decrease the reference count.
int newRefCount = Interlocked.Add(ref _refCount, -count);
// 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 && !_disposed)
{
// If the dispose object method throws an exception, nothing bad
// should happen if it does not invalidate any state.
this.DisposeObject(managed);
// Prevent the object from being disposed twice.
_disposed = true;
#if ENABLE_STATISTICS
Interlocked.Increment(ref _freedCount);
#endif
}
return newRefCount;
}
finally
{
Thread.EndCriticalRegion();
}
}
///
/// 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)
{
// Don't do anything if the object isn't owned.
if (!_owned)
return 0;
// Parameter validation.
if (count == 0)
return Interlocked.Add(ref _refCount, 0);
if (count < 0)
throw new ArgumentException("Cannot reference a negative number of times.");
#if ENABLE_STATISTICS
Interlocked.Add(ref _referencedCount, count);
#endif
return Interlocked.Add(ref _refCount, count);
}
}
}